diff --git a/.codecov.yml b/.codecov.yml index d724eb3c1d0b..04dae7644042 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,6 +1,8 @@ +comment: false coverage: status: project: default: threshold: 1 + informational: true patch: off diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000000..2a4ad4ec1b55 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,6 @@ +[*.{kt,kts}] +ij_kotlin_allow_trailing_comma = false +ij_kotlin_allow_trailing_comma_on_call_site = false + +[*.kts] +indent_style = tab diff --git a/.gitattributes b/.gitattributes index 252299c5609a..d836e4264a62 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,3 +4,4 @@ *.key binary *.jar binary *.ttf binary +release-notes-* merge=union diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 1bdc52cd3615..90d0d4bdaed2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -16,4 +16,3 @@ I hereby agree to the terms of the [JUnit Contributor License Agreement](https:/ - [ ] Change is covered by [automated tests](https://github.com/junit-team/junit5/blob/HEAD/CONTRIBUTING.md#tests) including corner cases, errors, and exception handling - [ ] Public API has [Javadoc](https://github.com/junit-team/junit5/blob/HEAD/CONTRIBUTING.md#javadoc) and [`@API` annotations](https://apiguardian-team.github.io/apiguardian/docs/current/api/org/apiguardian/api/API.html) - [ ] Change is documented in the [User Guide](https://junit.org/junit5/docs/snapshot/user-guide/) and [Release Notes](https://junit.org/junit5/docs/snapshot/user-guide/#release-notes) -- [ ] All [continuous integration builds](https://github.com/junit-team/junit5#continuous-integration-builds) pass diff --git a/.github/actions/main-build/action.yml b/.github/actions/main-build/action.yml new file mode 100644 index 000000000000..a7817cd314f0 --- /dev/null +++ b/.github/actions/main-build/action.yml @@ -0,0 +1,19 @@ +name: Main build +description: Sets up JDKs and runs Gradle +inputs: + arguments: + required: true + description: Gradle arguments + default: build +runs: + using: "composite" + steps: + - uses: ./.github/actions/setup-test-jdk + - uses: ./.github/actions/run-gradle + with: + arguments: ${{ inputs.arguments }} + - uses: actions/upload-artifact@v3 + if: ${{ always() }} + with: + name: Test Distribution trace files (${{ github.job }}) + path: '**/build/test-results/*/trace.json' diff --git a/.github/actions/run-gradle/action.yml b/.github/actions/run-gradle/action.yml new file mode 100644 index 000000000000..a4b1bc129f6c --- /dev/null +++ b/.github/actions/run-gradle/action.yml @@ -0,0 +1,25 @@ +name: Run Gradle +description: Sets up Gradle JDKs and runs Gradle +inputs: + arguments: + required: true + description: Gradle arguments + default: build +runs: + using: "composite" + steps: + - uses: actions/setup-java@v3 + id: setup-gradle-jdk + with: + distribution: temurin + java-version: 17 + - uses: gradle/gradle-build-action@v2 + env: + JAVA_HOME: ${{ steps.setup-gradle-jdk.outputs.path }} + with: + arguments: | + -Porg.gradle.java.installations.auto-download=false + -Penterprise.predictiveTestSelection.enabled=${{ github.event_name == 'pull_request' }} + "-Dscan.value.GitHub job=${{ github.job }}" + javaToolchains + ${{ inputs.arguments }} diff --git a/.github/actions/setup-test-jdk/action.yml b/.github/actions/setup-test-jdk/action.yml new file mode 100644 index 000000000000..4e8c96266c69 --- /dev/null +++ b/.github/actions/setup-test-jdk/action.yml @@ -0,0 +1,11 @@ +name: Set up Test JDK +description: Sets up the JDK required to run platform-tooling-support-tests +runs: + using: "composite" + steps: + - uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 8 + - shell: bash + run: echo "JDK8=$JAVA_HOME" >> $GITHUB_ENV diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000000..5c19f255d870 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,35 @@ +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/workflows/close-inactive-issues.yml b/.github/workflows/close-inactive-issues.yml new file mode 100644 index 000000000000..ab8bb97a6f28 --- /dev/null +++ b/.github/workflows/close-inactive-issues.yml @@ -0,0 +1,31 @@ +name: Close inactive issues and PRs +on: + schedule: + - cron: "30 1 * * *" + workflow_dispatch: +jobs: + close-issues: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@v8 + with: + only-labels: "status: waiting-for-feedback" + days-before-stale: 14 + days-before-close: 21 + stale-issue-label: "status: stale" + stale-pr-label: "status: stale" + stale-issue-message: > + If you would like us to be able to process this issue, please provide the requested information. + If the information is not provided within the next 3 weeks, we will be unable to proceed and this issue will be closed. + close-issue-message: > + Closing due to lack of requested feedback. + If you would like to proceed with your contribution, please provide the requested information and we will re-open this issue. + stale-pr-message: > + If you would like us to be able to process this pull request, please provide the requested information or make the requested changes. + If the information is not provided or the requested changes are not made within the next 3 weeks, we will be unable to proceed and this pull request will be closed. + close-pr-message: > + Closing due to lack of requested feedback. + If you would like to proceed with your contribution, please provide the requested information or make the requested changes, and we will re-open this pull request. diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000000..63b648c7b424 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,47 @@ +name: "CodeQL" + +on: + push: + branches: + - main + - 'releases/**' + pull_request: + # The branches below must be a subset of the branches above + branches: + - main + - 'releases/**' + schedule: + - cron: '0 19 * * 3' + +env: + GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + security-events: write + strategy: + fail-fast: false + matrix: + language: + - java + - javascript + steps: + - name: Check out repository + uses: actions/checkout@v3 + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + tools: latest + - name: Build + uses: ./.github/actions/run-gradle + with: + arguments: | + --no-build-cache + -Dscan.tag.CodeQL + allMainClasses + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/combine-prs.yml b/.github/workflows/combine-prs.yml new file mode 100644 index 000000000000..826472911980 --- /dev/null +++ b/.github/workflows/combine-prs.yml @@ -0,0 +1,16 @@ +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.amrom.workers.devbine-prs@v3.1.1 + with: + github_token: ${{ secrets.GH_TOKEN }} diff --git a/.github/workflows/cross-version.yml b/.github/workflows/cross-version.yml index 05e5a3d5c08e..8480a68a49c7 100644 --- a/.github/workflows/cross-version.yml +++ b/.github/workflows/cross-version.yml @@ -4,46 +4,50 @@ on: push: branches: - main - - 'releases/*' + - 'releases/**' pull_request: branches: - '*' env: - ORG_GRADLE_PROJECT_junitBuildCacheUsername: ${{ secrets.BUILD_CACHE_USERNAME }} - ORG_GRADLE_PROJECT_junitBuildCachePassword: ${{ secrets.BUILD_CACHE_PASSWORD }} + ENTERPRISE_TESTDISTRIBUTION_ENABLED: true + BUILDCACHE_USERNAME: ${{ secrets.BUILD_CACHE_USERNAME }} + BUILDCACHE_PASSWORD: ${{ secrets.BUILD_CACHE_PASSWORD }} + GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} jobs: openjdk: strategy: + fail-fast: false matrix: - jdk: [14, 15, 16] + jdk: [20, 21, 22] name: "OpenJDK ${{ matrix.jdk }}" runs-on: ubuntu-latest - container: "junitteam/build:${{ matrix.jdk }}" steps: - - uses: actions/checkout@v2 - - name: Cache Gradle wrapper and dependencies - uses: actions/cache@v2 + - name: Check out repository + uses: actions/checkout@v3 with: - path: | - /root/.gradle/caches/ - /root/.gradle/wrapper/dists - key: test-${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle', '**/*.gradle.kts', '**/gradle.properties', 'gradle/**', 'buildSrc/src/main/**') }} - restore-keys: | - test-${{ runner.os }}-gradle- - - name: Cache local Maven repository - uses: actions/cache@v2 + fetch-depth: 1 + - name: Set up Test JDK + uses: ./.github/actions/setup-test-jdk + - name: 'Set up JDK ${{ matrix.jdk }}' + uses: oracle-actions/setup-java@v1 with: - path: /root/.m2/repository - key: test-${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - test-${{ runner.os }}-maven- - - name: Prepare Gradle Enterprise credentials - run: | - mkdir -p /root/.gradle/enterprise/ - echo "${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }}" > /root/.gradle/enterprise/keys.properties - - name: Test - run: | - ./gradlew --version - ./gradlew --scan --no-parallel --warning-mode=all -Dplatform.tooling.support.tests.enabled=true -PjavaHome=$ADDITIONAL_JDK build "-Dscan.tag.JDK_${{ matrix.jdk }}" + website: jdk.java.net + release: ${{ matrix.jdk }} + version: latest + - name: 'Prepare JDK${{ matrix.jdk }} env var' + shell: bash + run: echo "JDK${{ matrix.jdk }}=$JAVA_HOME" >> $GITHUB_ENV + - name: Build + uses: ./.github/actions/run-gradle + with: + arguments: | + -PjavaToolchainVersion=${{ matrix.jdk }} + -Dscan.tag.JDK_${{ matrix.jdk }} + build + - name: Upload Test Distribution trace files + uses: actions/upload-artifact@v3 + with: + name: "Test Distribution trace files (OpenJDK ${{ matrix.jdk }})" + path: '**/build/test-results/*/trace.json' diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 405a2b306592..de5d0346a9f6 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -1,10 +1,22 @@ name: "Validate Gradle Wrapper" -on: [push, pull_request] + +on: + push: + branches: + - main + - 'releases/**' + pull_request: + branches: + - '*' jobs: validation: name: "Validation" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: gradle/wrapper-validation-action@v1 + - name: Check out repository + uses: actions/checkout@v3 + with: + fetch-depth: 1 + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@v1 diff --git a/.github/workflows/issue-labels.yml b/.github/workflows/issue-labels.yml new file mode 100644 index 000000000000..763dfe889565 --- /dev/null +++ b/.github/workflows/issue-labels.yml @@ -0,0 +1,20 @@ +name: Label new issues +on: + issues: + types: + - opened +jobs: + label_issues: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - uses: actions/github-script@v6 + 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/main.yml b/.github/workflows/main.yml index 2a4b5da0d7e0..676ec1a2f87a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,159 +4,66 @@ on: push: branches: - main - - 'releases/*' + - 'releases/**' pull_request: branches: - '*' env: - ORG_GRADLE_PROJECT_junitBuildCacheUsername: ${{ secrets.BUILD_CACHE_USERNAME }} - ORG_GRADLE_PROJECT_junitBuildCachePassword: ${{ secrets.BUILD_CACHE_PASSWORD }} + ENTERPRISE_TESTDISTRIBUTION_ENABLED: true + BUILDCACHE_USERNAME: ${{ secrets.BUILD_CACHE_USERNAME }} + BUILDCACHE_PASSWORD: ${{ secrets.BUILD_CACHE_PASSWORD }} + GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} jobs: - linux: - name: 'Linux' + Linux: runs-on: ubuntu-latest - container: junitteam/build:latest steps: - - uses: actions/checkout@v2 - - name: Cache Gradle wrapper and dependencies - uses: actions/cache@v2 + - name: Check out repository + uses: actions/checkout@v3 with: - path: | - /root/.gradle/caches/ - /root/.gradle/wrapper/dists - key: test-${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle', '**/*.gradle.kts', '**/gradle.properties', 'gradle/**', 'buildSrc/src/main/**') }} - restore-keys: | - test-${{ runner.os }}-gradle- - - name: Cache local Maven repository - uses: actions/cache@v2 - with: - path: /root/.m2/repository - key: test-${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - test-${{ runner.os }}-maven- - - name: Prepare Gradle Enterprise credentials - run: | - mkdir -p /root/.gradle/enterprise/ - echo "${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }}" > /root/.gradle/enterprise/keys.properties - - name: 'Test' + fetch-depth: 1 + - name: Install Graphviz run: | - ./gradlew --version - ./gradlew --scan --no-parallel --warning-mode=all -Dplatform.tooling.support.tests.enabled=true build - - windows: - name: 'Windows' - runs-on: windows-latest - steps: - - uses: actions/checkout@v2 - - name: Cache Gradle wrapper and dependencies - uses: actions/cache@v2 - with: - path: | - ~/.gradle/caches/ - ~/.gradle/wrapper/dists - key: test-${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle', '**/*.gradle.kts', '**/gradle.properties', 'gradle/**', 'buildSrc/src/main/**') }} - restore-keys: | - test-${{ runner.os }}-gradle- - - name: Cache local Maven repository - uses: actions/cache@v2 + sudo apt-get update + sudo apt-get install graphviz + - name: Install GraalVM + uses: graalvm/setup-graalvm@v1 with: - path: ~/.m2/repository - key: test-${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - test-${{ runner.os }}-maven- - - name: 'Set up JDK 11' - uses: actions/setup-java@v1 + version: 'latest' + java-version: '17' + components: 'native-image' + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Build + uses: ./.github/actions/main-build with: - java-version: 11 - - name: Prepare Gradle Enterprise credentials - shell: bash - run: | - mkdir -p $HOME/.gradle/enterprise/ - echo "${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }}" > $HOME/.gradle/enterprise/keys.properties - - name: 'Test' - shell: bash - run: | - ./gradlew --version - ./gradlew --scan --no-parallel --warning-mode=all -Dplatform.tooling.support.tests.enabled=true build - ./gradlew --stop - mac: - name: 'Mac OS' - runs-on: macos-latest + arguments: | + -Ptesting.enableJaCoCo + build + jacocoRootReport + prepareDocsForUploadToGhPages + - name: Upload to Codecov.io + uses: codecov/codecov-action@v3 + + Windows: + runs-on: windows-latest steps: - - uses: actions/checkout@v2 - - name: Cache Gradle wrapper and dependencies - uses: actions/cache@v2 + - name: Check out repository + uses: actions/checkout@v3 with: - path: | - ~/.gradle/caches/ - ~/.gradle/wrapper/dists - key: test-${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle', '**/*.gradle.kts', '**/gradle.properties', 'gradle/**', 'buildSrc/src/main/**') }} - restore-keys: | - test-${{ runner.os }}-gradle- - - name: Cache local Maven repository - uses: actions/cache@v2 - with: - path: ~/.m2/repository - key: test-${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - test-${{ runner.os }}-maven- - - name: 'Set up JDK 11' - uses: actions/setup-java@v1 - with: - java-version: 11 - - name: Prepare Gradle Enterprise credentials - run: | - mkdir -p $HOME/.gradle/enterprise/ - echo "${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }}" > $HOME/.gradle/enterprise/keys.properties - - name: 'Test' - run: | - ./gradlew --version - ./gradlew --scan --no-parallel --warning-mode=all -Dplatform.tooling.support.tests.enabled=true build + fetch-depth: 1 + - name: Build + uses: ./.github/actions/main-build - coverage: - name: 'Coverage' - needs: linux - runs-on: ubuntu-latest - container: junitteam/build:latest + macOS: + runs-on: macos-latest steps: - - uses: actions/checkout@v2 - - name: Cache Gradle wrapper and dependencies - uses: actions/cache@v2 - with: - path: | - /root/.gradle/caches/ - /root/.gradle/wrapper/dists - key: coverage-${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle', '**/*.gradle.kts', '**/gradle.properties', 'gradle/**', 'buildSrc/src/main/**') }} - restore-keys: | - coverage-${{ runner.os }}-gradle- - - name: Cache local Maven repository - uses: actions/cache@v2 - with: - path: /root/.m2/repository - key: coverage-${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - coverage-${{ runner.os }}-maven- - - name: 'Set up JDK 11' - uses: actions/setup-java@v1 + - name: Check out repository + uses: actions/checkout@v3 with: - java-version: 11 - - name: Prepare Gradle Enterprise credentials - run: | - mkdir -p /root/.gradle/enterprise/ - echo "${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }}" > /root/.gradle/enterprise/keys.properties - - name: 'Run tests with JaCoCo' - shell: bash - run: | - ./gradlew --version - ./gradlew --scan --no-parallel --stacktrace --warning-mode=all -PenableJaCoCo build jacocoRootReport - - name: Upload to Codecov.io - shell: bash - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - run: | - bash <(curl -s https://codecov.io/bash) + fetch-depth: 1 + - name: Build + uses: ./.github/actions/main-build publish_artifacts: name: Publish Snapshot Artifacts @@ -164,51 +71,40 @@ jobs: runs-on: ubuntu-latest if: github.event_name == 'push' && github.repository == 'junit-team/junit5' && (startsWith(github.ref, 'refs/heads/releases/') || github.ref == 'refs/heads/main') steps: - - uses: actions/checkout@v2 - - name: Cache Gradle wrapper and dependencies - uses: actions/cache@v2 - with: - path: | - ~/.gradle/caches/ - ~/.gradle/wrapper/dists - key: assemble-${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle', '**/*.gradle.kts', '**/gradle.properties', 'gradle/**', 'buildSrc/src/main/**') }} - restore-keys: | - assemble-${{ runner.os }}-gradle- - - name: 'Set up JDK 11' - uses: actions/setup-java@v1 + - name: Check out repository + uses: actions/checkout@v3 with: - java-version: 11 - - name: 'Publish' + fetch-depth: 1 + - name: Publish + uses: ./.github/actions/run-gradle env: ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.SONATYPE_USERNAME }} ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.SONATYPE_PASSWORD }} - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} - run: ./gradlew --scan publish -x check + with: + arguments: publish -x check update_documentation: name: Update Snapshot Documentation - needs: linux + concurrency: + group: github-pages + cancel-in-progress: true + needs: Linux runs-on: ubuntu-latest if: github.event_name == 'push' && github.repository == 'junit-team/junit5' && github.ref == 'refs/heads/main' steps: - - uses: actions/checkout@v2 - - name: Cache Gradle wrapper and dependencies - uses: actions/cache@v2 + - name: Check out repository + uses: actions/checkout@v3 with: - path: | - ~/.gradle/caches/ - ~/.gradle/wrapper/dists - key: assemble-${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle', '**/*.gradle.kts', '**/gradle.properties', 'gradle/**', 'buildSrc/src/main/**') }} - restore-keys: | - assemble-${{ runner.os }}-gradle- - - name: 'Set up JDK 11' - uses: actions/setup-java@v1 + fetch-depth: 1 + - name: Install Graphviz + run: | + sudo apt-get update + sudo apt-get install graphviz + - name: Restore Gradle cache and display toolchains + uses: ./.github/actions/run-gradle with: - java-version: 11 - - name: 'Upload Documentation' + arguments: --quiet + - name: Upload Documentation env: GRGIT_USER: ${{ secrets.GH_TOKEN }} - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} - run: | - sudo apt-get install graphviz - ./src/publishDocumentationSnapshotOnlyIfNecessary.sh + run: ./gradle/scripts/publishDocumentationSnapshotOnlyIfNecessary.sh diff --git a/.github/workflows/reproducible-build.yml b/.github/workflows/reproducible-build.yml index e48b0c5ff1b2..a32a51ac0002 100644 --- a/.github/workflows/reproducible-build.yml +++ b/.github/workflows/reproducible-build.yml @@ -3,37 +3,29 @@ name: Reproducible build on: push: branches: - - main - - 'releases/*' + - main + - 'releases/**' pull_request: branches: - - '*' + - '*' + +env: + GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} jobs: check_build_reproducibility: name: 'Check build reproducibility' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Cache Gradle wrapper and dependencies - uses: actions/cache@v2 + - name: Check out repository + uses: actions/checkout@v3 with: - path: | - ~/.gradle/caches/ - ~/.gradle/wrapper/dists - key: assemble-${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle', '**/*.gradle.kts', '**/gradle.properties', 'gradle/**', 'buildSrc/src/main/**') }} - restore-keys: | - assemble-${{ runner.os }}-gradle- - - name: 'Set up JDK 11' - uses: actions/setup-java@v1 + fetch-depth: 1 + - name: Restore Gradle cache and display toolchains + uses: ./.github/actions/run-gradle with: - java-version: 11 - - name: Prepare Gradle Enterprise credentials - shell: bash - run: | - mkdir -p $HOME/.gradle/enterprise/ - echo "${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }}" > $HOME/.gradle/enterprise/keys.properties + arguments: --quiet - name: Build and compare checksums shell: bash run: | - ./src/checkBuildReproducibility.sh + ./gradle/scripts/checkBuildReproducibility.sh diff --git a/.gitignore b/.gitignore index edd18cb0c5e2..6d276115b4be 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ # Gradle .gradle -/build/ -/*/build/ +build # Ignore Gradle GUI config gradle-app.setting @@ -29,5 +28,6 @@ gradle-app.setting *.graphml coverage.db* .metadata +/.sdkmanrc checksums* diff --git a/.idea/vcs.xml b/.idea/vcs.xml index f77f7d2313e4..178ac9238035 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,5 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.jitpack.yml b/.jitpack.yml deleted file mode 100644 index 72eb09e6aa7f..000000000000 --- a/.jitpack.yml +++ /dev/null @@ -1,5 +0,0 @@ -install: - - wget https://github.com/sormuras/bach/raw/HEAD/install-jdk.sh - - source ./install-jdk.sh --feature 11 - - ./gradlew --version - - ./gradlew publishToMavenLocal -x test diff --git a/.lgtm.yml b/.lgtm.yml deleted file mode 100644 index eb5f4c9b7a25..000000000000 --- a/.lgtm.yml +++ /dev/null @@ -1,4 +0,0 @@ -extraction: - java: - index: - java_version: 11 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 18e6158b17ac..2313cecda3ff 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,10 +28,10 @@ Issue: #999 ## Pull Requests Our [Definition of Done](https://github.com/junit-team/junit5/wiki/Definition-of-Done) -offers some guidelines on what we expect from a pull request. +(DoD) offers some guidelines on what we expect from a pull request. Feel free to open a pull request that does not fulfill all criteria, e.g. to discuss a certain change before polishing it, but please be aware that we will only merge it -in case the DoD is met. +once the DoD is met. Please add the following lines to your pull request description: @@ -71,8 +71,8 @@ Code formatting is enforced using the [Spotless](https://github.com/diffplug/spo Gradle plugin. You can use `gradle spotlessApply` to format new code and add missing license headers to source files. Formatter and import order settings for Eclipse are available in the repository under -[src/eclipse/junit-eclipse-formatter-settings.xml](src/eclipse/junit-eclipse-formatter-settings.xml) -and [src/eclipse/junit-eclipse.importorder](src/eclipse/junit-eclipse.importorder), +[junit-eclipse-formatter-settings.xml](gradle/config/eclipse/junit-eclipse-formatter-settings.xml) +and [junit-eclipse.importorder](gradle/config/eclipse/junit-eclipse.importorder), respectively. For IntelliJ IDEA there's a [plugin](https://plugins.jetbrains.com/plugin/6546) you can use in conjunction with the Eclipse settings. @@ -87,19 +87,23 @@ possible. In multi-line bullet point entries, subsequent lines should be indented. +### Spelling + +Use American English spelling rules when writing documentation as well as for +code -- class names, method names, variable names, etc. + ### Javadoc - Javadoc comments should be wrapped after 80 characters whenever possible. -- This first paragraph must be a single, concise sentence that ends with a period ("."). -- Place `

` on the same line as the first line in a new paragraph and precede `

` with a blank line. +- This first paragraph must be a single, concise sentence that ends with a period (`.`). +- Place `

` on the same line as the first line of a new paragraph and precede `

` with a blank line. - Insert a blank line before at-clauses/tags. - Favor `{@code foo}` over `foo`. - Favor literals (e.g., `{@literal @}`) over HTML entities. -- New classes and methods should have `@since ...` annotation. -- Use `@since 5.0` instead of `@since 5.0.0`. -- Do not use `@author` tags. Instead, contributors are listed on [GitHub](https://github.com/junit-team/junit5/graphs/contributors). -- Do not use verbs in third person form (e.g. use "Discover tests..." instead of "Discovers tests...") - in the first sentence describing a method. +- New classes and methods should declare a `@since ...` tag. +- Use `@since 5.10` instead of `@since 5.10.0`. +- Do not use `@author` tags. Instead, contributors are listed on the [GitHub](https://github.com/junit-team/junit5/graphs/contributors) page. +- Do not use verbs in third-person form in the first sentence of the Javadoc for a method -- for example, use "Discover tests..." instead of "Discovers tests...". #### Examples @@ -116,19 +120,19 @@ See [`ExtensionContext`](junit-jupiter-api/src/main/java/org/junit/jupiter/api/e #### Assertions -- Use `org.junit.jupiter.api.Assertions` wherever possible. +- Use `org.junit.jupiter.api.Assertions` for simple assertions. - Use AssertJ when richer assertions are needed. - Do not use `org.junit.Assert` or `junit.framework.Assert`. -#### Mocking +#### Mocking and Stubbing - Use either [Mockito](https://github.com/mockito/mockito) or hand-written test doubles. ### Logging - In general, logging should be used sparingly. -- All logging must be performed via the internal `Logger` façade provided via the JUnit [LoggerFactory](https://junit.org/junit5/docs/current/api/org/junit/platform/commons/logging/LoggerFactory.html). -- Levels defined in JUnit's [Logger](https://junit.org/junit5/docs/current/api/org/junit/platform/commons/logging/Logger.html) façade. +- All logging must be performed via the internal `Logger` façade provided via the JUnit [LoggerFactory](https://github.com/junit-team/junit5/blob/main/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LoggerFactory.java). +- Levels defined in JUnit's [Logger](https://github.com/junit-team/junit5/blob/main/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/Logger.java) façade, which delegates to Java Util Logging (JUL) for the actual logging. - _error_ (JUL: `SEVERE`, Log4J: `ERROR`): extra information (in addition to an Exception) about errors that will halt execution - _warn_ (JUL: `WARNING`, Log4J: `WARN`): potential usage or configuration errors that should not halt execution - _info_ (JUL: `INFO`, Log4J: `INFO`): information the users might want to know but not by default @@ -138,10 +142,11 @@ See [`ExtensionContext`](junit-jupiter-api/src/main/java/org/junit/jupiter/api/e ### Deprecation -Publicly available interfaces, classes and methods have a defined lifecycle +The JUnit 5 project uses the `@API` annotation from [API Guardian](https://github.com/apiguardian-team/apiguardian). +Publicly available interfaces, classes, and methods have a defined lifecycle which is described in detail in the [User Guide](https://junit.org/junit5/docs/current/user-guide/#api-evolution). -This process is using the `@API` annotation from [API Guardian](https://github.com/apiguardian-team/apiguardian). -It also describes the deprecation process followed for API items. + +That following describes the deprecation process followed for API items. To deprecate an item: - Update the `@API.status` to `DEPRECATED`. diff --git a/README.md b/README.md index 8b6a8761c39d..43f900ade772 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # JUnit 5 -This repository is the home of the next generation of JUnit, _JUnit 5_. +This repository is the home of _JUnit 5_. [![Support JUnit](https://img.shields.io/badge/%F0%9F%92%9A-Support%20JUnit-brightgreen.svg)](https://junit.org/sponsoring) ## Latest Releases -- General Availability (GA): [JUnit 5.7.0](https://github.com/junit-team/junit5/releases/tag/r5.7.0) (September 13, 2020) -- Preview (Milestone/Release Candidate): n/a +- General Availability (GA): [JUnit 5.10.0](https://github.com/junit-team/junit5/releases/tag/r5.10.0) (July 23, 2023) +- Preview (Milestone/Release Candidate): N/A ## Documentation @@ -28,7 +28,7 @@ label are specifically targeted for community contributions. ## Getting Help -Ask JUnit 5 related questions on [StackOverflow] or chat with the team and the community on [Gitter]. +Ask JUnit 5 related questions on [StackOverflow] or chat with the community on [Gitter]. ## Continuous Integration Builds @@ -43,95 +43,62 @@ 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 -PenableJaCoCo clean jacocoRootReport`. The results will be available +executing `./gradlew -Ptesting.enableJaCoCo clean jacocoRootReport`. The results will be available in `build/reports/jacoco/jacocoRootReport/html/index.html`. -## Gradle Build Scans and Build Caching +## Gradle Enterprise -JUnit 5 utilizes [Gradle Enterprise](https://gradle.com/) for _Build Scans_ and the -_Remote Build Cache_. An example build scan for JUnit 5 can be viewed -[here](https://ge.junit.org/s/2vwrn4rn67dky). Currently, only core team members can -publish build scans. The remote build cache, however, is enabled by default for everyone -so that local builds can reuse task outputs from previous CI builds. +[![Revved up by Gradle Enterprise](https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.junit.org/scans) -## Building from Source +JUnit 5 utilizes [Gradle Enterprise](https://gradle.com/) for _Build Scans_, _Build Cache_, and _Test Distribution_. -You need [JDK 11] to build JUnit 5. +The latest Build Scans are available on [ge.junit.org](https://ge.junit.org/). Currently, +only core team members can publish Build Scans and use Test Distribution on that server. +You can, however, publish a Build Scan to [scans.gradle.com](https://scans.gradle.com/) by +using the `--scan` parameter explicitly. -All modules can be _built_ with the [Gradle Wrapper] using the following command. +The remote Build Cache is enabled by default for everyone so that local builds can reuse +task outputs from previous CI builds. -`gradlew clean assemble` +## Building from Source -All modules can be _tested_ with the [Gradle Wrapper] using the following command. +You need [JDK 17] to build JUnit 5. [Gradle toolchains] are used to detect and +potentially download additional JDKs for compilation and test execution. -`gradlew clean test` +All modules can be _built_ and _tested_ with the [Gradle Wrapper] using the following command. -Since Gradle has excellent incremental build support, you can usually omit executing the -`clean` task. +`./gradlew build` ## Installing in Local Maven Repository All modules can be _installed_ with the [Gradle Wrapper] in a local Maven repository for consumption in other projects via the following command. -`gradlew clean publishToMavenLocal` +`./gradlew publishToMavenLocal` ## Dependency Metadata -The following sections list the dependency metadata for the JUnit Platform, JUnit -Jupiter, and JUnit Vintage. - -See also for releases and for snapshots. - -### JUnit Platform - -- **Group ID**: `org.junit.platform` -- **Version**: `1.7.0` or `1.8.0-SNAPSHOT` -- **Artifact IDs** and Java **module** name: - - `junit-platform-commons` (`org.junit.platform.commons`) - - `junit-platform-console` (`org.junit.platform.console`) - - `junit-platform-console-standalone` (*N/A*) - - `junit-platform-engine` (`org.junit.platform.engine`) - - `junit-platform-jfr` (`org.junit.platform.jfr`) - - `junit-platform-launcher` (`org.junit.platform.launcher`) - - `junit-platform-reporting` (`org.junit.platform.reporting`) - - `junit-platform-runner` (`org.junit.platform.runner`) - - `junit-platform-suite-api` (`org.junit.platform.suite.api`) - - `junit-platform-testkit` (`org.junit.platform.testkit`) - -### JUnit Jupiter +[![JUnit Jupiter version](https://img.shields.io/maven-central/v/org.junit.jupiter/junit-jupiter/5..svg?color=25a162&label=Jupiter)](https://central.sonatype.com/search?namespace=org.junit.jupiter) +[![JUnit Vintage version](https://img.shields.io/maven-central/v/org.junit.vintage/junit-vintage-engine/5..svg?color=25a162&label=Vintage)](https://central.sonatype.com/search?namespace=org.junit.vintage) +[![JUnit Platform version](https://img.shields.io/maven-central/v/org.junit.platform/junit-platform-commons/1..svg?color=25a162&label=Platform)](https://central.sonatype.com/search?namespace=org.junit.platform) -- **Group ID**: `org.junit.jupiter` -- **Version**: `5.7.0` or `5.8.0-SNAPSHOT` -- **Artifact IDs** and Java **module** name: - - `junit-jupiter` (`org.junit.jupiter`) - - `junit-jupiter-api` (`org.junit.jupiter.api`) - - `junit-jupiter-engine` (`org.junit.jupiter.engine`) - - `junit-jupiter-migrationsupport` (`org.junit.jupiter.migrationsupport`) - - `junit-jupiter-params` (`org.junit.jupiter.params`) +Consult the [Dependency Metadata] section of the [User Guide] for a list of all artifacts +of the JUnit Platform, JUnit Jupiter, and JUnit Vintage. -### JUnit Vintage - -- **Group ID**: `org.junit.vintage` -- **Version**: `5.7.0` or `5.8.0-SNAPSHOT` -- **Artifact ID** and Java **module** name: - - `junit-vintage-engine` (`org.junit.vintage.engine`) - -### Bill of Materials (BOM) - -- **Group ID**: `org.junit` -- **Artifact ID** `junit-bom` -- **Version**: `5.7.0` or `5.8.0-SNAPSHOT` +See also for releases and + for snapshots. [Codecov]: https://codecov.io/gh/junit-team/junit5 [CONTRIBUTING.md]: https://github.com/junit-team/junit5/blob/HEAD/CONTRIBUTING.md +[Dependency Metadata]: https://junit.org/junit5/docs/current/user-guide/#dependency-metadata [Gitter]: https://gitter.im/junit-team/junit5 +[Gradle toolchains]: https://docs.gradle.org/current/userguide/toolchains.html [Gradle Wrapper]: https://docs.gradle.org/current/userguide/gradle_wrapper.html#sec:using_wrapper [JaCoCo]: https://www.eclemma.org/jacoco/ [Javadoc]: https://junit.org/junit5/docs/current/api/ -[JDK 11]: https://jdk.java.net/11/ +[JDK 17]: https://foojay.io/almanac/java-17/ [Release Notes]: https://junit.org/junit5/docs/current/release-notes/ +[Samples]: https://github.com/junit-team/junit5-samples [StackOverflow]: https://stackoverflow.com/questions/tagged/junit5 [User Guide]: https://junit.org/junit5/docs/current/user-guide/ -[Samples]: https://github.com/junit-team/junit5-samples diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000000..fca52da512fa --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,12 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 5.9.x | :white_check_mark: | +| < 5.9 | :x: | + +## Reporting a Vulnerability + +To report a security vulnerability, please send an email to security@junit.org. diff --git a/build.gradle.kts b/build.gradle.kts index 41cba6dc5c6b..048d466a4546 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,262 +1,69 @@ -import java.time.OffsetDateTime -import java.time.Instant -import java.time.ZoneOffset -import java.time.format.DateTimeFormatter - plugins { - id("net.nemerosa.versioning") - id("com.github.ben-manes.versions") // gradle dependencyUpdates - id("com.diffplug.spotless") - id("io.spring.nohttp") -} - -apply(from = "gradle/build-scan-user-data.gradle") - -buildScan { - if (project.hasProperty("javaHome")) { - value("Custom Java home", project.property("javaHome") as String) - } + alias(libs.plugins.nohttp) + alias(libs.plugins.nexusPublish) + id("junitbuild.base-conventions") + id("junitbuild.build-metadata") + id("junitbuild.dependency-update-check") + id("junitbuild.jacoco-aggregation-conventions") + id("junitbuild.temp-maven-repo") } -val buildTimeAndDate by extra { - - // SOURCE_DATE_EPOCH is a UNIX timestamp for pinning build metadata against - // in order to achieve reproducible builds - // - // More details - https://reproducible-builds.org/docs/source-date-epoch/ - - if (System.getenv().containsKey("SOURCE_DATE_EPOCH")) { - - val sourceDateEpoch = System.getenv("SOURCE_DATE_EPOCH").toLong() - - Instant.ofEpochSecond(sourceDateEpoch).atOffset(ZoneOffset.UTC) - - } else { - OffsetDateTime.now() - } -} +description = "JUnit 5" -val buildDate by extra { DateTimeFormatter.ISO_LOCAL_DATE.format(buildTimeAndDate) } -val buildTime by extra { DateTimeFormatter.ofPattern("HH:mm:ss.SSSZ").format(buildTimeAndDate) } -val buildRevision by extra { versioning.info.commit } -val builtByValue by extra { project.findProperty("builtBy") ?: project.property("defaultBuiltBy") } +val license by extra(License( + name = "Eclipse Public License v2.0", + url = uri("https://www.eclipse.org/legal/epl-v20.html"), + headerFile = layout.projectDirectory.file("gradle/config/spotless/eclipse-public-license-2.0.java") +)) val platformProjects by extra(listOf( - project(":junit-platform-commons"), - project(":junit-platform-console"), - project(":junit-platform-console-standalone"), - project(":junit-platform-engine"), - project(":junit-platform-jfr"), - project(":junit-platform-launcher"), - project(":junit-platform-reporting"), - project(":junit-platform-runner"), - project(":junit-platform-suite-api"), - project(":junit-platform-testkit") -)) + projects.junitPlatformCommons, + projects.junitPlatformConsole, + projects.junitPlatformConsoleStandalone, + projects.junitPlatformEngine, + projects.junitPlatformJfr, + projects.junitPlatformLauncher, + projects.junitPlatformReporting, + projects.junitPlatformRunner, + projects.junitPlatformSuite, + projects.junitPlatformSuiteApi, + projects.junitPlatformSuiteCommons, + projects.junitPlatformSuiteEngine, + projects.junitPlatformTestkit +).map { it.dependencyProject }) val jupiterProjects by extra(listOf( - project(":junit-jupiter"), - project(":junit-jupiter-api"), - project(":junit-jupiter-engine"), - project(":junit-jupiter-migrationsupport"), - project(":junit-jupiter-params") -)) + projects.junitJupiter, + projects.junitJupiterApi, + projects.junitJupiterEngine, + projects.junitJupiterMigrationsupport, + projects.junitJupiterParams +).map { it.dependencyProject }) val vintageProjects by extra(listOf( - project(":junit-vintage-engine") + projects.junitVintageEngine.dependencyProject )) val mavenizedProjects by extra(platformProjects + jupiterProjects + vintageProjects) -val modularProjects by extra(mavenizedProjects - listOf(project(":junit-platform-console-standalone"))) - -val license by extra(License( - name = "Eclipse Public License v2.0", - url = uri("https://www.eclipse.org/legal/epl-v20.html"), - headerFile = file("src/spotless/eclipse-public-license-2.0.java") -)) - -val tempRepoName by extra("temp") -val tempRepoDir by extra(file("$buildDir/repo")) - -val enableJaCoCo = project.hasProperty("enableJaCoCo") -val jacocoTestProjects = listOf( - project(":junit-jupiter-engine"), - project(":junit-jupiter-migrationsupport"), - project(":junit-jupiter-params"), - project(":junit-platform-runner"), - project(":junit-vintage-engine"), - project(":platform-tests") -) -val jacocoCoveredProjects = modularProjects -val jacocoClassesDir = file("$buildDir/jacoco/classes") - -allprojects { - - apply(plugin = "eclipse") - apply(plugin = "idea") - apply(plugin = "com.diffplug.spotless") +val modularProjects by extra(mavenizedProjects - listOf(projects.junitPlatformConsoleStandalone.dependencyProject)) - if (enableJaCoCo) { - apply(plugin = "jacoco") - configure { - toolVersion = versions["jacoco"] - } +dependencies { + (modularProjects + listOf(projects.platformTests.dependencyProject)).forEach { + jacocoAggregation(project(it.path)) } +} +nexusPublishing { + packageGroup = "org.junit" repositories { - // mavenLocal() - mavenCentral() - maven(url = "https://oss.sonatype.org/content/repositories/snapshots") { - mavenContent { - snapshotsOnly() - } - } + sonatype() } } -subprojects { - - if (project in jupiterProjects) { - group = property("jupiterGroup")!! - } - else if (project in platformProjects) { - group = property("platformGroup")!! - version = property("platformVersion")!! - } - else if (project in vintageProjects) { - group = property("vintageGroup")!! - version = property("vintageVersion")!! - } - - tasks.withType().configureEach { - isPreserveFileTimestamps = false - isReproducibleFileOrder = true - dirMode = Integer.parseInt("0755", 8) - fileMode = Integer.parseInt("0644", 8) - } - - pluginManager.withPlugin("java") { - - spotless { - val headerFile = license.headerFile - val importOrderConfigFile = rootProject.file("src/eclipse/junit-eclipse.importorder") - val javaFormatterConfigFile = rootProject.file("src/eclipse/junit-eclipse-formatter-settings.xml") - - java { - licenseHeaderFile(headerFile, "(package|import|open|module) ") - importOrderFile(importOrderConfigFile) - eclipse().configFile(javaFormatterConfigFile) - removeUnusedImports() - trimTrailingWhitespace() - endWithNewline() - } - - kotlin { - ktlint(versions["ktlint"]) - licenseHeaderFile(headerFile) - trimTrailingWhitespace() - endWithNewline() - } - } - - afterEvaluate { - if (enableJaCoCo && project in jacocoCoveredProjects) { - val jarTask = (tasks.findByName("shadowJar") ?: tasks["jar"]) as Jar - val extractJar by tasks.registering(Copy::class) { - from(zipTree(jarTask.archivePath)) - into(jacocoClassesDir) - include("**/*.class") - // don't report coverage for shadowed classes - exclude("**/shadow/**") - // don't version-specific classes of MR JARs - exclude("META-INF/versions/**") - includeEmptyDirs = false - onlyIf { jarTask.enabled } - } - jarTask.finalizedBy(extractJar) - } - } - } - - pluginManager.withPlugin("maven-publish") { - configure { - repositories { - repositories { - maven { - name = tempRepoName - url = uri(tempRepoDir) - } - } - } - } - } +nohttp { + source.exclude("**/.gradle/**", "gradle/plugins/**/build/**", "buildSrc/build/**") } -rootProject.apply { - description = "JUnit 5" - - spotless { - format("misc") { - target("**/*.gradle", "**/*.gradle.kts", "**/*.gitignore") - targetExclude("**/build/**") - indentWithTabs() - trimTrailingWhitespace() - endWithNewline() - } - format("documentation") { - target("**/*.adoc", "**/*.md") - trimTrailingWhitespace() - endWithNewline() - } - } - - nohttp { - // Must cast, since `source` is only exposed as a FileTree - (source as ConfigurableFileTree).exclude("buildSrc/build/generated-sources/**") - } - - tasks { - dependencyUpdates { - checkConstraints = true - resolutionStrategy { - componentSelection { - all { - val rejected = listOf("alpha", "beta", "rc", "cr", "m", "preview", "b", "ea") - .map { qualifier -> Regex("(?i).*[.-]$qualifier[.\\d-+]*") } - .any { it.matches(candidate.version) } - if (rejected) { - reject("Release candidate") - } - } - } - } - } - } - - if (enableJaCoCo) { - tasks { - val jacocoMerge by registering(JacocoMerge::class) { - subprojects.filter { it in jacocoTestProjects } - .forEach { subproj -> - executionData(fileTree("dir" to "${subproj.buildDir}/jacoco", "include" to "*.exec")) - dependsOn(subproj.tasks.withType()) - } - } - register("jacocoRootReport") { - dependsOn(jacocoMerge) - jacocoCoveredProjects.forEach { - it.pluginManager.withPlugin("java") { - sourceDirectories.from(it.the()["main"].allSource.srcDirs) - } - } - classDirectories.from(files(jacocoClassesDir)) - executionData(jacocoMerge.get().destinationFile) - reports { - html.isEnabled = true - xml.isEnabled = true - csv.isEnabled = false - } - } - } - } +tasks.checkstyleNohttp { + notCompatibleWithConfigurationCache("https://github.com/spring-io/nohttp/issues/61") } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts deleted file mode 100644 index fd74ea278a13..000000000000 --- a/buildSrc/build.gradle.kts +++ /dev/null @@ -1,16 +0,0 @@ -plugins { - `kotlin-dsl` -} - -repositories { - mavenCentral() - gradlePluginPortal() -} - -dependencies { - implementation(kotlin("gradle-plugin")) - implementation("de.marcphilipp.gradle:nexus-publish-plugin:0.4.0") - implementation("biz.aQute.bnd:biz.aQute.bnd.gradle:5.1.2") - implementation("com.github.jengelman.gradle.plugins:shadow:6.0.0") - implementation("org.gradle:test-retry-gradle-plugin:1.1.8") -} diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts deleted file mode 100644 index 44425d9c15fc..000000000000 --- a/buildSrc/settings.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -// intentionally left blank diff --git a/buildSrc/src/main/kotlin/JavaLibraryExtension.kt b/buildSrc/src/main/kotlin/JavaLibraryExtension.kt deleted file mode 100644 index 7c020478d6c3..000000000000 --- a/buildSrc/src/main/kotlin/JavaLibraryExtension.kt +++ /dev/null @@ -1,7 +0,0 @@ -import org.gradle.api.JavaVersion - -@Suppress("UnstableApiUsage") -open class JavaLibraryExtension { - var mainJavaVersion: JavaVersion = Versions.jvmTarget - var testJavaVersion: JavaVersion = JavaVersion.VERSION_11 -} diff --git a/buildSrc/src/main/kotlin/ProjectExtensions.kt b/buildSrc/src/main/kotlin/ProjectExtensions.kt deleted file mode 100644 index d6d2f4215049..000000000000 --- a/buildSrc/src/main/kotlin/ProjectExtensions.kt +++ /dev/null @@ -1,12 +0,0 @@ -import org.gradle.api.Project -import org.gradle.kotlin.dsl.extra -import org.gradle.kotlin.dsl.provideDelegate - -val Project.javaModuleName: String - get() = "org." + this.name.replace('-', '.') - -val Project.versions: Versions - get() { - var versions: Versions? by rootProject.extra - return versions ?: Versions(rootProject).also { versions = it } - } diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt deleted file mode 100644 index c74c5265d80f..000000000000 --- a/buildSrc/src/main/kotlin/Versions.kt +++ /dev/null @@ -1,24 +0,0 @@ -import org.gradle.api.JavaVersion -import org.gradle.api.Project -import org.gradle.kotlin.dsl.extra -import kotlin.reflect.KProperty - -class Versions(private val project: Project) { - - companion object { - val jvmTarget = JavaVersion.VERSION_1_8 - } - - private val properties = object { - operator fun getValue(receiver: Any?, property: KProperty<*>) = get(property.name) - } - - val junit4 by properties - val junit4Min by properties - val opentest4j by properties - val apiguardian by properties - val assertj by properties - - operator fun get(name: String) = project.extra.get("$name.version") as String - -} diff --git a/buildSrc/src/main/kotlin/custom-java-home.gradle.kts b/buildSrc/src/main/kotlin/custom-java-home.gradle.kts deleted file mode 100644 index 8533c5f949fd..000000000000 --- a/buildSrc/src/main/kotlin/custom-java-home.gradle.kts +++ /dev/null @@ -1,48 +0,0 @@ -import org.gradle.internal.os.OperatingSystem -import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile - -if (project.hasProperty("javaHome")) { - - val javaHome: String by project - require(file(javaHome).isDirectory) { - "Java home directory set via `javaHome` project property is invalid: $javaHome" - } - - fun javaHomeExecutable(execName: String): String { - val extension = if (OperatingSystem.current().isWindows) ".exe" else "" - val executable = File(File(javaHome, "bin"), "$execName$extension") - require(executable.exists()) { - "File does not exist: $executable" - } - return executable.canonicalPath - } - - tasks { - withType().configureEach { - options.isFork = true - options.forkOptions.javaHome = file(javaHome) - doFirst { - // Avoid compiler warnings for non-existing path entries - classpath = classpath.filter { it.exists() } - } - } - withType().configureEach { - options.isFork = true - options.forkOptions.javaHome = file(javaHome) - } - withType().configureEach { - kotlinOptions { - jdkHome = javaHome - } - } - withType().configureEach { - executable = javaHomeExecutable("javadoc") - } - withType().configureEach { - executable = javaHomeExecutable("java") - } - withType().configureEach { - setExecutable(javaHomeExecutable("java")) - } - } -} diff --git a/buildSrc/src/main/kotlin/java-multi-release-sources.gradle.kts b/buildSrc/src/main/kotlin/java-multi-release-sources.gradle.kts deleted file mode 100644 index 1843acb7c395..000000000000 --- a/buildSrc/src/main/kotlin/java-multi-release-sources.gradle.kts +++ /dev/null @@ -1,41 +0,0 @@ -plugins { - id("java-library-conventions") -} - -val mavenizedProjects: List by rootProject.extra - -val mainRelease9 by sourceSets.registering { - compileClasspath += sourceSets.main.get().output - runtimeClasspath += sourceSets.main.get().output - java { - setSrcDirs(setOf("src/main/java9")) - } -} - -configurations.named(mainRelease9.get().compileClasspathConfigurationName).configure { - extendsFrom(configurations.compileClasspath.get()) -} - -tasks { - - named("allMainClasses").configure { - dependsOn(mainRelease9.get().classesTaskName) - } - - named(mainRelease9.get().compileJavaTaskName).configure { - options.release.set(9) - } - - named("checkstyle${mainRelease9.name.capitalize()}").configure { - configFile = rootProject.file("src/checkstyle/checkstyleMain.xml") - } - - if (project in mavenizedProjects) { - javadoc { - source(mainRelease9.get().allJava) - } - named("sourcesJar").configure { - from(mainRelease9.get().allSource) - } - } -} diff --git a/buildSrc/src/main/kotlin/junit4-compatibility.gradle.kts b/buildSrc/src/main/kotlin/junit4-compatibility.gradle.kts deleted file mode 100644 index deb9fdced557..000000000000 --- a/buildSrc/src/main/kotlin/junit4-compatibility.gradle.kts +++ /dev/null @@ -1,28 +0,0 @@ -plugins { - `java-library` -} - -val junit_4_12 by configurations.creating { - extendsFrom(configurations.testRuntimeClasspath.get()) -} - -dependencies { - junit_4_12("junit:junit") { - version { - strictly("4.12") - } - } - pluginManager.withPlugin("osgi-conventions") { - "osgiVerification"("org.apache.servicemix.bundles:org.apache.servicemix.bundles.junit:4.13_1") - } -} - -tasks { - val test_4_12 by registering(Test::class) { - classpath -= configurations.testRuntimeClasspath.get() - classpath += junit_4_12 - } - check { - dependsOn(test_4_12) - } -} diff --git a/buildSrc/src/main/kotlin/kotlin-library-conventions.gradle.kts b/buildSrc/src/main/kotlin/kotlin-library-conventions.gradle.kts deleted file mode 100644 index 3342cd189501..000000000000 --- a/buildSrc/src/main/kotlin/kotlin-library-conventions.gradle.kts +++ /dev/null @@ -1,14 +0,0 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -plugins { - id("java-library-conventions") - kotlin("jvm") -} - -tasks.withType().configureEach { - kotlinOptions { - jvmTarget = Versions.jvmTarget.toString() - apiVersion = "1.3" - languageVersion = "1.3" - } -} diff --git a/buildSrc/src/main/kotlin/osgi-conventions.gradle.kts b/buildSrc/src/main/kotlin/osgi-conventions.gradle.kts deleted file mode 100644 index 8b8f1705dc7f..000000000000 --- a/buildSrc/src/main/kotlin/osgi-conventions.gradle.kts +++ /dev/null @@ -1,112 +0,0 @@ -import aQute.bnd.gradle.BundleTaskConvention -import aQute.bnd.gradle.FileSetRepositoryConvention -import aQute.bnd.gradle.Resolve - -plugins { - `java-library` -} - -// This task enhances `jar` and `shadowJar` tasks with the bnd -// `BundleTaskConvention` convention which allows for generating OSGi -// metadata into the jar -tasks.withType().matching { - task: Jar -> task.name == "jar" || task.name == "shadowJar" -}.configureEach { - val btc = BundleTaskConvention(this) - - // These are bnd instructions necessary for generating OSGi metadata. - // We've generalized these so that they are widely applicable limiting - // module configurations to special cases. - btc.setBnd(""" - # These are the general rules for package imports. - Import-Package: \ - !org.apiguardian.api,\ - org.junit.platform.commons.logging;status=INTERNAL,\ - kotlin.*;resolution:="optional",\ - * - - # This tells bnd not to complain if a module doesn't actually import - # the kotlin packages, but enough modules do to make it a default. - -fixupmessages.kotlin.import: "Unused Import-Package instructions: \\[kotlin.*\\]";is:=ignore - - # This tells bnd to ignore classes it finds in `META-INF/versions/` - # because bnd doesn't yet support multi-release jars. - -fixupmessages.wrong.dir: "Classes found in the wrong directory: \\{META-INF/versions/...";is:=ignore - - # Don't scan for Class.forName package imports. - # See https://bnd.bndtools.org/instructions/noclassforname.html - -noclassforname: true - - # Don't add all the extra headers bnd normally adds. - # See https://bnd.bndtools.org/instructions/noextraheaders.html - -noextraheaders: true - - # Don't add the Private-Package header. - # See https://bnd.bndtools.org/instructions/removeheaders.html - -removeheaders: Private-Package - - # Instruct the APIGuardianAnnotations how to operate. - # See https://bnd.bndtools.org/instructions/export-apiguardian.html - -export-apiguardian: *;version=${project.version} - """) - - // Add the convention to the jar task - convention.plugins["bundle"] = btc - - doLast { - // Do the actual work putting OSGi stuff in the jar. - btc.buildBundle() - } -} - -val osgiPropertiesFile = file("$buildDir/verifyOSGiProperties.bndrun") - -// Bnd's Resolve task uses a properties file for its configuration. This -// task writes out the properties necessary for it to verify the OSGi -// metadata. -val osgiProperties by tasks.registering(WriteProperties::class) { - outputFile = osgiPropertiesFile - property("-standalone", true) - project.extensions.getByType(JavaLibraryExtension::class.java).let { javaLibrary -> - property("-runee", "JavaSE-${javaLibrary.mainJavaVersion}") - } - property("-runrequires", "osgi.identity;filter:='(osgi.identity=${project.name})'") - property("-runsystempackages", "jdk.internal.misc,sun.misc") -} - -val osgiVerification by configurations.creating { - extendsFrom(configurations.runtimeClasspath.get()) -} - -// Bnd's Resolve task is what verifies that a jar can be used in OSGi and -// that its metadata is valid. If the metadata is invalid this task will -// fail. -val verifyOSGi by tasks.registering(Resolve::class) { - dependsOn(osgiProperties) - setBndrun(osgiPropertiesFile) - isReportOptional = false - withConvention(FileSetRepositoryConvention::class) { - - // By default bnd will use jars found in: - // 1. project.sourceSets.main.runtimeClasspath - // 2. project.configurations.archives.artifacts.files - // to validate the metadata. - // This adds jars defined in `osgiVerification` also so that bnd - // can use them to validate the metadata without causing those to - // end up in the dependencies of those projects. - bundles(osgiVerification) - } -} - -tasks.check { - dependsOn(verifyOSGi) -} - -// The ${project.description}, for some odd reason, is only available -// after evaluation. -afterEvaluate { - tasks.withType().configureEach { - convention.findPlugin(BundleTaskConvention::class.java) - ?.bnd("Bundle-Name: ${project.description}") - } -} diff --git a/buildSrc/src/main/kotlin/publishing-conventions.gradle.kts b/buildSrc/src/main/kotlin/publishing-conventions.gradle.kts deleted file mode 100644 index 6f58ce7453af..000000000000 --- a/buildSrc/src/main/kotlin/publishing-conventions.gradle.kts +++ /dev/null @@ -1,101 +0,0 @@ -import java.time.Duration - -plugins { - `maven-publish` - signing - id("de.marcphilipp.nexus-publish") -} - -val isSnapshot = project.version.toString().contains("SNAPSHOT") -val isContinuousIntegrationEnvironment = System.getenv("CI")?.toBoolean() ?: false -val isJitPackEnvironment = System.getenv("JITPACK")?.toBoolean() ?: false - -// ensure project is built successfully before publishing it -tasks.withType().configureEach { - dependsOn(tasks.build) -} -tasks.withType().configureEach { - dependsOn(tasks.build) -} - -signing { - sign(publishing.publications) - isRequired = !(isSnapshot || isContinuousIntegrationEnvironment || isJitPackEnvironment) -} - -tasks.withType().configureEach { - onlyIf { - !isSnapshot // Gradle Module Metadata currently does not support signing snapshots - } -} - -nexusPublishing { - connectTimeout.set(Duration.ofMinutes(2)) - clientTimeout.set(Duration.ofMinutes(2)) - packageGroup.set("org.junit") - repositories { - sonatype() - } -} - -publishing { - publications { - create("maven") { - pom { - name.set(provider { - project.description ?: "${project.group}:${project.name}" - }) - url.set("https://junit.org/junit5/") - scm { - connection.set("scm:git:git://github.com/junit-team/junit5.git") - developerConnection.set("scm:git:git://github.com/junit-team/junit5.git") - url.set("https://github.com/junit-team/junit5") - } - licenses { - license { - val license: License by rootProject.extra - name.set(license.name) - url.set(license.url.toString()) - } - } - developers { - developer { - id.set("bechte") - name.set("Stefan Bechtold") - email.set("stefan.bechtold@me.com") - } - developer { - id.set("jlink") - name.set("Johannes Link") - email.set("business@johanneslink.net") - } - developer { - id.set("marcphilipp") - name.set("Marc Philipp") - email.set("mail@marcphilipp.de") - } - developer { - id.set("mmerdes") - name.set("Matthias Merdes") - email.set("matthias.merdes@heidelpay.com") - } - developer { - id.set("sbrannen") - name.set("Sam Brannen") - email.set("sam@sambrannen.com") - } - developer { - id.set("sormuras") - name.set("Christian Stein") - email.set("sormuras@gmail.com") - } - developer { - id.set("juliette-derancourt") - name.set("Juliette de Rancourt") - email.set("derancourt.juliette@gmail.com") - } - } - } - } - } -} diff --git a/buildSrc/src/main/kotlin/testing-conventions.gradle.kts b/buildSrc/src/main/kotlin/testing-conventions.gradle.kts deleted file mode 100644 index 57a97e90247d..000000000000 --- a/buildSrc/src/main/kotlin/testing-conventions.gradle.kts +++ /dev/null @@ -1,43 +0,0 @@ -import org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL -import org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED - -plugins { - id("org.gradle.test-retry") -} - -tasks.withType().configureEach { - useJUnitPlatform { - includeEngines("junit-jupiter") - } - include("**/*Test.class", "**/*Tests.class") - testLogging { - events = setOf(FAILED) - exceptionFormat = FULL - } - retry { - maxRetries.set(2) - } - systemProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager") - // Required until ASM officially supports the JDK 14 - systemProperty("net.bytebuddy.experimental", true) -} - -dependencies { - "testImplementation"("org.assertj:assertj-core") - "testImplementation"("org.mockito:mockito-junit-jupiter") { - exclude(module = "junit-jupiter-engine") - } - - if (project.name != "junit-jupiter-engine") { - "testImplementation"(project(":junit-jupiter-api")) - "testImplementation"(project(":junit-jupiter-params")) - - "testRuntimeOnly"(project(":junit-jupiter-engine")) - } - "testImplementation"(testFixtures(project(":junit-jupiter-api"))) - - "testRuntimeOnly"(project(":junit-platform-launcher")) - - "testRuntimeOnly"("org.apache.logging.log4j:log4j-core") - "testRuntimeOnly"("org.apache.logging.log4j:log4j-jul") -} diff --git a/dependencies/dependencies.gradle.kts b/dependencies/dependencies.gradle.kts deleted file mode 100644 index 090ab495abd5..000000000000 --- a/dependencies/dependencies.gradle.kts +++ /dev/null @@ -1,37 +0,0 @@ -plugins { - `java-platform` -} - -dependencies { - constraints { - // api means "the dependency is for both compilation and runtime" - // runtime means "the dependency is only for runtime, not for compilation" - // In other words, marking dependency as "runtime" would avoid accidental - // dependency on it during compilation - api("org.apiguardian:apiguardian-api:${versions.apiguardian}") - api("org.opentest4j:opentest4j:${versions.opentest4j}") - runtime("org.apache.logging.log4j:log4j-core:${versions["log4j"]}") - runtime("org.apache.logging.log4j:log4j-jul:${versions["log4j"]}") - api("io.github.classgraph:classgraph:${versions["classgraph"]}") - api("org.codehaus.groovy:groovy-all:${versions["groovy"]}") - api("junit:junit:[${versions.junit4Min},)") { - version { - prefer(versions.junit4) - } - } - api("com.univocity:univocity-parsers:${versions["univocity-parsers"]}") - api("info.picocli:picocli:${versions["picocli"]}") - api("org.assertj:assertj-core:${versions.assertj}") - api("org.openjdk.jmh:jmh-core:${versions["jmh"]}") - api("org.openjdk.jmh:jmh-generator-annprocess:${versions["jmh"]}") - api("de.sormuras:bartholdy:${versions["bartholdy"]}") - api("commons-io:commons-io:${versions["commons-io"]}") - api("com.tngtech.archunit:archunit-junit5-api:${versions["archunit"]}") - api("com.tngtech.archunit:archunit-junit5-engine:${versions["archunit"]}") - api("org.slf4j:slf4j-jdk14:${versions["slf4j"]}") - api("org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions["kotlinx-coroutines-core"]}") - api("org.mockito:mockito-junit-jupiter:${versions["mockito"]}") - api("biz.aQute.bnd:biz.aQute.bndlib:${versions["bnd"]}") - api("org.spockframework:spock-core:${versions["spock"]}") - } -} diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index 4cb26b1433ca..4cc640563170 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -1,15 +1,19 @@ +import junitbuild.exec.CaptureJavaExecOutput +import junitbuild.exec.ClasspathSystemPropertyProvider +import junitbuild.exec.GenerateStandaloneConsoleLauncherShadowedArtifactsFile +import junitbuild.exec.RunConsoleLauncher +import junitbuild.javadoc.ModuleSpecificJavadocFileOption +import org.asciidoctor.gradle.base.AsciidoctorAttributeProvider import org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask -import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet -import org.junit.gradle.exec.ClasspathSystemPropertyProvider -import org.junit.gradle.javadoc.ModuleSpecificJavadocFileOption -import java.io.ByteArrayOutputStream -import java.nio.file.Files +import org.gradle.api.tasks.PathSensitivity.RELATIVE plugins { - id("org.asciidoctor.jvm.convert") - id("org.asciidoctor.jvm.pdf") - id("org.ajoberstar.git-publish") - `kotlin-library-conventions` + alias(libs.plugins.asciidoctorConvert) + alias(libs.plugins.asciidoctorPdf) + alias(libs.plugins.gitPublish) + id("junitbuild.build-parameters") + id("junitbuild.kotlin-library-conventions") + id("junitbuild.testing-conventions") } val modularProjects: List by rootProject @@ -22,14 +26,11 @@ javaLibrary { testJavaVersion = JavaVersion.VERSION_1_8 } -val apiReport by configurations.creating { - extendsFrom(configurations.internal.get()) -} +val apiReport by configurations.creatingResolvable +val standaloneConsoleLauncher by configurations.creatingResolvable dependencies { - internal(platform(project(":dependencies"))) - - implementation(project(":junit-jupiter-api")) { + implementation(projects.junitJupiterApi) { because("Jupiter API is used in src/main/java") } @@ -37,42 +38,52 @@ dependencies { // in reports generated by the ApiReportGenerator. modularProjects.forEach { apiReport(it) } - testImplementation(project(":junit-jupiter")) - testImplementation(project(":junit-jupiter-migrationsupport")) - testImplementation(project(":junit-platform-console")) - testImplementation(project(":junit-platform-runner")) - testImplementation(project(":junit-platform-testkit")) - testImplementation("org.jetbrains.kotlin:kotlin-stdlib") + testImplementation(projects.junitJupiterMigrationsupport) + testImplementation(projects.junitPlatformConsole) + testImplementation(projects.junitPlatformRunner) + testImplementation(projects.junitPlatformSuite) + testImplementation(projects.junitPlatformTestkit) + testImplementation(kotlin("stdlib")) - testImplementation(project(":junit-vintage-engine")) - testRuntimeOnly("org.apache.logging.log4j:log4j-core") - testRuntimeOnly("org.apache.logging.log4j:log4j-jul") + testImplementation(projects.junitVintageEngine) + testRuntimeOnly(libs.apiguardian) { + because("it's required to generate API tables") + } - testImplementation("io.github.classgraph:classgraph") { + testImplementation(libs.classgraph) { because("ApiReportGenerator needs it") } + + testImplementation(libs.jimfs) { + because("Jimfs is used in src/test/java") + } + + standaloneConsoleLauncher(projects.junitPlatformConsoleStandalone) } asciidoctorj { modules { diagram.use() - pdf.version(versions["asciidoctor-pdf"]) + pdf.version(libs.versions.asciidoctor.pdf) } + requires(file("src/docs/asciidoc/resources/themes/rouge_junit.rb")) } 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 docsDir = file("$buildDir/ghpages-docs") -val replaceCurrentDocs = project.hasProperty("replaceCurrentDocs") +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 (versions.opentest4j.contains("SNAPSHOT")) "snapshot" else versions.opentest4j -val apiGuardianDocVersion = if (versions.apiguardian.contains("SNAPSHOT")) "snapshot" else versions.apiguardian +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() gitPublish { - repoUri.set("https://github.com/junit-team/junit5.git") - branch.set("gh-pages") + repoUri = "https://github.com/junit-team/junit5.git" + branch = "gh-pages" + sign = false + fetchDepth = 1 contents { from(docsDir) @@ -88,15 +99,20 @@ gitPublish { } } -val generatedAsciiDocPath = file("$buildDir/generated/asciidoc") -val consoleLauncherOptionsFile = File(generatedAsciiDocPath, "console-launcher-options.txt") -val experimentalApisTableFile = File(generatedAsciiDocPath, "experimental-apis-table.txt") -val deprecatedApisTableFile = File(generatedAsciiDocPath, "deprecated-apis-table.txt") - -val elementListsDir = file("$buildDir/elementLists") +val generatedAsciiDocPath = layout.buildDirectory.dir("generated/asciidoc") +val consoleLauncherOptionsFile = generatedAsciiDocPath.map { it.file("console-launcher-options.txt") } +val consoleLauncherDiscoverOptionsFile = generatedAsciiDocPath.map { it.file("console-launcher-discover-options.txt") } +val consoleLauncherExecuteOptionsFile = generatedAsciiDocPath.map { it.file("console-launcher-execute-options.txt") } +val consoleLauncherEnginesOptionsFile = generatedAsciiDocPath.map { it.file("console-launcher-engines-options.txt") } +val experimentalApisTableFile = generatedAsciiDocPath.map { it.file("experimental-apis-table.adoc") } +val deprecatedApisTableFile = generatedAsciiDocPath.map { it.file("deprecated-apis-table.adoc") } +val standaloneConsoleLauncherShadowedArtifactsFile = generatedAsciiDocPath.map { it.file("console-launcher-standalone-shadowed-artifacts.adoc") } + +val jdkJavadocBaseUrl = "https://docs.oracle.com/en/java/javase/11/docs/api" +val elementListsDir = layout.buildDirectory.dir("elementLists") val externalModulesWithoutModularJavadoc = mapOf( "org.apiguardian.api" to "https://apiguardian-team.github.io/apiguardian/docs/$apiGuardianDocVersion/api/", - "org.assertj.core" to "https://javadoc.io/doc/org.assertj/assertj-core/${versions.assertj}/", + "org.assertj.core" to "https://javadoc.io/doc/org.assertj/assertj-core/${libs.versions.assertj.get()}/", "org.opentest4j" to "https://ota4j-team.github.io/opentest4j/docs/$ota4jDocVersion/api/" ) require(externalModulesWithoutModularJavadoc.values.all { it.endsWith("/") }) { @@ -105,52 +121,102 @@ require(externalModulesWithoutModularJavadoc.values.all { it.endsWith("/") }) { tasks { - val consoleLauncherTest by registering(JavaExec::class) { - dependsOn(testClasses) - val reportsDir = file("$buildDir/test-results") + 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) - outputs.cacheIf { true } - classpath = sourceSets["test"].runtimeClasspath - main = "org.junit.platform.console.ConsoleLauncher" - args("--scan-classpath") - args("--include-classname", ".*Tests") - args("--include-classname", ".*Demo") - args("--exclude-tag", "exclude") - args("--reports-dir", reportsDir) - systemProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager") + argumentProviders.add(CommandLineArgumentProvider { + listOf( + "--reports-dir=${reportsDir.get()}", + "--config=junit.platform.reporting.output.dir=${reportsDir.get()}" + + ) + }) + args.addAll("--config", "enableHttpServer=true") + args.addAll("--include-classname", ".*Tests") + args.addAll("--include-classname", ".*Demo") + args.addAll("--exclude-tag", "exclude") + args.addAll("--exclude-tag", "timeout") + } + + register("consoleLauncher") { + hideOutput = false + outputs.upToDateWhen { false } } test { + include("**/*Demo.class") + (options as JUnitPlatformOptions).apply { + includeEngines("junit-vintage") + includeTags("timeout") + } + } + + check { dependsOn(consoleLauncherTest) - exclude("**/*") } - val generateConsoleLauncherOptions by registering(JavaExec::class) { - classpath = sourceSets["test"].runtimeClasspath - main = "org.junit.platform.console.ConsoleLauncher" - args("--help", "--disable-banner") - redirectOutput(consoleLauncherOptionsFile) + val generateConsoleLauncherOptions by registering(CaptureJavaExecOutput::class) { + classpath.from(sourceSets["test"].runtimeClasspath) + mainClass = "org.junit.platform.console.ConsoleLauncher" + args.addAll("--help", "--disable-banner") + outputFile = consoleLauncherOptionsFile + } + + val generateConsoleLauncherDiscoverOptions by registering(CaptureJavaExecOutput::class) { + classpath.from(sourceSets["test"].runtimeClasspath) + mainClass = "org.junit.platform.console.ConsoleLauncher" + args.addAll("discover", "--help", "--disable-banner") + outputFile = consoleLauncherDiscoverOptionsFile + } + + val generateConsoleLauncherExecuteOptions by registering(CaptureJavaExecOutput::class) { + classpath.from(sourceSets["test"].runtimeClasspath) + mainClass = "org.junit.platform.console.ConsoleLauncher" + args.addAll("execute", "--help", "--disable-banner") + outputFile = consoleLauncherExecuteOptionsFile + } + + val generateConsoleLauncherEnginesOptions by registering(CaptureJavaExecOutput::class) { + classpath.from(sourceSets["test"].runtimeClasspath) + mainClass = "org.junit.platform.console.ConsoleLauncher" + args.addAll("engines", "--help", "--disable-banner") + outputFile = consoleLauncherEnginesOptionsFile } - val generateExperimentalApisTable by registering(JavaExec::class) { - classpath = sourceSets["test"].runtimeClasspath - main = "org.junit.api.tools.ApiReportGenerator" + val generateExperimentalApisTable by registering(CaptureJavaExecOutput::class) { + classpath.from(sourceSets["test"].runtimeClasspath) + mainClass = "org.junit.api.tools.ApiReportGenerator" jvmArgumentProviders += ClasspathSystemPropertyProvider("api.classpath", apiReport) - args("EXPERIMENTAL") - redirectOutput(experimentalApisTableFile) + args.add("EXPERIMENTAL") + outputFile = experimentalApisTableFile } - val generateDeprecatedApisTable by registering(JavaExec::class) { - classpath = sourceSets["test"].runtimeClasspath - main = "org.junit.api.tools.ApiReportGenerator" + val generateDeprecatedApisTable by registering(CaptureJavaExecOutput::class) { + classpath.from(sourceSets["test"].runtimeClasspath) + mainClass = "org.junit.api.tools.ApiReportGenerator" jvmArgumentProviders += ClasspathSystemPropertyProvider("api.classpath", apiReport) - args("DEPRECATED") - redirectOutput(deprecatedApisTableFile) + args.add("DEPRECATED") + outputFile = deprecatedApisTableFile + } + + val generateStandaloneConsoleLauncherShadowedArtifactsFile by registering(GenerateStandaloneConsoleLauncherShadowedArtifactsFile::class) { + inputJar.fileProvider(standaloneConsoleLauncher.elements.map { it.single().asFile }) + outputFile = standaloneConsoleLauncherShadowedArtifactsFile } withType().configureEach { - dependsOn(generateConsoleLauncherOptions, generateExperimentalApisTable, generateDeprecatedApisTable) - inputs.files(consoleLauncherOptionsFile, experimentalApisTableFile, deprecatedApisTableFile) + inputs.files( + generateConsoleLauncherOptions, + generateConsoleLauncherDiscoverOptions, + generateConsoleLauncherExecuteOptions, + generateConsoleLauncherEnginesOptions, + generateExperimentalApisTable, + generateDeprecatedApisTable, + generateStandaloneConsoleLauncherShadowedArtifactsFile + ) resources { from(sourceDir) { @@ -159,30 +225,41 @@ tasks { } } - attributes(mapOf( + // Temporary workaround for https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/599 + inputs.dir(sourceDir).withPropertyName("sourceDir").withPathSensitivity(RELATIVE) + + attributeProviders += AsciidoctorAttributeProvider { + mapOf( "jupiter-version" to version, "platform-version" to project.property("platformVersion"), "vintage-version" to project.property("vintageVersion"), "bom-version" to version, - "junit4-version" to versions.junit4, - "apiguardian-version" to versions.apiguardian, - "ota4j-version" to versions.opentest4j, - "surefire-version" to versions["surefire"], + "junit4-version" to libs.versions.junit4.get(), + "apiguardian-version" to libs.versions.apiguardian.get(), + "ota4j-version" to libs.versions.opentest4j.get(), + "surefire-version" to libs.versions.surefire.get(), "release-branch" to releaseBranch, "docs-version" to docsVersion, "revnumber" to version, - "consoleLauncherOptionsFile" to consoleLauncherOptionsFile, - "experimentalApisTableFile" to experimentalApisTableFile, - "deprecatedApisTableFile" to deprecatedApisTableFile, + "consoleLauncherOptionsFile" to consoleLauncherOptionsFile.get(), + "consoleLauncherDiscoverOptionsFile" to consoleLauncherDiscoverOptionsFile.get(), + "consoleLauncherExecuteOptionsFile" to consoleLauncherExecuteOptionsFile.get(), + "consoleLauncherEnginesOptionsFile" to consoleLauncherEnginesOptionsFile.get(), + "experimentalApisTableFile" to experimentalApisTableFile.get(), + "deprecatedApisTableFile" to deprecatedApisTableFile.get(), + "standaloneConsoleLauncherShadowedArtifactsFile" to standaloneConsoleLauncherShadowedArtifactsFile.get(), "outdir" to outputDir.absolutePath, "source-highlighter" to "rouge", + "rouge-style" to "junit", "tabsize" to "4", "toc" to "left", "icons" to "font", "sectanchors" to true, "idprefix" to "", - "idseparator" to "-" - )) + "idseparator" to "-", + "jdk-javadoc-base-url" to jdkJavadocBaseUrl + ) + } sourceSets["test"].apply { attributes(mapOf( @@ -191,11 +268,19 @@ tasks { )) inputs.dir(java.srcDirs.first()) inputs.dir(resources.srcDirs.first()) - withConvention(KotlinSourceSet::class) { - attributes(mapOf("kotlinTestDir" to kotlin.srcDirs.first())) - inputs.dir(kotlin.srcDirs.first()) - } + attributes(mapOf("kotlinTestDir" to kotlin.srcDirs.first())) + inputs.dir(kotlin.srcDirs.first()) + } + + forkOptions { + // To avoid warning, see https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/597 + jvmArgs( + "--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED", + "--add-opens", "java.base/java.io=ALL-UNNAMED" + ) } + + notCompatibleWithConfigurationCache("https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/564") } asciidoctor { @@ -229,7 +314,7 @@ tasks { doFirst { externalModulesWithoutModularJavadoc.forEach { (moduleName, baseUrl) -> val resource = resources.text.fromUri("${baseUrl}element-list") - elementListsDir.resolve(moduleName).apply { + elementListsDir.get().asFile.resolve(moduleName).apply { mkdir() resolve("element-list").writeText("module:$moduleName\n${resource.asString()}") } @@ -261,17 +346,17 @@ tasks { this as StandardJavadocDocletOptions splitIndex(true) - addBooleanOption("Xdoclint:none", true) + addBooleanOption("Xdoclint:all,-missing", true) addBooleanOption("html5", true) addMultilineStringsOption("tag").value = listOf( "apiNote:a:API Note:", "implNote:a:Implementation Note:" ) - links("https://docs.oracle.com/en/java/javase/11/docs/api/") - links("https://junit.org/junit4/javadoc/${versions.junit4}/") + links(jdkJavadocBaseUrl) + links("https://junit.org/junit4/javadoc/${libs.versions.junit4.get()}/") externalModulesWithoutModularJavadoc.forEach { (moduleName, baseUrl) -> - linksOffline(baseUrl, "$elementListsDir/$moduleName") + linksOffline(baseUrl, elementListsDir.get().asFile.resolve(moduleName).absolutePath) } groups = mapOf( @@ -293,6 +378,7 @@ tasks { addStringOption("-add-modules", "info.picocli") addOption(ModuleSpecificJavadocFileOption("-add-reads", mapOf( "org.junit.platform.console" to "info.picocli", + "org.junit.platform.reporting" to "org.opentest4j.reporting.events", "org.junit.jupiter.params" to "univocity.parsers" ))) } @@ -301,7 +387,7 @@ tasks { classpath = files(modularProjects.map { it.sourceSets.main.get().compileClasspath }) maxMemory = "1024m" - destinationDir = file("$buildDir/docs/javadoc") + destinationDir = layout.buildDirectory.dir("docs/javadoc").get().asFile doFirst { (options as CoreJavadocOptions).modulePath = classpath.files.toList() @@ -331,14 +417,14 @@ tasks { } } } - into("$buildDir/docs/fixedJavadoc") + into(layout.buildDirectory.dir("docs/fixedJavadoc")) } val prepareDocsForUploadToGhPages by registering(Copy::class) { dependsOn(fixJavadoc, asciidoctor, asciidoctorPdf) outputs.dir(docsDir) - from("$buildDir/checksum") { + from(layout.buildDirectory.dir("checksum")) { include("published-checksum.txt") } from(asciidoctor.map { it.outputDir }) { @@ -355,7 +441,7 @@ tasks { from(fixJavadoc.map { it.destinationDir }) { into("api") } - into("$docsDir/$docsVersion") + into(docsDir.map { it.dir(docsVersion.toString()) }) includeEmptyDirs = false } @@ -368,31 +454,36 @@ tasks { into("$docsDir/current") } - gitPublishCommit { + val configureGitAuthor by registering { + dependsOn(gitPublishReset) + doFirst { + File(gitPublish.repoDir.get().asFile, ".git/config").appendText(""" + [user] + name = JUnit Team + email = team@junit.org + """.trimIndent()) + } + } + + gitPublishCopy { dependsOn(prepareDocsForUploadToGhPages, createCurrentDocsFolder) } -} -fun JavaExec.redirectOutput(outputFile: File) { - outputs.file(outputFile) - val byteStream = ByteArrayOutputStream() - standardOutput = byteStream - doLast { - Files.createDirectories(outputFile.parentFile.toPath()) - Files.write(outputFile.toPath(), byteStream.toByteArray()) + gitPublishCommit { + dependsOn(configureGitAuthor) } } eclipse { classpath { - plusConfigurations.add(project(":junit-platform-console").configurations["shadowed"]) - plusConfigurations.add(project(":junit-jupiter-params").configurations["shadowed"]) + plusConfigurations.add(projects.junitPlatformConsole.dependencyProject.configurations["shadowed"]) + plusConfigurations.add(projects.junitJupiterParams.dependencyProject.configurations["shadowed"]) } } idea { module { - scopes["PROVIDED"]!!["plus"]!!.add(project(":junit-platform-console").configurations["shadowed"]) - scopes["PROVIDED"]!!["plus"]!!.add(project(":junit-jupiter-params").configurations["shadowed"]) + scopes["PROVIDED"]!!["plus"]!!.add(projects.junitPlatformConsole.dependencyProject.configurations["shadowed"]) + scopes["PROVIDED"]!!["plus"]!!.add(projects.junitJupiterParams.dependencyProject.configurations["shadowed"]) } } diff --git a/documentation/src/docs/asciidoc/link-attributes.adoc b/documentation/src/docs/asciidoc/link-attributes.adoc index a994609b335a..6aa27c3d46b8 100644 --- a/documentation/src/docs/asciidoc/link-attributes.adoc +++ b/documentation/src/docs/asciidoc/link-attributes.adoc @@ -14,6 +14,7 @@ endif::[] :ClassSupport: {javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/ClassSupport.html[ClassSupport] :ModifierSupport: {javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/ModifierSupport.html[ModifierSupport] :ReflectionSupport: {javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/ReflectionSupport.html[ReflectionSupport] +:Testable: {javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/annotation/Testable.html[@Testable] // Platform Console Launcher :junit-platform-console: {javadoc-root}/org.junit.platform.console/org/junit/platform/console/package-summary.html[junit-platform-console] :ConsoleLauncher: {javadoc-root}/org.junit.platform.console/org/junit/platform/console/ConsoleLauncher.html[ConsoleLauncher] @@ -27,20 +28,28 @@ endif::[] // Platform Launcher API :junit-platform-launcher: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/package-summary.html[junit-platform-launcher] :Launcher: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/Launcher.html[Launcher] +:LauncherConfig: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/LauncherConfig.html[LauncherConfig] :LauncherDiscoveryListener: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherDiscoveryListener.html[LauncherDiscoveryListener] :LauncherDiscoveryRequest: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherDiscoveryRequest.html[LauncherDiscoveryRequest] -:PostDiscoveryFilter: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/PostDiscoveryFilter.html[PostDiscoveryFilter] :LauncherDiscoveryRequestBuilder: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.html[LauncherDiscoveryRequestBuilder] +:LauncherFactory: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/LauncherFactory.html[LauncherFactory] +:LauncherInterceptor: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherInterceptor.html[LauncherInterceptor] +:LauncherSession: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherSession.html[LauncherSession] +:LauncherSessionListener: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherSessionListener.html[LauncherSessionListener] :LoggingListener: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/listeners/LoggingListener.html[LoggingListener] +:PostDiscoveryFilter: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/PostDiscoveryFilter.html[PostDiscoveryFilter] :SummaryGeneratingListener: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/listeners/SummaryGeneratingListener.html[SummaryGeneratingListener] :TestExecutionListener: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/TestExecutionListener.html[TestExecutionListener] :TestPlan: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/TestPlan.html[TestPlan] +:UniqueIdTrackingListener: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.html[UniqueIdTrackingListener] // Platform Reporting :LegacyXmlReportGeneratingListener: {javadoc-root}/org.junit.platform.reporting/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListener.html[LegacyXmlReportGeneratingListener] +:OpenTestReportGeneratingListener: {javadoc-root}/org.junit.platform.reporting/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.html[OpenTestReportGeneratingListener] // Platform Runner :JUnitPlatform-Runner: {javadoc-root}/org.junit.platform.runnner/org/junit/platform/runner/JUnitPlatform.html[JUnitPlatform] -// Platform Suite API +// Platform Suite :suite-api-package: {javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/package-summary.html[org.junit.platform.suite.api] +:junit-platform-suite-engine: {javadoc-root}/org.junit.platform.suite.engine/org/junit/platform/suite/engine/package-summary.html[junit-platform-suite-engine] // Platform Test Kit :testkit-engine-package: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/package-summary.html[org.junit.platform.testkit.engine] :EngineExecutionResults: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/EngineExecutionResults.html[EngineExecutionResults] @@ -56,18 +65,25 @@ endif::[] :TestExecutionResultConditions: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/TestExecutionResultConditions.html[TestExecutionResultConditions] // Jupiter Core API :api-package: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/package-summary.html[org.junit.jupiter.api] -:Alphanumeric: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.Alphanumeric.html[Alphanumeric] -:MethodName: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.MethodName.html[MethodName] :Assertions: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Assertions.html[org.junit.jupiter.api.Assertions] :Assumptions: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Assumptions.html[org.junit.jupiter.api.Assumptions] +:ClassOrderer_ClassName: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.ClassName.html[ClassOrderer.ClassName] +:ClassOrderer_DisplayName: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.DisplayName.html[ClassOrderer.DisplayName] +:ClassOrderer_OrderAnnotation: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.OrderAnnotation.html[ClassOrderer.OrderAnnotation] +:ClassOrderer_Random: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.Random.html[ClassOrderer.Random] +:ClassOrderer: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.html[ClassOrderer] :Disabled: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Disabled.html[@Disabled] -:DisplayName: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.DisplayName.html[DisplayName] +:MethodOrderer_Alphanumeric: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.Alphanumeric.html[MethodOrderer.Alphanumeric] +:MethodOrderer_DisplayName: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.DisplayName.html[MethodOrderer.DisplayName] +:MethodOrderer_MethodName: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.MethodName.html[MethodOrderer.MethodName] +:MethodOrderer_OrderAnnotation: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.OrderAnnotation.html[MethodOrderer.OrderAnnotation] +:MethodOrderer_Random: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.Random.html[MethodOrderer.Random] :MethodOrderer: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.html[MethodOrderer] +:Named: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Named.html[Named] :Order: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Order.html[@Order] -:OrderAnnotation: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.OrderAnnotation.html[OrderAnnotation] -:Random: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.Random.html[Random] :RepetitionInfo: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/RepetitionInfo.html[RepetitionInfo] :TestInfo: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestInfo.html[TestInfo] +:TestClassOrder: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestClassOrder.html[@TestClassOrder] :TestMethodOrder: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestMethodOrder.html[@TestMethodOrder] :TestReporter: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestReporter.html[TestReporter] :TestTemplate: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestTemplate.html[@TestTemplate] @@ -85,6 +101,7 @@ endif::[] :BeforeAllCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeAllCallback.html[BeforeAllCallback] :BeforeEachCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeEachCallback.html[BeforeEachCallback] :BeforeTestExecutionCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeTestExecutionCallback.html[BeforeTestExecutionCallback] +:ExecutableInvoker: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExecutableInvoker.html[ExecutableInvoker] :ExecutionCondition: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExecutionCondition.html[ExecutionCondition] :ExtendWith: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExtendWith.html[@ExtendWith] :ExtensionContext: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExtensionContext.html[ExtensionContext] @@ -96,40 +113,47 @@ endif::[] :TestExecutionExceptionHandler: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.html[TestExecutionExceptionHandler] :TestInstanceFactory: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestInstanceFactory.html[TestInstanceFactory] :TestInstancePostProcessor: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestInstancePostProcessor.html[TestInstancePostProcessor] +:TestInstancePreConstructCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestInstancePreConstructCallback.html[TestInstancePreConstructCallback] :TestInstancePreDestroyCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestInstancePreDestroyCallback.html[TestInstancePreDestroyCallback] :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] // 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] :DisabledIfEnvironmentVariable: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.html[@DisabledIfEnvironmentVariable] :DisabledIfSystemProperty: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledIfSystemProperty.html[@DisabledIfSystemProperty] +:DisabledInNativeImage: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledInNativeImage.html[@DisabledInNativeImage] :DisabledOnJre: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledOnJre.html[@DisabledOnJre] :DisabledOnOs: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledOnOs.html[@DisabledOnOs] -:DisabledIf: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledIf.html[@DisabledIf] :EnabledForJreRange: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledForJreRange.html[@EnabledForJreRange] +:EnabledIf: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledIf.html[@EnabledIf] :EnabledIfEnvironmentVariable: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.html[@EnabledIfEnvironmentVariable] :EnabledIfSystemProperty: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledIfSystemProperty.html[@EnabledIfSystemProperty] +:EnabledInNativeImage: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledInNativeImage.html[@EnabledInNativeImage] :EnabledOnJre: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledOnJre.html[@EnabledOnJre] :EnabledOnOs: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledOnOs.html[@EnabledOnOs] -:EnabledIf: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledIf.html[@EnabledIf] :JRE: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/JRE.html[JRE] // Jupiter I/O :TempDir: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/io/TempDir.html[@TempDir] // Jupiter Params :params-provider-package: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/package-summary.html[org.junit.jupiter.params.provider] +:AnnotationBasedArgumentConverter: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/AnnotationBasedArgumentConverter.html[AnnotationBasedArgumentConverter] +:AnnotationBasedArgumentsProvider: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProvider.html[AnnotationBasedArgumentsProvider] :ArgumentsAccessor: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/aggregator/ArgumentsAccessor.html[ArgumentsAccessor] :ArgumentsAggregator: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/aggregator/ArgumentsAggregator.html[ArgumentsAggregator] +:CsvArgumentsProvider: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/CsvArgumentsProvider.html[CsvArgumentsProvider] :EmptySource: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/EmptySource.html[@EmptySource] :MethodSource: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/MethodSource.html[@MethodSource] :NullAndEmptySource: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/NullAndEmptySource.html[@NullAndEmptySource] :NullSource: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/NullSource.html[@NullSource] :ParameterizedTest: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/ParameterizedTest.html[@ParameterizedTest] +:ValueArgumentsProvider: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/ValueArgumentsProvider.html[ValueArgumentsProvider] // Jupiter Engine :junit-jupiter-engine: {javadoc-root}/org.junit.jupiter.engine/org/junit/jupiter/engine/package-summary.html[junit-jupiter-engine] // Jupiter Extension Implementations :DisabledCondition: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java[DisabledCondition] -:RepetitionInfoParameterResolver: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionInfoParameterResolver.java[RepetitionInfoParameterResolver] +:RepetitionExtension: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionExtension.java[RepetitionExtension] :TempDirectory: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java[TempDirectory] :TestInfoParameterResolver: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestInfoParameterResolver.java[TestInfoParameterResolver] :TestReporterParameterResolver: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestReporterParameterResolver.java[TestReporterParameterResolver] @@ -154,15 +178,19 @@ endif::[] // Third-party Links :API: https://apiguardian-team.github.io/apiguardian/docs/current/api/[@API] :API_Guardian: https://github.com/apiguardian-team/apiguardian[@API Guardian] -:AssertJ: https://joel-costigliola.github.io/assertj/[AssertJ] +:AssertJ: https://assertj.github.io/doc/[AssertJ] :Gitter: https://gitter.im/junit-team/junit5[Gitter] :Hamcrest: https://hamcrest.org/JavaHamcrest/[Hamcrest] +:Jimfs: https://google.github.io/jimfs/[Jimfs] :Log4j: https://logging.apache.org/log4j/2.x/[Log4j] :Log4j_JDK_Logging_Adapter: https://logging.apache.org/log4j/2.x/log4j-jul/index.html[Log4j JDK Logging Adapter] :Logback: https://logback.qos.ch/[Logback] :LogManager: https://docs.oracle.com/javase/8/docs/api/java/util/logging/LogManager.html[LogManager] :Maven_Central: https://search.maven.org/[Maven Central] :MockitoExtension: https://github.com/mockito/mockito/blob/release/2.x/subprojects/junit-jupiter/src/main/java/org/mockito/junit/jupiter/MockitoExtension.java[MockitoExtension] +:ServiceLoader: {jdk-javadoc-base-url}/java.base/java/util/ServiceLoader.html[ServiceLoader] :SpringExtension: https://github.com/spring-projects/spring-framework/tree/HEAD/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java[SpringExtension] :StackOverflow: https://stackoverflow.com/questions/tagged/junit5[Stack Overflow] :Truth: https://truth.dev/[Truth] +:OpenTestReporting: https://github.com/ota4j-team/open-test-reporting[Open Test Reporting] +:OpenTestReportingCliTool: https://github.com/ota4j-team/open-test-reporting#cli-tool-for-validation-and-format-conversion[Open Test Reporting CLI Tool] diff --git a/documentation/src/docs/asciidoc/release-notes/index.adoc b/documentation/src/docs/asciidoc/release-notes/index.adoc index 82817f7d5100..ec787091b41e 100644 --- a/documentation/src/docs/asciidoc/release-notes/index.adoc +++ b/documentation/src/docs/asciidoc/release-notes/index.adoc @@ -8,7 +8,7 @@ Stefan Bechtold; Sam Brannen; Johannes Link; Matthias Merdes; Marc Philipp; Juli :numbered!: // -This document contains the _change log_ for all JUnit 5 releases since 5.6 GA. +This document contains the _change log_ for all JUnit 5 releases since 5.9 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 @@ -16,10 +16,12 @@ authors as well as build tool and IDE vendors. include::{includedir}/link-attributes.adoc[] -include::{basedir}/release-notes-5.7.0.adoc[] +include::{basedir}/release-notes-5.10.0.adoc[] -include::{basedir}/release-notes-5.6.2.adoc[] +include::{basedir}/release-notes-5.9.3.adoc[] -include::{basedir}/release-notes-5.6.1.adoc[] +include::{basedir}/release-notes-5.9.2.adoc[] -include::{basedir}/release-notes-5.6.0.adoc[] +include::{basedir}/release-notes-5.9.1.adoc[] + +include::{basedir}/release-notes-5.9.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 new file mode 100644 index 000000000000..3141c2dbae97 --- /dev/null +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.0.adoc @@ -0,0 +1,173 @@ +[[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 a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit5-repo}+/milestone/65?closed=1+[5.10.0-M1], +link:{junit5-repo}+/milestone/69?closed=1+[5.10.0-RC1], +link:{junit5-repo}+/milestone/71?closed=1+[5.10.0-RC2], and +link:{junit5-repo}+/milestone/70?closed=1+[5.10.0 GA] milestone pages in the JUnit +repository on GitHub. + + +[[release-notes-5.10.0-junit-platform]] +=== JUnit Platform + +==== Deprecations and Breaking Changes + +* Building native images with GraalVM now requires configuring the build arg + `--initialize-at-build-time=org.junit.platform.launcher.core.LauncherConfig` and + `--initialize-at-build-time=org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter`. +* The `getMethodParameterTypes()` methods in `MethodSelector` and `NestedMethodSelector` + have been deprecated and replaced by `getParameterTypeNames()` for greater clarity. + +==== New Features and Improvements + +* Various "experimental" APIs have been promoted to "stable", including + `ModuleSelector`, `EngineDiscoveryListener`, `EngineDiscoveryRequestResolver`, + `LauncherSession`, `LauncherSessionListener`, parallel execution support classes, + `@Suite` and related annotations, and others. +* All utility methods in `ReflectionSupport` that return a `List` now have counterparts + which return a `Stream`. +* New `tryToLoadClass(...)` variant in `ReflectionSupport` that accepts an explicit + `ClassLoader`, allowing classes to be resolved with custom `ClassLoader` arrangements. +* `ReflectionSupport.findMethod(Class, String, String)` now uses the `ClassLoader` of + the supplied `Class` to load parameter types instead of using the _default_ + `ClassLoader`. This allows parameter types to be resolved with custom `ClassLoader` + arrangements (such as OSGi). Consequently, `DiscoverySelectors.selectMethod(Class, + String, String)` also now works properly with custom `ClassLoader` arrangements. + +* New `@SelectMethod` selector support in the `@Suite` test engine. +* Classes may now be selected by fully-qualified name via the `names` attribute in + `@SelectClasses`. +* New overloaded constructors for `ClassSelector`, `NestedClassSelector`, + `MethodSelector`, and `NestedMethodSelector` that take an explicit `ClassLoader` as a + parameter, allowing selectors to select classes in custom `ClassLoader` arrangements + like in OSGi. +* New `selectMethod()` and `selectNestedMethod()` variants in `DiscoverySelectors` that + accept a `Class...` argument of parameter types as a type-safe alternative to + providing the names of parameter types as a comma-delimited string. +* For consistency with JUnit Jupiter lifecycle callbacks, listener method pairs for + started/finished and opened/closed events are now invoked using "wrapping" semantics. + This means that finished/closed event methods are invoked in reverse order compared to + the corresponding started/opened event methods when multiple listeners are registered. + This affects the following listener interfaces: + `TestExecutionListener`, `EngineExecutionListener`, `LauncherDiscoveryListener`, and + `LauncherSessionListener`. +* New `LauncherInterceptor` SPI for intercepting the creation of instances of `Launcher` + and `LauncherSessionlistener` as well as invocations of the `discover` and `execute` + methods of the former. Please refer to the + <<../user-guide/index.adoc#launcher-api-launcher-interceptors-custom, User Guide>> for + details. +* Support for limiting the `max-pool-size-factor` for parallel execution via a + configuration parameter. +* New `testfeed` details mode for `ConsoleLauncher` that prints test execution events as + they occur in a concise format. +* The existing functionality of the `ConsoleLauncher` has been split into two subcommands: + `execute` for executing tests and `engines` for listing registered test engines. +* A new `discover` subcommand has been added to the `ConsoleLauncher` to print the + discovered tests for the specified details mode without executing them. +* Improved error message for cyclic graphs detected during test discovery to be more + actionable. +* Extracted `NamespacedHierarchicalStore` from JUnit Jupiter engine for reuse by other + test engines and their extensions. +* New dry-run mode to simulate test execution without actually running tests. Please refer + to the <<../user-guide/index.adoc#launcher-api-dry-run-mode, User Guide>> for details. +* Stack traces produced by failing tests are now pruned of calls from the `org.junit`, + `jdk.internal.reflect`, and `sun.reflect` packages. This feature can be disabled via a + configuration parameter. Please refer to the + <<../user-guide/index.adoc#stacktrace-pruning, User Guide>> for details. +* New `getAncestors()` method in `TestDescriptor`. + + +[[release-notes-5.10.0-junit-jupiter]] +=== JUnit Jupiter + +==== Bug Fixes + +* The extensions supporting `@MethodSource`, `@EnabledIf`, and `@DisabledIf` now load + classes by fully-qualified class name using the `ClassLoader` obtained from the test + class when possible. This allows classes to be resolved with custom `ClassLoader` + arrangements (such as OSGi). +* When converting an argument for a `@ParameterizedTest` method from a fully-qualified + class name (`String`) to a `Class`, the `ClassLoader` of the class in which the + `@ParameterizedTest` method is declared is now used to resolve the `Class` instead of + the _default_ `ClassLoader`. + +==== Deprecations and Breaking Changes + +* The `dynamic` parallel execution strategy now allows the thread pool to be saturated by + default. +* Implicit type conversion of boolean values like in `@CsvSource` is now stricter, only + allowing values `"true"` or `"false"` (case-insensitive), in order to make accidental + mistakes apparent and to avoid potential confusion. + +==== New Features and Improvements + +* Various "experimental" APIs have been promoted to "stable", including + `MethodOrderer`, `ClassOrderer`, `InvocationInterceptor`, + `LifecycleMethodExecutionExceptionHandler`, `@TempDir`, parallel execution annotations, + and others. +* `JAVA_22` has been added to the `JRE` enum for use with JRE-based execution conditions. +* New `reason` attribute in `@Execution` which can be used to document the reason for + using the selected execution mode. +* New `junit.jupiter.execution.parallel.config.dynamic.max-pool-size-factor` configuration + parameter to set the maximum pool size factor. +* New `junit.jupiter.execution.parallel.config.dynamic.saturate` configuration + parameter to disable pool saturation. +* `@RepeatedTest` can now be configured with a failure threshold which signifies the + number of failures after which remaining repetitions will be automatically skipped. See + the <<../user-guide/index.adoc#writing-tests-repeated-tests, User Guide>> for details. +* If `@MethodSource` is used with a non-static factory method that should be `static`, the + exception thrown now provides the user a meaningful explanation of how to address the + problem. +* `@EmptySource` now supports additional types, including `Collection` and `Map` subtypes + with a public no-arg constructor. +* New `ArgumentsAccessor.getInvocationIndex()` method that supplies the index of a + `@ParameterizedTest` invocation. +* New `AnnotationBasedArgumentsProvider` convenience base class which implements both + `ArgumentsProvider` and `AnnotationConsumer`. +* New `AnnotationBasedArgumentConverter` convenience base class which implements both + `ArgumentConverter` and `AnnotationConsumer`. +* `@TempDir` can now be used as a meta-annotation in order to create custom _composed + annotations_. See the `@JimfsTempDir` example in the + <<../user-guide/index.adoc#writing-tests-built-in-extensions-TempDirectory, User Guide>> + for details. +* `@TempDir` now successfully cleans up files and directories on Windows that are set to + read-only. +* New `TempDirFactory` SPI for customizing how the `@TempDir` extension creates temporary + directories. See the + <<../user-guide/index.adoc#writing-tests-built-in-extensions-TempDirectory, User Guide>> + for details. +* The <<../user-guide/index.adoc#extensions-RandomNumberExtension, User Guide>> now + includes an example implementation of the `RandomNumberExtension` in order to improve + the documentation for extension registration via `@ExtendWith` on fields. +* The scope of applicability for `TestWatcher` implementations is now more extensively + documented in the User Guide and Javadoc. +* `DisplayNameGenerator` methods are now allowed to return `null`, in order to signal to + fall back to the default display name generator. + + +[[release-notes-5.10.0-junit-vintage]] +=== JUnit Vintage + +No changes. diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.6.0.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.6.0.adoc deleted file mode 100644 index 472f6baa708d..000000000000 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.6.0.adoc +++ /dev/null @@ -1,22 +0,0 @@ -[[release-notes-5.6.0]] -== 5.6.0 - -*Date of Release:* January 20, 2020 - -*Scope:* - -* New `@EnabledForJreRange` and `@DisabledForJreRange` execution conditions -* `@Order` allows to specify relative order -* Parameter names are included in default display names of parameterized test invocations -* Improvements to `@CsvSource` and `@CsvFileSource` -* New `TestInstancePreDestroyCallback` extension API -* Performance improvements and bug fixes for the Vintage engine -* Improved error reporting for failures during test discovery and execution -* Support for using `any()` and `none()` in tag expressions -* `org.junit.platform.console` now provides a `java.util.spi.ToolProvider` -* `DiscoverySelectors` for tests in inherited nested classes -* OSGi metadata -* Minor bug fixes and improvements - -For complete details consult the -https://junit.org/junit5/docs/5.6.0/release-notes/index.html[5.6.0 Release Notes] online. diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.6.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.6.1.adoc deleted file mode 100644 index ba7fd3266eb3..000000000000 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.6.1.adoc +++ /dev/null @@ -1,36 +0,0 @@ -[[release-notes-5.6.1]] -== 5.6.1 - -*Date of Release:* March 22, 2020 - -*Scope:* Bug fixes since 5.6.0 - -For a complete list of all _closed_ issues and pull requests for this release, consult the -link:{junit5-repo}+/milestone/47?closed=1+[5.6.1] milestone page in the JUnit repository -on GitHub. - - -[[release-notes-5.6.1-junit-platform]] -=== JUnit Platform - -==== Bug Fixes - -* In order to avoid file locking issues on Microsoft Windows, URLs are no longer cached - when loading `junit-platform.properties` files. -* The presence of multiple `junit-platform.properties` files on the classpath now only - results in a warning if the files have different URLs. - - -[[release-notes-5.6.1-junit-jupiter]] -=== JUnit Jupiter - -==== Bug Fixes - -* `TestInstancePreDestroyCallback` extensions are now invoked in reverse registration - order when `PER_CLASS` test instance lifecycle semantics have been configured. - - -[[release-notes-5.6.1-junit-vintage]] -=== JUnit Vintage - -No changes. diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.6.2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.6.2.adoc deleted file mode 100644 index 0da105d0ef82..000000000000 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.6.2.adoc +++ /dev/null @@ -1,39 +0,0 @@ -[[release-notes-5.6.2]] -== 5.6.2 - -*Date of Release:* April 10, 2020 - -*Scope:* Bug fixes since 5.6.1 - -For a complete list of all _closed_ issues and pull requests for this release, consult the -link:{junit5-repo}+/milestone/48?closed=1+[5.6.2] milestone page in the JUnit repository -on GitHub. - - -[[release-notes-5.6.2-junit-platform]] -=== JUnit Platform - -==== Bug Fixes - -* `ReflectionSupport.findNestedClasses()` no longer detects inner class cycles for classes - that do not match the supplied `Predicate`. For example, JUnit Jupiter no longer throws - an exception if an inner class cycle is detected in a nested class hierarchy whose inner - classes are not annotated with `@Nested`. - - -[[release-notes-5.6.2-junit-jupiter]] -=== JUnit Jupiter - -==== Bug Fixes - -* Test discovery no longer halts with an exception for inner class hierarchies that form a - cycle if such inner classes are not annotated with `@Nested`. - - -[[release-notes-5.6.2-junit-vintage]] -=== JUnit Vintage - -==== Bug Fixes - -* Generating display names from JUnit 4 `Descriptions` now falls back to `getDisplayName` - if `getMethodName` returns a blank `String` instead of throwing an exception. diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.7.0.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.7.0.adoc deleted file mode 100644 index 972739531da1..000000000000 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.7.0.adoc +++ /dev/null @@ -1,210 +0,0 @@ -[[release-notes-5.7.0]] -== 5.7.0 - -*Date of Release:* September 13, 2020 - -*Scope:* - -* Promotion of experimental features in JUnit Platform and Jupiter -* Java Flight Recorder support -* TestKit improvements -* `@Isolated` tests -* Configurable default method orderer -* Custom disabled reasons for all `@Enabled*`/`@Disabled*` annotations -* Improvements to `assertTimeoutPreemptively()` -* Improvements to `@CsvFileSource` and `@CsvSource` - - -For a complete list of all _closed_ issues and pull requests for this release, consult the -link:{junit5-repo}+/milestone/44?closed=1+[5.7 M1], -link:{junit5-repo}+/milestone/49?closed=1+[5.7 RC1], and -link:{junit5-repo}+/milestone/50?closed=1+[5.7 GA] milestone pages in the JUnit repository -on GitHub. - - -[[release-notes-5.7.0-RC1-overall-improvements]] -=== Overall Improvements - -* Javadoc JARs now contain `package-list` in addition to `element-list` for compatibility - with tools like NetBeans 11. - - -[[release-notes-5.7.0-junit-platform]] -=== JUnit Platform - -==== Promoted Features - -The following APIs have been promoted from "experimental" status: - -* `LauncherConstants` is now _stable_ -* `LauncherConfig` (except for methods introduced in 5.7) is now _stable_ -* `LegacyXmlReportGeneratingListener` is now _stable_ -* `org.junit.platform.testkit.engine` is now _maintained_ - -==== Bug Fixes - -* In XML reports generated by the `ConsoleLauncher` or - `LegacyXmlReportGeneratingListener`, characters in exception messages and other - user-supplied values that are not allowed in XML are now replaced with their character - reference – for example, `\0` becomes `�`. -* The `Launcher` now throws an exception when a previously executed `TestPlan` is - attempted to be executed again. - -==== Deprecations and Breaking Changes - -* In the `EngineTestKit` API, the `all()`, `containers()`, and `tests()` methods in - `EngineExecutionResults` that were deprecated in JUnit Platform 1.6.0 have been removed - in favor of `allEvents()`, `containerEvents()`, and `testEvents()`, respectively. -* The following methods in `EngineTestKit` are now deprecated with replacements: - - `execute(String, EngineDiscoveryRequest)` → `execute(String, LauncherDiscoveryRequest)` - - `execute(TestEngine, EngineDiscoveryRequest)` → `execute(TestEngine, LauncherDiscoveryRequest)` - - `Builder.filters(DiscoveryFilter...)` → `Builder.filters(Filter...)` -* `EngineTestKit` no longer takes into account implicit configuration parameters (i.e. - system properties and the `junit-platform.properties` classpath resource) by default. - This change makes tests executed via `EngineTestKit` independent of the environment they - are executed in. - -==== New Features and Improvements - -* The number of containers and tests excluded by post discovery filters based on their tags - is now logged, along with the exclusion reasons. -* New `junit.platform.execution.listeners.deactivate` configuration parameter that allows - one to specify a comma-separated list of patterns for deactivating - `TestExecutionListener` implementations registered via the `ServiceLoader` mechanism. -* The `@Testable` annotation may now be applied _directly_ to fields. -* New `Node.DynamicTestExecutor#execute(TestDescriptor, EngineExecutionListener)` method - for engines that wish to provide a custom `EngineExecutionListener` and cancel or wait - for the execution of a submitted test via the returned `Future`. -* New `EngineExecutionListener.NOOP` `EngineExecutionListener` implementation. -* All declared methods in the `EngineExecutionListener` API now have empty `default` - implementations. -* The `EngineTestKit` now reuses the same test discovery and execution logic as the - `Launcher`. Thus, it's now possible to test an engine's behavior in the presence of - post-discovery filters (e.g. tag filters) and with regard to pruning. -* The `EngineTestKit` now supports matching conditions with events loosely, i.e. an - incomplete match with or without a fixed order. -* The `@Testable` annotation may now target any element type, including fields, methods, - classes, packages, and modules. -* When using the `ConsoleLauncher`, classes selected explicitly via `--select-class` and - `--select-method` are now always executed regardless of class name patterns provided - via `--include-classname` or the default class name pattern. -* The `ConsoleLauncher` now honors the `--disable-ansi-colors` option when printing usage - help. -* New `FilePosition` support in `FileSelector` and `ClasspathResourceSelector`. -* New `getJavaClass()` and `getJavaMethod()` methods in - `org.junit.platform.engine.support.descriptor.MethodSource`. -* Custom `PostDiscoveryFilter` implementations can now be registered via Java’s - `ServiceLoader` mechanism. -* New `org.junit.platform.jfr` module. When running on Java 11 or later, it provides and - registers a `TestExecutionListener` that generates Java Flight Recorder events. -* The `ExecutionRecorder` in `junit-platform-testkit` is now public for fine-grained - testing of classes that use `EngineExecutionListener`. -* `ForkJoinPoolHierarchicalTestExecutorService` can now be constructed by supplying a - `ParallelExecutionConfiguration`. -* `HierarchicalTestEngine` now supports a global resource lock. - - -[[release-notes-5.7.0-junit-jupiter]] -=== JUnit Jupiter - -==== Promoted Features - -The following APIs have been promoted from _experimental_ to _stable_: - -* `junit-jupiter-migrationsupport` -* `junit-jupiter-params` (i.e. `@ParameterizedTest` etc.) -* `@TestMethodOrder`, `MethodOrderer`, and its pre-5.7 implementations -* `@DisplayNameGeneration`, `DisplayNameGenerator`, and its pre-5.7 implementations -* `@Timeout` -* `TestInstanceFactory` -* `TestInstancePreDestroyCallback` -* `TestInstances` and corresponding `ExtensionContext` methods -* `TestWatcher` -* Kotlin-specific assertions that were introduced in 5.1 - -==== Bug Fixes - -* `@TempDir` is now able to clean up files in read-only directories. -* The Jupiter engine now ignores `MethodSelectors` for methods in non-Jupiter test - classes instead of failing for missing methods in such cases. -* `CloseableResource` instances stored in `ExtensionContext.Store` are now closed in the - reverse order they were added in. Previously, the order was undefined and unstable. -* Inherited `@BeforeEach` methods are now executed on correct instances for `@Nested` - classes. -* Registered `TestInstancePreDestroyCallback` extensions are now always called if an - instance of a test class was created, regardless whether any registered - `TestInstancePostProcessor` extension threw an exception. -* Disabled `@TestTemplate` methods (e.g. `@ParameterizedTest` and `@RepeatedTest` methods) - are now reported to registered `TestWatcher` extensions. - -==== Deprecations and Breaking Changes - -* `MethodOrderer.Alphanumeric` has been deprecated in favor of `MethodOrderer.MethodName` - which provides the exact same functionality but has a more descriptive name. - -==== New Features and Improvements - -* New `@EnabledIf` and `@DisabledIf` annotations can be used to enable or disable a test - or container based on condition methods. -* New `MethodOrderer` named `DisplayName` that sorts test methods alphanumerically based - on their display names. -* New `DisplayNameGenerator` named `Simple` (based on `Standard`) that removes trailing - parentheses for methods with no parameters. -* `assertThrows()` for Kotlin can now be used with suspending functions and other lambda - contexts that require inlining. -* The `JRE` enum now provides a static `currentVersion()` method that returns the enum - constant for the currently executing JRE, e.g. for use in custom execution conditions - and other extensions. -* The `name` attribute of `@ParameterizedTest` is now clearly documented to be a - `MessageFormat` pattern. -* Synthetic constructors are now ignored when instantiating a test class. -* The Javadoc for the `provideTestTemplateInvocationContexts()` method in - `TestTemplateInvocationContextProvider` has been aligned with the actual implementation. - Providers are now officially allowed to return an empty stream, and the error message - when all provided streams are empty is now more helpful. -* New `getDisplayName()` method in `MethodDescriptor` for use in `MethodOrderer` - implementations. -* New `assertLinesMatch()` method overloads in `Assertions` that accept two - `Stream` instances for comparison. -* `assertTimeoutPreemptively()` in `Assertions` now reports the stack trace of the timed - out thread in the cause of the `AssertionFailedError`. -* `assertTimeoutPreemptively()` now uses threads with a specific name, conveying their use - by the framework, to facilitate debugging and stack trace analysis. -* All `@Enabled*`/`@Disabled*` annotations now have an optional `disabledReason` attribute - that can be used to provide an additional explanation as to why a test or container - might be disabled. -* `JAVA_16` has been added to the `JRE` enum for use with JRE-based execution conditions. -* New `MethodOrderer.MethodName` to replace `MethodOrderer.Alphanumeric` with the exact - same functionality but a more descriptive name. -* New `junit.jupiter.testmethod.order.default` configuration parameter to set the default - `MethodOrderer` that will be used unless `@TestMethodOrder` is present. -* New `DynamicTest.stream()` factory method that accepts a `Stream` instead of an - `Iterator` for the input source. -* `@CsvFileSource` now allows one to specify file paths as an alternative to classpath - resources. -* `@CsvFileSource` and `@CsvSource` now provide a `maxCharsPerColumn` attribute for - configuring the maximum number of characters per column. -* Arguments in display names of parameterized test invocations are now truncated if they - exceed a configurable maximum length (defaults to 512 characters). -* New `@Isolated` annotation allows to run test classes in isolation of other test classes - when using parallel test execution. -* New `TypedArgumentConverter` for converting one specific type to another, therefore - reducing boilerplate type checks compared to implementing `ArgumentConverter` directly. -* New `ExtensionContext.getConfigurationParameter(String, Function)` - convenience method for reading transformed configuration parameters from extensions. - - -[[release-notes-5.7.0-junit-vintage]] -=== JUnit Vintage - -==== Bug Fixes - -* The Vintage engine no longer fails when resolving a `MethodSelector` for methods of test - classes that cannot be found via reflection. This allows selecting Spock feature methods - by their source code name even though they have a generated method name in the bytecode. - -==== New Features and Improvements - -* The internal `JUnit4VersionCheck` class -- which verifies that a supported version of - JUnit 4 is on the classpath -- now implements a lenient version ID parsing algorithm in - order to support custom version ID formats such as `4.12.0`, `4.12-patch_1`, etc. diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.0.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.0.adoc new file mode 100644 index 000000000000..e5ee0004d778 --- /dev/null +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.0.adoc @@ -0,0 +1,21 @@ +[[release-notes-5.9.0]] +== 5.9.0 + +*Date of Release:* July 26, 2022 + +*Scope:* + +* XML reports in the new https://github.com/ota4j-team/open-test-reporting[Open Test Reporting] + format +* Configurable cleanup mode for `@TempDir` +* Configurable thread mode for `@Timeout` +* Conditional execution based on OS architectures +* New `TestInstancePreConstructCallback` extension API +* Reusable parameter resolution for custom extension methods via `ExecutableInvoker` +* Parameter injection for `@MethodSource` methods +* New `IterationSelector` +* Various improvements to `ConsoleLauncher` +* Numerous bug fixes and minor improvements + +For complete details consult the +https://junit.org/junit5/docs/5.9.0/release-notes/index.html[5.9.0 Release Notes] online. diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc new file mode 100644 index 000000000000..3c82815368e1 --- /dev/null +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc @@ -0,0 +1,53 @@ +[[release-notes-5.9.1]] +== 5.9.1 + +*Date of Release:* September 20, 2022 + +*Scope:* + +* New `@EnabledInNativeImage` and `@DisabledInNativeImage` annotations for testing in + GraalVM native images. +* Minor bug fixes and enhancements since 5.9.0 + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit5-repo}+/milestone/63?closed=1+[5.9.1] milestone page in the JUnit repository +on GitHub. + + +[[release-notes-5.9.1-junit-platform]] +=== JUnit Platform + +==== Bug Fixes + +* `ReflectionSupport.findMethods(...)` now returns a distinct set of methods. +* Execution in GraalVM native images no longer requires `--initialize-at-build-time` for + `OpenTestReportGeneratingListener`. + + +[[release-notes-5.9.1-junit-jupiter]] +=== JUnit Jupiter + +==== Bug Fixes + +* Headers provided via the `value` attribute in `@CsvSource` for a `@ParameterizedTest` + are now properly parsed when the `useHeadersInDisplayName` attribute is set to `true`. +* A `@ParameterizedTest` method configured with a `@MethodSource` annotation that + references a factory method inherited from multiple interfaces no longer fails with an + exception stating that multiple factory methods with the same name were found. +* A `@ParameterizedTest` method configured with a `@MethodSource` annotation that + references a factory method whose name is the same as other non-factory methods in the + same class no longer fails with an exception stating that multiple factory methods with + the same name were found. +* Assertion failures thrown from methods with applied timeouts using `ThreadMode.SEPARATE` + are now properly reported. + +==== New Features and Improvements + +* New `@EnabledInNativeImage` and `@DisabledInNativeImage` annotations for enabling and + disabling tests within a GraalVM native image. + + +[[release-notes-5.9.1-junit-vintage]] +=== JUnit Vintage + +No changes. diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.2.adoc new file mode 100644 index 000000000000..249e805dd0f0 --- /dev/null +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.2.adoc @@ -0,0 +1,61 @@ +[[release-notes-5.9.2]] +== 5.9.2 + +*Date of Release:* January 10, 2023 + +*Scope:* Bug fixes and enhancements since 5.9.1 + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit5-repo}+/milestones/5.9.2+[5.9.2] milestone page in the JUnit repository on +GitHub. + + +[[release-notes-5.9.2-junit-platform]] +=== JUnit Platform + +==== Bug Fixes + +* The Java 7 based constructor for `ForkJoinPool` is no longer accidentally used on Java 9 + or higher when invalid `ParallelExecutionConfiguration` is provided. Instead, an + exception is thrown for invalid configuration, thereby preventing invalid configuration + from being silently ignored. + +==== New Features and Improvements + +* New `TestPlan.getTestIdentifier(UniqueId)` and `TestPlan.getChildren(UniqueId)` methods + to avoid parsing unique IDs unnecessarily during test execution. +* Support for limiting the `max-pool-size` for parallel execution via a configuration + parameter. +* Suite discovery now ignores cycles encountered in a test suite and logs an informational + message at `CONFIG` level instead of throwing an exception. + + +[[release-notes-5.9.2-junit-jupiter]] +=== JUnit Jupiter + +==== Bug Fixes + +* New `@MethodSource` syntax for explicitly selecting an overloaded local factory method + without specifying its fully qualified name. + +==== Deprecations and Breaking Changes + +* The `fixed` parallel execution strategy now allows the thread pool to be saturated by + default. + +==== New Features and Improvements + +* `JAVA_21` has been added to the `JRE` enum for use with JRE-based execution conditions. +* New `junit.jupiter.execution.parallel.config.fixed.max-pool-size` configuration + parameter to set the maximum pool size. +* New `junit.jupiter.execution.parallel.config.fixed.saturate` configuration parameter to + disable pool saturation. + + +[[release-notes-5.9.2-junit-vintage]] +=== JUnit Vintage + +==== Bug Fixes + +* `Parameterized` tests are now properly reported when used in combination with the + `Enclosed` runner. diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.3.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.3.adoc new file mode 100644 index 000000000000..6d1020227e39 --- /dev/null +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.3.adoc @@ -0,0 +1,59 @@ +[[release-notes-5.9.3]] +== 5.9.3 + +*Date of Release:* April 26, 2023 + +*Scope:* Bug fixes and enhancements since 5.9.2 + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit5-repo}+/milestone/67?closed=1+[5.9.3] milestone page in the +JUnit repository on GitHub. + + +[[release-notes-5.9.3-junit-platform]] +=== JUnit Platform + +No changes. + + +[[release-notes-5.9.3-junit-jupiter]] +=== JUnit Jupiter + +==== Bug Fixes + +* Parameter types for _local_ `@MethodSource` factory method names are now validated. For + example, `@MethodSource("myFactory(example.NonexistentType)")` will now result in an + exception stating that `example.NonexistentType` cannot be resolved to a valid type. +* The syntax for parameter types in _local_ `@MethodSource` factory method names now + supports canonical array names -- for example, you may now specify `int[]` as in + `@MethodSource("myFactory(int[])")` instead of the _binary_ name `[I` as in + `@MethodSource("myFactory([I)")` (which was already supported) and + `@MethodSource("myFactory(java.lang.String[])")` instead of + `@MethodSource("myFactory([Ljava.lang.String;)")`. +* The search algorithm used to find `@MethodSource` factory methods now applies consistent + semantics for _local_ qualified method names and fully-qualified method names for + overloaded factory methods. +* The `+{displayName}+` placeholder for `@ParameterizedTest` invocation names is no longer + parsed using `java.text.MessageFormat`. Consequently, any text in the display name of + the `@ParameterizedTest` method will be included unmodified in the invocation display + name. For example, Kotlin method names and custom display names configured via + `@DisplayName` can now contain apostrophes (`'`) as well as text resembling + `MessageFormat` elements such as `+{0}+` or `+{data}+`. +* Exceptions thrown for files that cannot be deleted when cleaning up a temporary + directory created via `@TempDir` now include the root cause. +* Lifecycle methods are allowed to be declared as `private` again for backwards + compatibility; however, using `private` visibility for lifecycle methods is strongly + discouraged and will be disallowed in a future release. + +==== New Features and Improvements + +* The search algorithm used to find `@MethodSource` factory methods now falls back to + lenient search semantics when a factory method cannot be found by qualified name + (without a parameter list) and also provides better diagnostics when a unique factory + method cannot be found. + + +[[release-notes-5.9.3-junit-vintage]] +=== JUnit Vintage + +No changes. diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-TEMPLATE.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-TEMPLATE.adoc index 2952e59175fe..ef56e8c27ff9 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-TEMPLATE.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-TEMPLATE.adoc @@ -1,18 +1,31 @@ -// TODO: replace all occurrences of ⚠️ with appropriate values, delete this comment, and -// 'include:' this new file in index.adoc. -[[release-notes-⚠️]] -== ⚠️ +// TODO: +// +// 1) Make a copy of this template file, replacing TEMPLATE in the file name with the +// current version (for example, 5.10.0-M1, 5.10.0-RC1, 5.10.0). +// 2) Open the new file for editing. +// 3) Replace all occurrences of VERSION with the current version (for example, 5.10.0-M1, +// 5.10.0-RC1, 5.10.0). The same version must be used in the file name and within the +// file. +// 4) Replace MILESTONE_NUMBER with the appropriate milestone number. This is an integer +// which you can determine via https://github.com/junit-team/junit5/milestones/. If a +// GitHub milestone does not yet exist for the given VERSION, you will need to create +// a new GitHub milestone or ask a member of the JUnit team to create it for you. +// 5) 'include:' this new file in index.adoc. +// 6) Delete this entire comment block. +// +[[release-notes-VERSION]] +== VERSION *Date of Release:* ❓ *Scope:* ❓ -For a complete list of all _closed_ issues and pull requests for this release, consult -the link:{junit5-repo}+/milestone/⚠️?closed=1+[⚠️] milestone page in the JUnit repository -on GitHub. +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit5-repo}+/milestone/MILESTONE_NUMBER?closed=1+[VERSION] milestone page in the +JUnit repository on GitHub. -[[release-notes-⚠️-junit-platform]] +[[release-notes-VERSION-junit-platform]] === JUnit Platform ==== Bug Fixes @@ -28,7 +41,7 @@ on GitHub. * ❓ -[[release-notes-⚠️-junit-jupiter]] +[[release-notes-VERSION-junit-jupiter]] === JUnit Jupiter ==== Bug Fixes @@ -44,7 +57,7 @@ on GitHub. * ❓ -[[release-notes-⚠️-junit-vintage]] += [[release-notes-VERSION-junit-vintage]] === JUnit Vintage ==== Bug Fixes diff --git a/documentation/src/docs/asciidoc/resources/themes/rouge_junit.rb b/documentation/src/docs/asciidoc/resources/themes/rouge_junit.rb new file mode 100644 index 000000000000..003a88b51955 --- /dev/null +++ b/documentation/src/docs/asciidoc/resources/themes/rouge_junit.rb @@ -0,0 +1,148 @@ +require 'rouge' unless defined? ::Rouge.version + +module Rouge; module Themes + + class JUnit < CSSTheme + name 'junit' + + # This is an extension of the official "github" theme to remove accessibility issues in code blocks + # Primer primitives (https://github.com/primer/primitives/tree/main/src/tokens) + P_RED_0 = {:light => '#ffebe9', :dark => '#ffdcd7'} + P_RED_3 = {:dark => '#ff7b72'} + P_RED_5 = {:light => '#cf222e'} + P_RED_7 = {:light => '#82071e', :dark => '#8e1519'} + P_RED_8 = {:dark => '#67060c'} + P_ORANGE_2 = {:dark => '#ffa657'} + P_ORANGE_6 = {:light => '#953800'} + P_GREEN_0 = {:light => '#dafbe1', :dark => '#aff5b4'} + P_GREEN_1 = {:dark => '#7ee787'} + P_GREEN_6 = {:light => '#116329'} + P_GREEN_8 = {:dark => '#033a16'} + P_BLUE_1 = {:dark => '#a5d6ff'} + P_BLUE_2 = {:dark => '#79c0ff'} + P_BLUE_5 = {:dark => '#1f6feb'} + P_BLUE_6 = {:light => '#0550ae'} + P_BLUE_7 = {:light => '#055099'} + P_BLUE_8 = {:light => '#0a3069'} + P_PURPLE_2 = {:dark => '#d2a8ff'} + P_PURPLE_5 = {:light => '#8250df'} + P_GRAY_0 = {:light => '#f6f8fa', :dark => '#f0f6fc'} + P_GRAY_1 = {:dark => '#c9d1d9'} + P_GRAY_3 = {:dark => '#8b949e'} + P_GRAY_5 = {:light => '#34383d'} # '#6e7781' + P_GRAY_8 = {:dark => '#161b22'} + P_GRAY_9 = {:light => '#24292f'} + + extend HasModes + + def self.light! + mode :dark # indicate that there is a dark variant + mode! :light + end + + def self.dark! + mode :light # indicate that there is a light variant + mode! :dark + end + + def self.make_dark! + palette :comment => P_GRAY_3[@mode] + palette :constant => P_BLUE_2[@mode] + palette :entity => P_PURPLE_2[@mode] + palette :heading => P_BLUE_5[@mode] + palette :keyword => P_RED_3[@mode] + palette :string => P_BLUE_1[@mode] + palette :tag => P_GREEN_1[@mode] + palette :variable => P_ORANGE_2[@mode] + + palette :fgDefault => P_GRAY_1[@mode] + palette :bgDefault => P_GRAY_8[@mode] + + palette :fgInserted => P_GREEN_0[@mode] + palette :bgInserted => P_GREEN_8[@mode] + + palette :fgDeleted => P_RED_0[@mode] + palette :bgDeleted => P_RED_8[@mode] + + palette :fgError => P_GRAY_0[@mode] + palette :bgError => P_RED_7[@mode] + end + + def self.make_light! + palette :comment => P_GREEN_6[@mode] + palette :constant => P_BLUE_6[@mode] + palette :entity => P_PURPLE_5[@mode] + palette :heading => P_BLUE_6[@mode] + palette :keyword => P_RED_5[@mode] + palette :string => P_BLUE_8[@mode] + palette :tag => P_BLUE_7[@mode] + palette :variable => P_ORANGE_6[@mode] + + palette :fgDefault => P_GRAY_9[@mode] + palette :bgDefault => P_GRAY_0[@mode] + + palette :fgInserted => P_GREEN_6[@mode] + palette :bgInserted => P_GREEN_0[@mode] + + palette :fgDeleted => P_RED_7[@mode] + palette :bgDeleted => P_RED_0[@mode] + + palette :fgError => P_GRAY_0[@mode] + palette :bgError => P_RED_7[@mode] + end + + light! + + style Text, :fg => :fgDefault, :bg => :bgDefault + + style Keyword, :fg => :keyword + + style Generic::Error, :fg => :fgError + + style Generic::Deleted, :fg => :fgDeleted, :bg => :bgDeleted + + style Name::Builtin, + Name::Class, + Name::Constant, + Name::Namespace, :fg => :variable + + style Literal::String::Regex, + Name::Attribute, + Name::Tag, :fg => :tag + + style Generic::Inserted, :fg => :fgInserted, :bg => :bgInserted + + style Keyword::Constant, + Literal, + Literal::String::Backtick, + Name::Builtin::Pseudo, + Name::Exception, + Name::Label, + Name::Property, + Name::Variable, + Operator, :fg => :constant + + style Generic::Heading, + Generic::Subheading, :fg => :heading, :bold => true + + style Literal::String, :fg => :string + + style Name::Decorator, + Name::Function, :fg => :entity + + style Error, :fg => :fgError, :bg => :bgError + + style Comment, + Generic::Lineno, + Generic::Traceback, :fg => :comment + + style Name::Entity, + Literal::String::Interpol, :fg => :fgDefault + + style Generic::Emph, :fg => :fgDefault, :italic => true + + style Generic::Strong, :fg => :fgDefault, :bold => true + + end +end; end + diff --git a/documentation/src/docs/asciidoc/user-guide/advanced-topics.adoc b/documentation/src/docs/asciidoc/user-guide/advanced-topics.adoc index 53164790e5b2..ab7f4d11463a 100644 --- a/documentation/src/docs/asciidoc/user-guide/advanced-topics.adoc +++ b/documentation/src/docs/asciidoc/user-guide/advanced-topics.adoc @@ -1,10 +1,12 @@ [[advanced-topics]] == Advanced Topics -include::launcher-api.adoc[] +include::advanced-topics/junit-platform-reporting.adoc[] -include::testkit.adoc[] +include::advanced-topics/junit-platform-suite-engine.adoc[] -//// -include::engines.adoc[] -//// +include::advanced-topics/testkit.adoc[] + +include::advanced-topics/launcher-api.adoc[] + +include::advanced-topics/engines.adoc[] diff --git a/documentation/src/docs/asciidoc/user-guide/advanced-topics/engines.adoc b/documentation/src/docs/asciidoc/user-guide/advanced-topics/engines.adoc new file mode 100644 index 000000000000..460a6fd4ba3c --- /dev/null +++ b/documentation/src/docs/asciidoc/user-guide/advanced-topics/engines.adoc @@ -0,0 +1,122 @@ +[[test-engines]] +=== Test Engines + +A `TestEngine` facilitates _discovery_ and _execution_ of tests for a particular +programming model. + +For example, JUnit provides a `TestEngine` that discovers and executes tests written using +the JUnit Jupiter programming model (see <> and <>). + +[[test-engines-junit]] +==== JUnit Test Engines + +JUnit provides three `TestEngine` implementations. + +* `{junit-jupiter-engine}`: The core of JUnit Jupiter. +* `{junit-vintage-engine}`: A thin layer on top of JUnit 4 to allow running _vintage_ + tests (based on JUnit 3.8 and JUnit 4) with the JUnit Platform launcher infrastructure. +* `{junit-platform-suite-engine}`: Executes declarative suites of tests with the JUnit + Platform launcher infrastructure. + +[[test-engines-custom]] +==== Custom Test Engines + +You can contribute your own custom `{TestEngine}` by implementing the interfaces in the +{junit-platform-engine} module and _registering_ your engine. + +Every `TestEngine` must provide its own _unique ID_, _discover_ tests from an +`EngineDiscoveryRequest`, and _execute_ those tests according to an `ExecutionRequest`. + +[WARNING] +.The `junit-` unique ID prefix is reserved for TestEngines from the JUnit Team +==== +The JUnit Platform `Launcher` enforces that only `TestEngine` implementations published +by the JUnit Team may use the `junit-` prefix for their `TestEngine` IDs. + +* If any third-party `TestEngine` claims to be `junit-jupiter` or `junit-vintage`, an + exception will be thrown, immediately halting execution of the JUnit Platform. +* If any third-party `TestEngine` uses the `junit-` prefix for its ID, a warning message + will be logged. Later releases of the JUnit Platform will throw an exception for such + violations. +==== + +In order to facilitate test discovery within IDEs and tools prior to launching the JUnit +Platform, `TestEngine` implementations are encouraged to make use of the `@Testable` +annotation. For example, the `@Test` and `@TestFactory` annotations in JUnit Jupiter are +meta-annotated with `@Testable`. Consult the Javadoc for `{Testable}` for further details. + +If your custom `TestEngine` needs to be configured, consider allowing users to supply +configuration via <>. Please note, +however, that you are strongly encouraged to use a unique prefix for all configuration +parameters supported by your test engine. Doing so will ensure that there are no conflicts +between the names of your configuration parameters and those from other test engines. In +addition, since configuration parameters may be supplied as JVM system properties, it is +wise to avoid conflicts with the names of other system properties. For example, JUnit +Jupiter uses `junit.jupiter.` as a prefix of all of its supported configuration +parameters. Furthermore, as with the warning above regarding the `junit-` prefix for +`TestEngine` IDs, you should not use `junit.` as a prefix for the names of your own +configuration parameters. + +Although there is currently no official guide on how to implement a custom `TestEngine`, +you can consult the implementation of <> or the implementation of +third-party test engines listed in the +https://github.com/junit-team/junit5/wiki/Third-party-Extensions#junit-platform-test-engines[JUnit 5 wiki]. +You will also find various tutorials and blogs on the Internet that demonstrate how to +write a custom `TestEngine`. + +NOTE: `{HierarchicalTestEngine}` is a convenient abstract base implementation of the +`TestEngine` SPI (used by the `{junit-jupiter-engine}`) that only requires implementors to +provide the logic for test discovery. It implements execution of `TestDescriptors` that +implement the `Node` interface, including support for parallel execution. + +[[test-engines-registration]] +==== Registering a TestEngine + +`TestEngine` registration is supported via Java's `{ServiceLoader}` mechanism. + +For example, the `junit-jupiter-engine` module registers its +`org.junit.jupiter.engine.JupiterTestEngine` in a file named +`org.junit.platform.engine.TestEngine` within the `/META-INF/services` folder in the +`junit-jupiter-engine` JAR. + +[[test-engines-requirements]] +==== Requirements + +NOTE: The words "must", "must not", "required", "shall", "shall not", "should", "should +not", "recommended", "may", and "optional" in this section are to be interpreted as +described in https://www.ietf.org/rfc/rfc2119.txt[RFC 2119.] + +[[test-engines-requirements-mandatory]] +===== Mandatory requirements + +For interoperability with build tools and IDEs, `TestEngine` implementations must adhere +to the following requirements: + +* The `TestDescriptor` returned from `TestEngine.discover()` _must_ be the root of a tree + of `TestDescriptor` instances. This implies that there _must not_ be any cycles between + a node and its descendants. +* A `TestEngine` _must_ be able to discover `UniqueIdSelectors` for any unique ID that it + previously generated and returned from `TestEngine.discover()`. This enables selecting a + subset of tests to execute or rerun. +* The `executionSkipped`, `executionStarted`, and `executionFinished` methods of the + `EngineExecutionListener` passed to `TestEngine.execute()` _must_ be called for every + `TestDescriptor` node in the tree returned from `TestEngine.discover()` at most + once. Parent nodes _must_ be reported as started before their children and as finished + after their children. If a node is reported as skipped, there _must not_ be any events + reported for its descendants. + +[[test-engines-requirements-enhanced-compatibility]] +===== Enhanced compatibility + +Adhering to the following requirements is optional but recommended for enhanced +compatibility with build tools and IDEs: + +* Unless to indicate an empty discovery result, the `TestDescriptor` returned from + `TestEngine.discover()` _should_ have children rather than being completely dynamic. + This allows tools to display the structure of the tests and to select a subset of tests + to execute. +* When resolving `UniqueIdSelectors`, a `TestEngine` _should_ only return `TestDescriptor` + instances with matching unique IDs including their ancestors but _may_ return additional + siblings or other nodes that are required for the execution of the selected tests. +* `TestEngines` _should_ support <> tests and containers so + that tag filters can be applied when discovering tests. 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 new file mode 100644 index 000000000000..655d6bc7903b --- /dev/null +++ b/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-reporting.adoc @@ -0,0 +1,138 @@ +[[junit-platform-reporting]] +=== JUnit Platform Reporting + +The `junit-platform-reporting` artifact contains `{TestExecutionListener}` implementations +that generate XML test reports in two flavors: +<> 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 + +`{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. + +[[junit-platform-reporting-open-test-reporting]] +==== Open Test Reporting XML format + +`{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. + +TIP: The {OpenTestReportingCliTool} can be used to convert from the event-based format to +the hierarchical format which is more human-readable. + +===== Gradle + +For Gradle, writing Open Test Reporting compatible XML reports can be enabled and +configured via system properties. The following samples configure its output directory to +be the same directory Gradle uses for its own XML reports. A `CommandLineArgumentProvider` +is used to keep the tasks relocatable across different machines which is important when +using Gradle's Build Cache. + +[source,groovy,indent=0] +[subs=attributes+] +.Groovy DSL +---- +dependencies { + testRuntimeOnly("org.junit.platform:junit-platform-reporting:{platform-version}") +} +tasks.withType(Test).configureEach { + def outputDir = reports.junitXml.outputLocation + jvmArgumentProviders << ({ + [ + "-Djunit.platform.reporting.open.xml.enabled=true", + "-Djunit.platform.reporting.output.dir=${outputDir.get().asFile.absolutePath}" + ] + } as CommandLineArgumentProvider) +} +---- + +[source,kotlin,indent=0] +[subs=attributes+] +.Kotlin DSL +---- +dependencies { + testRuntimeOnly("org.junit.platform:junit-platform-reporting:{platform-version}") +} +tasks.withType().configureEach { + val outputDir = reports.junitXml.outputLocation + jvmArgumentProviders += CommandLineArgumentProvider { + listOf( + "-Djunit.platform.reporting.open.xml.enabled=true", + "-Djunit.platform.reporting.output.dir=${outputDir.get().asFile.absolutePath}" + ) + } +} +---- + +===== Maven + +For Maven Surefire/Failsafe, you can enable Open Test Reporting output and configure the +resulting XML files to be written to the same directory Surefire/Failsafe uses for its own +XML reports as follows: + +[source,xml,indent=0] +[subs=attributes+] +---- + + + + + org.junit.platform + junit-platform-reporting + {platform-version} + test + + + + + + maven-surefire-plugin + {surefire-version} + + + + junit.platform.reporting.open.xml.enabled = true + junit.platform.reporting.output.dir = target/surefire-reports + + + + + + + + +---- + +===== Console Launcher + +When using the <>, you can enable Open Test Reporting +output by setting the configuration parameters via `--config`: + +[source,console,subs=attributes+] +---- +$ java -jar junit-platform-console-standalone-{platform-version}.jar \ + --config=junit.platform.reporting.open.xml.enabled=true \ + --config=junit.platform.reporting.output.dir=reports +---- 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 new file mode 100644 index 000000000000..d38a312d799f --- /dev/null +++ b/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-suite-engine.adoc @@ -0,0 +1,50 @@ +[[junit-platform-suite-engine]] +=== JUnit Platform Suite Engine + +The JUnit Platform supports the declarative definition and execution of suites of tests +from _any_ test engine using the JUnit Platform. + +[[junit-platform-suite-engine-setup]] +==== Setup + +In addition to the `junit-platform-suite-api` and `junit-platform-suite-engine` artifacts, +you need _at least one_ other test engine and its dependencies on the classpath. See +<> for details regarding group IDs, artifact IDs, and versions. + +[[junit-platform-suite-engine-setup-required-dependencies]] +===== Required Dependencies + +* `junit-platform-suite-api` in _test_ scope: artifact containing annotations needed to + configure a test suite +* `junit-platform-suite-engine` in _test runtime_ scope: implementation of the + `TestEngine` API for declarative test suites + +NOTE: Both of the required dependencies are aggregated in the `junit-platform-suite` +artifact which can be declared in _test_ scope instead of declaring explicit dependencies +on `junit-platform-suite-api` and `junit-platform-suite-engine`. + +[[junit-platform-suite-engine-setup-transitive-dependencies]] +===== Transitive Dependencies + +* `junit-platform-suite-commons` in _test_ scope +* `junit-platform-launcher` in _test_ scope +* `junit-platform-engine` in _test_ scope +* `junit-platform-commons` in _test_ scope +* `opentest4j` in _test_ scope + +[[junit-platform-suite-engine-example]] +==== @Suite Example + +By annotating a class with `@Suite` it is marked as a test suite on the JUnit Platform. +As seen in the following example, selector and filter annotations can then be used to +control the contents of the suite. + +[source,java,indent=0] +---- +include::{testDir}/example/SuiteDemo.java[tags=user_guide] +---- + +.Additional Configuration Options +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. 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 new file mode 100644 index 000000000000..18c0b261e299 --- /dev/null +++ b/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc @@ -0,0 +1,277 @@ +[[launcher-api]] +=== JUnit Platform Launcher API + +One of the prominent goals of JUnit 5 is to make the interface between JUnit and its +programmatic clients – build tools and IDEs – more powerful and stable. The purpose is to +decouple the internals of discovering and executing tests from all the filtering and +configuration that's necessary from the outside. + +JUnit 5 introduces the concept of a `Launcher` that can be used to discover, filter, and +execute tests. Moreover, third party test libraries – like Spock, Cucumber, and FitNesse +– can plug into the JUnit Platform's launching infrastructure by providing a custom +<>. + +The launcher API is in the `{junit-platform-launcher}` module. + +An example consumer of the launcher API is the `{ConsoleLauncher}` in the +`{junit-platform-console}` project. + +[[launcher-api-discovery]] +==== Discovering Tests + +Having _test discovery_ as a dedicated feature of the platform itself frees IDEs and build +tools from most of the difficulties they had to go through to identify test classes and +test methods in previous versions of JUnit. + +Usage Example: + +[source,java,indent=0] +---- +include::{testDir}/example/UsingTheLauncherDemo.java[tags=imports] +---- + +[source,java,indent=0] +---- +include::{testDir}/example/UsingTheLauncherDemo.java[tags=discovery] +---- + +You can select classes, methods, and all classes in a package or even search for all tests +in the class-path or module-path. Discovery takes place across all participating test +engines. + +The resulting `TestPlan` is a hierarchical (and read-only) description of all engines, +classes, and test methods that fit the `LauncherDiscoveryRequest`. The client can +traverse the tree, retrieve details about a node, and get a link to the original source +(like class, method, or file position). Every node in the test plan has a _unique ID_ +that can be used to invoke a particular test or group of tests. + +Clients can register one or more `{LauncherDiscoveryListener}` implementations via the +`{LauncherDiscoveryRequestBuilder}` to gain insight into events that occur during test +discovery. By default, the builder registers an "abort on failure" listener that aborts +test discovery after the first discovery failure is encountered. The default +`LauncherDiscoveryListener` can be changed via the +`junit.platform.discovery.listener.default` <>. + +[[launcher-api-execution]] +==== Executing Tests + +To execute tests, clients can use the same `LauncherDiscoveryRequest` as in the discovery +phase or create a new request. Test progress and reporting can be achieved by registering +one or more `{TestExecutionListener}` implementations with the `Launcher` as in the +following example. + +[source,java,indent=0] +---- +include::{testDir}/example/UsingTheLauncherDemo.java[tags=execution] +---- + +There is no return value for the `execute()` method, but you can use a +`TestExecutionListener` to aggregate the results. For examples see the +`{SummaryGeneratingListener}`, `{LegacyXmlReportGeneratingListener}`, and +`{UniqueIdTrackingListener}`. + +NOTE: All `TestExecutionListener` methods are called sequentially. Methods for start +events are called in registration order while methods for finish events are called in +reverse order. +Test case execution won't start before all `executionStarted` calls have returned. + +[[launcher-api-engines-custom]] +==== Registering a TestEngine + +See the dedicated section on <> for +details. + +[[launcher-api-post-discovery-filters-custom]] +==== Registering a PostDiscoveryFilter + +In addition to specifying post-discovery filters as part of a `{LauncherDiscoveryRequest}` +passed to the `{Launcher}` API, `{PostDiscoveryFilter}` implementations will be discovered +at runtime via Java's `{ServiceLoader}` mechanism and automatically applied by the +`Launcher` in addition to those that are part of the request. + +For example, an `example.CustomTagFilter` class implementing `PostDiscoveryFilter` and +declared within the `/META-INF/services/org.junit.platform.launcher.PostDiscoveryFilter` +file is loaded and applied automatically. + +[[launcher-api-launcher-session-listeners-custom]] +==== Registering a LauncherSessionListener + +Registered implementations of `{LauncherSessionListener}` are notified when a +`{LauncherSession}` is opened (before a `{Launcher}` first discovers and executes tests) +and closed (when no more tests will be discovered or executed). They can be registered +programmatically via the `{LauncherConfig}` that is passed to the `{LauncherFactory}`, or +they can be discovered at runtime via Java's `{ServiceLoader}` mechanism and automatically +registered with `LauncherSession` (unless automatic registration is disabled.) + +[[launcher-api-launcher-session-listeners-tool-support]] +===== Tool Support + +The following build tools and IDEs are known to provide full support for `LauncherSession`: + +* Gradle 4.6 and later +* Maven Surefire/Failsafe 3.0.0-M6 and later +* IntelliJ IDEA 2017.3 and later + +Other tools might also work but have not been tested explicitly. + +[[launcher-api-launcher-session-listeners-tool-example-usage]] +===== Example Usage + +A `LauncherSessionListener` is well suited for implementing once-per-JVM setup/teardown +behavior since it's called before the first and after the last test in a launcher session, +respectively. The scope of a launcher session depends on the used IDE or build tool but +usually corresponds to the lifecycle of the test JVM. A custom listener that starts an +HTTP server before executing the first test and stops it after the last test has been +executed, could look like this: + +[source,java] +.src/test/java/example/session/GlobalSetupTeardownListener.java +---- +package example.session; + +include::{testDir}/example/session/GlobalSetupTeardownListener.java[tags=user_guide] +---- +<1> Start the HTTP server +<2> Export its host address as a system property for consumption by tests +<3> Export its port as a system property for consumption by tests +<4> Stop the HTTP server + +This sample uses the HTTP server implementation from the jdk.httpserver module that comes +with the JDK but would work similarly with any other server or resource. In order for the +listener to be picked up by JUnit Platform, you need to register it as a service by adding +a resource file with the following name and contents to your test runtime classpath (e.g. +by adding the file to `src/test/resources`): + +[source] +.src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener +---- +include::{testResourcesDir}/META-INF/services/org.junit.platform.launcher.LauncherSessionListener[] +---- + +You can now use the resource from your test: + +[source,java] +.src/test/java/example/session/HttpTests.java +---- +package example.session; + +include::{testDir}/example/session/HttpTests.java[tags=user_guide] +---- +<1> Read the host address of the server from the system property set by the listener +<2> Read the port of the server from the system property set by the listener +<3> Send a request to the server +<4> Check the status code of the response + +[[launcher-api-launcher-interceptors-custom]] +==== Registering a LauncherInterceptor + +In order to intercept the creation of instances of `{Launcher}` and +`{LauncherSessionListener}` and calls to the `discover` and `execute` methods of the +former, clients can register custom implementations of `{LauncherInterceptor}` via Java's +`{ServiceLoader}` mechanism by additionally setting the +`junit.platform.launcher.interceptors.enabled` <> to `true`. + +A typical use case is to create a custom replace the `ClassLoader` used by the JUnit +Platform to load test classes and engine implementations. + +[source,java] +---- +include::{testDir}/example/CustomLauncherInterceptor.java[tags=user_guide] +---- + +[[launcher-api-launcher-discovery-listeners-custom]] +==== Registering a LauncherDiscoveryListener + +In addition to specifying discovery listeners as part of a `{LauncherDiscoveryRequest}` or +registering them programmatically via the `{Launcher}` API, custom +`LauncherDiscoveryListener` implementations can be discovered at runtime via Java's +`{ServiceLoader}` mechanism and automatically registered with the `Launcher` created via +the `{LauncherFactory}`. + +For example, an `example.CustomLauncherDiscoveryListener` class implementing +`LauncherDiscoveryListener` and declared within the +`/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener` file is loaded +and registered automatically. + +[[launcher-api-listeners-custom]] +==== Registering a TestExecutionListener + +In addition to the public `{Launcher}` API method for registering test execution listeners +programmatically, custom `{TestExecutionListener}` implementations will be discovered at +runtime via Java's `{ServiceLoader}` mechanism and automatically registered with the +`Launcher` created via the `{LauncherFactory}`. + +For example, an `example.CustomTestExecutionListener` class implementing +`TestExecutionListener` and declared within the +`/META-INF/services/org.junit.platform.launcher.TestExecutionListener` file is loaded and +registered automatically. + +[[launcher-api-listeners-config]] +==== Configuring a TestExecutionListener + +When a `{TestExecutionListener}` is registered programmatically via the `{Launcher}` API, +the listener may provide programmatic ways for it to be configured -- for example, via its +constructor, setter methods, etc. However, when a `TestExecutionListener` is registered +automatically via Java's `ServiceLoader` mechanism (see +<>), there is no way for the user to directly configure the +listener. In such cases, the author of a `TestExecutionListener` may choose to make the +listener configurable via <>. The +listener can then access the configuration parameters via the `TestPlan` supplied to the +`testPlanExecutionStarted(TestPlan)` and `testPlanExecutionFinished(TestPlan)` callback +methods. See the `{UniqueIdTrackingListener}` for an example. + +[[launcher-api-listeners-custom-deactivation]] +==== Deactivating a TestExecutionListener + +Sometimes it can be useful to run a test suite _without_ certain execution listeners being +active. For example, you might have custom a `{TestExecutionListener}` that sends the test +results to an external system for reporting purposes, and while debugging you might not +want these _debug_ results to be reported. To do this, provide a pattern for the +`junit.platform.execution.listeners.deactivate` _configuration parameter_ to specify which +execution listeners should be deactivated (i.e. not registered) for the current test run. + +[NOTE] +==== +Only listeners registered via the `{ServiceLoader}` mechanism within the +`/META-INF/services/org.junit.platform.launcher.TestExecutionListener` file can be +deactivated. In other words, any `TestExecutionListener` registered explicitly via the +`{LauncherDiscoveryRequest}` cannot be deactivated via the +`junit.platform.execution.listeners.deactivate` _configuration parameter_. + +In addition, since execution listeners are registered before the test run starts, the +`junit.platform.execution.listeners.deactivate` _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}`. +==== + +[[launcher-api-listeners-custom-deactivation-pattern]] +===== Pattern Matching Syntax + +Refer to <> for details. + +[[launcher-api-launcher-config]] +==== Configuring the Launcher + +If you require fine-grained control over automatic detection and registration of test +engines and listeners, you may create an instance of `{LauncherConfig}` and supply that to +the `{LauncherFactory}`. Typically, an instance of `LauncherConfig` is created via the +built-in fluent _builder_ API, as demonstrated in the following example. + +[source,java,indent=0] +---- +include::{testDir}/example/UsingTheLauncherDemo.java[tags=launcherConfig] +---- + +[[launcher-api-dry-run-mode]] +==== Dry-Run Mode + +When running tests via the `{Launcher}` API, you can enable _dry-run mode_ by setting the +`junit.platform.execution.dryRun.enabled` <> to `true`. In this mode, the `{Launcher}` will not actually +execute any tests but will notify registered `{TestExecutionListener}` instances as if all +tests had been skipped and their containers had been successful. This can be useful to +test changes in the configuration of a build or to verify a listener is called as expected +without having to wait for all tests to be executed. diff --git a/documentation/src/docs/asciidoc/user-guide/testkit.adoc b/documentation/src/docs/asciidoc/user-guide/advanced-topics/testkit.adoc similarity index 97% rename from documentation/src/docs/asciidoc/user-guide/testkit.adoc rename to documentation/src/docs/asciidoc/user-guide/advanced-topics/testkit.adoc index 32658381eaff..665c4daea788 100644 --- a/documentation/src/docs/asciidoc/user-guide/testkit.adoc +++ b/documentation/src/docs/asciidoc/user-guide/advanced-topics/testkit.adoc @@ -5,10 +5,6 @@ The `junit-platform-testkit` artifact provides support for executing a test plan JUnit Platform and then verifying the expected results. As of JUnit Platform 1.4, this support is limited to the execution of a single `TestEngine` (see <>). -WARNING: Although the Test Kit is currently an <> feature, the JUnit Team invites you to try it out and provide feedback to -help improve the Test Kit APIs and eventually <> this feature. - [[testkit-engine]] ==== Engine Test Kit diff --git a/documentation/src/docs/asciidoc/user-guide/appendix.adoc b/documentation/src/docs/asciidoc/user-guide/appendix.adoc index 2551c36ba692..d92194fd2b79 100644 --- a/documentation/src/docs/asciidoc/user-guide/appendix.adoc +++ b/documentation/src/docs/asciidoc/user-guide/appendix.adoc @@ -4,7 +4,8 @@ [[reproducible-builds]] === Reproducible Builds -Starting with version 5.7, JUnit 5 aims for its non-javadoc JARs to be https://reproducible-builds.org/[reproducible] +Starting with version 5.7, JUnit 5 aims for its non-javadoc JARs to be +https://reproducible-builds.org/[reproducible]. Under identical build conditions, such as Java version, repeated builds should provide the same output byte-for-byte. @@ -39,19 +40,32 @@ artifacts are deployed to Sonatype's {snapshot-repo}[snapshots repository] under directory. See <> for details. `junit-platform-engine`:: Public API for test engines. See <> for details. + `junit-platform-jfr`:: + Provides a `LauncherDiscoveryListener` and `TestExecutionListener` for Java Flight + Recorder events on the JUnit Platform. See <> + for details. `junit-platform-launcher`:: Public API for configuring and launching test plans -- typically used by IDEs and build tools. See <> for details. `junit-platform-reporting`:: `TestExecutionListener` implementations that generate test reports -- typically used - by IDEs and build tools. See <> for details. + by IDEs and build tools. See <> for details. `junit-platform-runner`:: Runner for executing tests and test suites on the JUnit Platform in a JUnit 4 environment. See <> for details. + `junit-platform-suite`:: + JUnit Platform Suite artifact that transitively pulls in dependencies on + `junit-platform-suite-api` and `junit-platform-suite-engine` for simplified dependency + management in build tools such as Gradle and Maven. `junit-platform-suite-api`:: Annotations for configuring test suites on the JUnit Platform. Supported by the - <> and possibly by - third-party `TestEngine` implementations. + <> and the + <>. + `junit-platform-suite-commons`:: + Common support utilities for executing test suites on the JUnit Platform. + `junit-platform-suite-engine`:: + Engine that executes test suites on the JUnit Platform; only required at runtime. See + <> for details. `junit-platform-testkit`:: Provides support for executing a test plan for a given `TestEngine` and then accessing the results via a fluent API to verify the expected results. @@ -135,20 +149,27 @@ package org.junit.jupiter { package org.junit.vintage { [junit-vintage-engine] as vintage_engine - [junit:junit] as junit4 } package org.junit.platform { [junit-platform-commons] as commons [junit-platform-console] as console [junit-platform-engine] as engine + [junit-platform-jfr] as jfr [junit-platform-launcher] as launcher [junit-platform-reporting] as reporting [junit-platform-runner] as runner + [junit-platform-suite] as suite [junit-platform-suite-api] as suite_api + [junit-platform-suite-commons] as suite_commons + [junit-platform-suite-engine] as suite_engine [junit-platform-testkit] as testkit } +package "JUnit 4" { + [junit:junit] as junit4 +} + package org.opentest4j { [opentest4j] } @@ -170,33 +191,42 @@ jupiter ..> jupiter_api jupiter ..> jupiter_params jupiter ..> jupiter_engine -jupiter_api ..> opentest4j -jupiter_api ..> commons +jupiter_api ....> opentest4j +jupiter_api ...> commons -jupiter_engine ..> engine +jupiter_engine ...> engine jupiter_engine ..> jupiter_api jupiter_params ..> jupiter_api jupiter_migration_support ..> jupiter_api -jupiter_migration_support ..> junit4 +jupiter_migration_support ...> junit4 console ..> launcher console ..> reporting launcher ..> engine -engine ..> opentest4j +jfr ..> launcher + +engine ....> opentest4j engine ..> commons reporting ..> launcher -runner ..> launcher -runner ..> suite_api -runner ..> junit4 +runner ..> suite_commons +runner ...> junit4 + +suite ..> suite_api +suite ..> suite_engine + +suite_engine ..> suite_commons + +suite_commons ..> launcher +suite_commons ..> suite_api -testkit ..> opentest4j +testkit ....> opentest4j testkit ..> launcher -vintage_engine ..> engine +vintage_engine ...> engine vintage_engine ..> junit4 ---- diff --git a/documentation/src/docs/asciidoc/user-guide/extensions.adoc b/documentation/src/docs/asciidoc/user-guide/extensions.adoc index 3ad1310f8a26..09ca2ec5e88b 100644 --- a/documentation/src/docs/asciidoc/user-guide/extensions.adoc +++ b/documentation/src/docs/asciidoc/user-guide/extensions.adoc @@ -21,27 +21,32 @@ Java's <> mechanism. Developers can register one or more extensions _declaratively_ by annotating a test interface, test class, test method, or custom _<>_ with `@ExtendWith(...)` and supplying class references for the extensions -to register. +annotation>>_ with `@ExtendWith(...)` and supplying class references for the extensions to +register. As of JUnit Jupiter 5.8, `@ExtendWith` may also be declared on fields or on +parameters in test class constructors, in test methods, and in `@BeforeAll`, `@AfterAll`, +`@BeforeEach`, and `@AfterEach` lifecycle methods. -For example, to register a custom `RandomParametersExtension` for a particular test -method, you would annotate the test method as follows. +For example, to register a `WebServerExtension` for a particular test method, you would +annotate the test method as follows. We assume the `WebServerExtension` starts a local web +server and injects the server's URL into parameters annotated with `@WebServerUrl`. [source,java,indent=0] ---- -@ExtendWith(RandomParametersExtension.class) @Test -void test(@Random int i) { - // ... +@ExtendWith(WebServerExtension.class) +void getProductList(@WebServerUrl String serverUrl) { + WebClient webClient = new WebClient(); + // Use WebClient to connect to web server using serverUrl and verify response + assertEquals(200, webClient.get(serverUrl + "/products").getResponseStatus()); } ---- -To register a custom `RandomParametersExtension` for all tests in a particular class and -its subclasses, you would annotate the test class as follows. +To register the `WebServerExtension` for all tests in a particular class and its +subclasses, you would annotate the test class as follows. [source,java,indent=0] ---- -@ExtendWith(RandomParametersExtension.class) +@ExtendWith(WebServerExtension.class) class MyTests { // ... } @@ -71,15 +76,16 @@ class MySecondTests { [TIP] .Extension Registration Order ==== -Extensions registered declaratively via `@ExtendWith` will be executed in the order in -which they are declared in the source code. For example, the execution of tests in both -`MyFirstTests` and `MySecondTests` will be extended by the `DatabaseExtension` and -`WebServerExtension`, **in exactly that order**. +Extensions registered declaratively via `@ExtendWith` at the class level, method level, or +parameter level will be executed in the order in which they are declared in the source +code. For example, the execution of tests in both `MyFirstTests` and `MySecondTests` will +be extended by the `DatabaseExtension` and `WebServerExtension`, **in exactly that order**. ==== If you wish to combine multiple extensions in a reusable way, you can define a custom _<>_ and use `@ExtendWith` as a -_meta-annotation_: +_meta-annotation_ as in the following code listing. Then `@DatabaseAndWebServerExtension` +can be used in place of `@ExtendWith({ DatabaseExtension.class, WebServerExtension.class })`. [source,java,indent=0] ---- @@ -90,6 +96,59 @@ public @interface DatabaseAndWebServerExtension { } ---- +The above examples demonstrate how `@ExtendWith` can be applied at the class level or at +the method level; however, for certain use cases it makes sense for an extension to be +registered declaratively at the field or parameter level. Consider a +`RandomNumberExtension` that generates random numbers that can be injected into a field or +via a parameter in a constructor, test method, or lifecycle method. If the extension +provides a `@Random` annotation that is meta-annotated with +`@ExtendWith(RandomNumberExtension.class)` (see listing below), the extension can be used +transparently as in the following `RandomNumberDemo` example. + +[source,java,indent=0] +---- +include::{testDir}/example/extensions/Random.java[tags=user_guide] +---- + +[source,java,indent=0] +---- +include::{testDir}/example/extensions/RandomNumberDemo.java[tags=user_guide] +---- + +[[extensions-RandomNumberExtension]] +The following code listing provides an example of how one might choose to implement such a +`RandomNumberExtension`. This implementation works for the use cases in +`RandomNumberDemo`; however, it may not prove robust enough to cover all use cases – for +example, the random number generation support is limited to integers, it uses +`java.util.Random` instead of `java.security.SecureRandom`, etc. In any case, it is +important to note which extension APIs are implemented and for what reasons. + +Specifically, `RandomNumberExtension` implements the following extension APIs: + +- `BeforeAllCallback`: to support static field injection +- `TestInstancePostProcessor`: to support non-static field injection +- `ParameterResolver`: to support constructor and method injection + +[source,java,indent=0] +---- +include::{testDir}/example/extensions/RandomNumberExtension.java[tags=user_guide] +---- + +[TIP] +.Extension Registration Order for `@ExtendWith` on Fields +==== +Extensions registered declaratively via `@ExtendWith` on fields will be ordered relative +to `@RegisterExtension` fields and other `@ExtendWith` fields using an algorithm that is +deterministic but intentionally nonobvious. However, `@ExtendWith` fields can be ordered +using the `@Order` annotation. See the <> tip for `@RegisterExtension` fields for details. +==== + +NOTE: `@ExtendWith` fields may be either `static` or non-static. The documentation on +<> and +<> for +`@RegisterExtension` fields also applies to `@ExtendWith` fields. + [[extensions-registration-programmatic]] ==== Programmatic Extension Registration @@ -106,27 +165,27 @@ extension's constructor, a static factory method, or a builder API. [TIP] .Extension Registration Order ==== -By default, extensions registered programmatically via `@RegisterExtension` will be -ordered using an algorithm that is deterministic but intentionally nonobvious. This -ensures that subsequent runs of a test suite execute extensions in the same order, thereby -allowing for repeatable builds. However, there are times when extensions need to be -registered in an explicit order. To achieve that, annotate `@RegisterExtension` fields -with `{Order}`. - -Any `@RegisterExtension` field not annotated with `@Order` will be ordered using the -_default_ order which has a value of `Integer.MAX_VALUE / 2`. This allows `@Order` -annotated extension fields to be explicitly ordered before or after non-annotated -extension fields. Extensions with an explicit order value less than the default order -value will be registered before non-annotated extensions. Similarly, extensions with an -explicit order value greater than the default order value will be registered after -non-annotated extensions. For example, assigning an extension an explicit order value that -is greater than the default order value allows _before_ callback extensions to be -registered last and _after_ callback extensions to be registered first, relative to other -programmatically registered extensions. +By default, extensions registered programmatically via `@RegisterExtension` or +declaratively via `@ExtendWith` on fields will be ordered using an algorithm that is +deterministic but intentionally nonobvious. This ensures that subsequent runs of a test +suite execute extensions in the same order, thereby allowing for repeatable builds. +However, there are times when extensions need to be registered in an explicit order. To +achieve that, annotate `@RegisterExtension` fields or `@ExtendWith` fields with `{Order}`. + +Any `@RegisterExtension` field or `@ExtendWith` field not annotated with `@Order` will be +ordered using the _default_ order which has a value of `Integer.MAX_VALUE / 2`. This +allows `@Order` annotated extension fields to be explicitly ordered before or after +non-annotated extension fields. Extensions with an explicit order value less than the +default order value will be registered before non-annotated extensions. Similarly, +extensions with an explicit order value greater than the default order value will be +registered after non-annotated extensions. For example, assigning an extension an explicit +order value that is greater than the default order value allows _before_ callback +extensions to be registered last and _after_ callback extensions to be registered first, +relative to other programmatically registered extensions. ==== -NOTE: `@RegisterExtension` fields must not be `private` or `null` (at evaluation time) but -may be either `static` or non-static. +NOTE: `@RegisterExtension` fields must not be `null` (at evaluation time) but may be +either `static` or non-static. [[extensions-registration-programmatic-static-fields]] ===== Static Fields @@ -149,19 +208,18 @@ lifecycle methods annotated with `@BeforeAll` or `@AfterAll` as well as `@Before `server` field if necessary. [source,java,indent=0] -.An extension registered via a static field +.Registering an extension via a static field in Java ---- include::{testDir}/example/registration/WebServerDemo.java[tags=user_guide] ---- [[extensions-registration-programmatic-static-fields-kotlin]] -===== Static Fields in Kotlin +====== Static Fields in Kotlin The Kotlin programming language does not have the concept of a `static` field. However, -the compiler can be instructed to generate static fields using annotations. Since, as -stated earlier, `@RegisterExtension` fields must not be `private` nor `null`, one -**cannot** use the `@JvmStatic` annotation in Kotlin as it generates `private` fields. -Rather, the `@JvmField` annotation must be used. +the compiler can be instructed to generate a `private static` field using the `@JvmStatic` +annotation in Kotlin. If you want the Kotlin compiler to generate a `public static` field, +you can use the `@JvmField` annotation instead. The following example is a version of the `WebServerDemo` from the previous section that has been ported to Kotlin. @@ -206,8 +264,8 @@ include::{testDir}/example/registration/DocumentationDemo.java[tags=user_guide] In addition to <> and <> support using annotations, JUnit Jupiter also supports _global extension registration_ via Java's -`java.util.ServiceLoader` mechanism, allowing third-party extensions to be auto-detected -and automatically registered based on what is available in the classpath. +`{ServiceLoader}` mechanism, allowing third-party extensions to be auto-detected and +automatically registered based on what is available in the classpath. Specifically, a custom extension can be registered by supplying its fully qualified class name in a file named `org.junit.jupiter.api.extension.Extension` within the @@ -227,7 +285,7 @@ following system property. `-Djunit.jupiter.extensions.autodetection.enabled=true` -When auto-detection is enabled, extensions discovered via the `ServiceLoader` mechanism +When auto-detection is enabled, extensions discovered via the `{ServiceLoader}` mechanism will be added to the extension registry after JUnit Jupiter's global extensions (e.g., support for `TestInfo`, `TestReporter`, etc.). @@ -282,6 +340,17 @@ following system property. Refer to <> for details. +[[extensions-test-instance-pre-construct-callback]] +=== Test Instance Pre-construct Callback + +`{TestInstancePreConstructCallback}` defines the API for `Extensions` that wish to be invoked +_prior_ to test instances being constructed (by a constructor call or via +`{TestInstanceFactory}`). + +This extension provides a symmetric call to `{TestInstancePreDestroyCallback}` and is useful +in combination with other extensions to prepare constructor parameters or keeping track of test +instances and their lifecycle. + [[extensions-test-instance-factories]] === Test Instance Factories @@ -336,11 +405,11 @@ test instance, invoking custom de-initialization methods on the test instance, e runtime. If a _test class_ constructor, _test method_, or _lifecycle method_ (see -<>) declares a parameter, the parameter must be -_resolved_ at runtime by a `ParameterResolver`. A `ParameterResolver` can either be -built-in (see `{TestInfoParameterResolver}`) or <>. Generally speaking, parameters may be resolved by _name_, _type_, -_annotation_, or any combination thereof. +<>) declares a parameter, the parameter must be _resolved_ at +runtime by a `ParameterResolver`. A `ParameterResolver` can either be built-in (see +`{TestInfoParameterResolver}`) or <>. +Generally speaking, parameters may be resolved by _name_, _type_, _annotation_, or any +combination thereof. If you wish to implement a custom `{ParameterResolver}` that resolves parameters based solely on the type of the parameter, you may find it convenient to extend the @@ -366,6 +435,13 @@ those provided in `java.lang.reflect.Parameter` in order to avoid this bug in th * `List findRepeatableAnnotations(Class annotationType)` ==== +[NOTE] +==== +Other extensions can also leverage registered `ParameterResolvers` for method and +constructor invocations, using the `{ExecutableInvoker}` available via the +`getExecutableInvoker()` method in the `ExtensionContext`. +==== + [[extensions-test-result-processing]] === Test Result Processing @@ -379,20 +455,44 @@ information for the following events. * `testFailed`: invoked after a _test method_ has failed NOTE: In contrast to the definition of "test method" presented in -<>, in this context _test method_ refers to any -`@Test` method or `@TestTemplate` method (for example, a `@RepeatedTest` or -`@ParameterizedTest`). +<>, in this context _test method_ refers to any `@Test` method +or `@TestTemplate` method (for example, a `@RepeatedTest` or `@ParameterizedTest`). + +Extensions implementing this interface can be registered at the class level, instance +level, or method level. When registered at the class level, a `TestWatcher` will be +invoked for any contained _test method_ including those in `@Nested` classes. When +registered at the method level, a `TestWatcher` will only be invoked for the _test method_ +for which it was registered. + +[WARNING] +==== +If a `TestWatcher` is registered via a non-static (instance) field – for example, using +`@RegisterExtension` – and the test class is configured with +`@TestInstance(Lifecycle.PER_METHOD)` semantics (which is the default lifecycle mode), the +`TestWatcher` will **not** be invoked with events for `@TestTemplate` methods (for +example, `@RepeatedTest` or `@ParameterizedTest`). + +To ensure that a `TestWatcher` is invoked for all _test methods_ in a given class, it is +therefore recommended that the `TestWatcher` be registered at the class level with +`@ExtendWith` or via a `static` field with `@RegisterExtension` or `@ExtendWith`. +==== + +If there is a failure at the class level — for example, an exception thrown by a +`@BeforeAll` method — no test results will be reported. Similarly, if the test class is +disabled via an `ExecutionCondition` — for example, `@Disabled` — no test results will be +reported. -Extensions implementing this interface can be registered at the method level or at the -class level. In the latter case they will be invoked for any contained _test method_ -including those in `@Nested` classes. +In contrast to other Extension APIs, a `TestWatcher` is not permitted to adversely +influence the execution of tests. Consequently, any exception thrown by a method in the +`TestWatcher` API will be logged at `WARNING` level and will not be allowed to propagate +or fail test execution. [WARNING] ==== Any instances of `ExtensionContext.Store.CloseableResource` stored in the `Store` of the -provided `{ExtensionContext}` will be closed _before_ methods in this API are invoked (see -<>). You can use the parent context's `Store` to work with such -resources. +provided `{ExtensionContext}` will be closed _before_ methods in the `TestWatcher` API are +invoked (see <>). You can use the parent context's `Store` to +work with such resources. ==== [[extensions-lifecycle-callbacks]] @@ -740,29 +840,33 @@ callbacks implemented by `Extension2`. `Extension1` is therefore said to _wrap_ `Extension2`. JUnit Jupiter also guarantees _wrapping_ behavior within class and interface hierarchies -for user-supplied _lifecycle methods_ (see <>). +for user-supplied _lifecycle methods_ (see <>). -* `@BeforeAll` methods are inherited from superclasses as long as they are not _hidden_ or - _overridden_. Furthermore, `@BeforeAll` methods from superclasses will be executed - **before** `@BeforeAll` methods in subclasses. +* `@BeforeAll` methods are inherited from superclasses as long as they are not _hidden_, + _overridden_, or _superseded_ (i.e., replaced based on signature only, irrespective of + Java's visibility rules). Furthermore, `@BeforeAll` methods from superclasses will be + executed **before** `@BeforeAll` methods in subclasses. ** Similarly, `@BeforeAll` methods declared in an interface are inherited as long as they are not _hidden_ or _overridden_, and `@BeforeAll` methods from an interface will be executed **before** `@BeforeAll` methods in the class that implements the interface. -* `@AfterAll` methods are inherited from superclasses as long as they are not _hidden_ or - _overridden_. Furthermore, `@AfterAll` methods from superclasses will be executed - **after** `@AfterAll` methods in subclasses. +* `@AfterAll` methods are inherited from superclasses as long as they are not _hidden_, + _overridden_, or _superseded_ (i.e., replaced based on signature only, irrespective of + Java's visibility rules). Furthermore, `@AfterAll` methods from superclasses will be + executed **after** `@AfterAll` methods in subclasses. ** Similarly, `@AfterAll` methods declared in an interface are inherited as long as they are not _hidden_ or _overridden_, and `@AfterAll` methods from an interface will be executed **after** `@AfterAll` methods in the class that implements the interface. * `@BeforeEach` methods are inherited from superclasses as long as they are not - _overridden_. Furthermore, `@BeforeEach` methods from superclasses will be executed - **before** `@BeforeEach` methods in subclasses. + _overridden_ or _superseded_ (i.e., replaced based on signature only, irrespective of + Java's visibility rules). Furthermore, `@BeforeEach` methods from superclasses will be + executed **before** `@BeforeEach` methods in subclasses. ** Similarly, `@BeforeEach` methods declared as interface default methods are inherited as long as they are not _overridden_, and `@BeforeEach` default methods will be executed **before** `@BeforeEach` methods in the class that implements the interface. * `@AfterEach` methods are inherited from superclasses as long as they are not - _overridden_. Furthermore, `@AfterEach` methods from superclasses will be executed - **after** `@AfterEach` methods in subclasses. + _overridden_ or _superseded_ (i.e., replaced based on signature only, irrespective of + Java's visibility rules). Furthermore, `@AfterEach` methods from superclasses will be + executed **after** `@AfterEach` methods in subclasses. ** Similarly, `@AfterEach` methods declared as interface default methods are inherited as long as they are not _overridden_, and `@AfterEach` default methods will be executed **after** `@AfterEach` methods in the class that implements the interface. @@ -884,7 +988,6 @@ image::extensions_BrokenLifecycleMethodConfigDemo.png[caption='',title='BrokenLi [TIP] ==== Due to the aforementioned behavior, the JUnit Team recommends that developers declare at -most one of each type of _lifecycle method_ (see <>) -per test class or test interface unless there are no dependencies between such lifecycle -methods. +most one of each type of _lifecycle method_ (see <>) per test +class or test interface unless there are no dependencies between such lifecycle methods. ==== diff --git a/documentation/src/docs/asciidoc/user-guide/images/writing-tests_nested_test_ide.png b/documentation/src/docs/asciidoc/user-guide/images/writing-tests_nested_test_ide.png new file mode 100644 index 000000000000..8a5dd0a1d158 Binary files /dev/null and b/documentation/src/docs/asciidoc/user-guide/images/writing-tests_nested_test_ide.png differ diff --git a/documentation/src/docs/asciidoc/user-guide/launcher-api.adoc b/documentation/src/docs/asciidoc/user-guide/launcher-api.adoc deleted file mode 100644 index a1964e34e59c..000000000000 --- a/documentation/src/docs/asciidoc/user-guide/launcher-api.adoc +++ /dev/null @@ -1,191 +0,0 @@ -[[launcher-api]] -=== JUnit Platform Launcher API - -One of the prominent goals of JUnit 5 is to make the interface between JUnit and its -programmatic clients – build tools and IDEs – more powerful and stable. The purpose is to -decouple the internals of discovering and executing tests from all the filtering and -configuration that's necessary from the outside. - -JUnit 5 introduces the concept of a `Launcher` that can be used to discover, filter, and -execute tests. Moreover, third party test libraries – like Spock, Cucumber, and FitNesse -– can plug into the JUnit Platform's launching infrastructure by providing a custom -`{TestEngine}`. - -The launcher API is in the `{junit-platform-launcher}` module. - -An example consumer of the launcher API is the `{ConsoleLauncher}` in the -`{junit-platform-console}` project. - -[[launcher-api-discovery]] -==== Discovering Tests - -Introducing _test discovery_ as a dedicated feature of the platform itself will -(hopefully) free IDEs and build tools from most of the difficulties they had to go -through to identify test classes and test methods in the past. - -Usage Example: - -[source,java,indent=0] ----- -include::{testDir}/example/UsingTheLauncherDemo.java[tags=imports] ----- - -[source,java,indent=0] ----- -include::{testDir}/example/UsingTheLauncherDemo.java[tags=discovery] ----- - -There's currently the possibility to select classes, methods, and all classes in a -package or even search for all tests in the classpath. Discovery takes place across all -participating test engines. - -The resulting `TestPlan` is a hierarchical (and read-only) description of all engines, -classes, and test methods that fit the `LauncherDiscoveryRequest`. The client can -traverse the tree, retrieve details about a node, and get a link to the original source -(like class, method, or file position). Every node in the test plan has a _unique ID_ -that can be used to invoke a particular test or group of tests. - -Clients can register one or more `{LauncherDiscoveryListener}` implementations to get -insights into events that occur during test discovery via the -`{LauncherDiscoveryRequestBuilder}`. The builder registers a default listener that can be -changed via the `junit.platform.discovery.listener.default` configuration parameter. If -the parameter is not set, test discovery will be aborted after the first failure is -encountered. - -[[launcher-api-execution]] -==== Executing Tests - -To execute tests, clients can use the same `LauncherDiscoveryRequest` as in the discovery -phase or create a new request. Test progress and reporting can be achieved by registering -one or more `{TestExecutionListener}` implementations with the `Launcher` as in the -following example. - -[source,java,indent=0] ----- -include::{testDir}/example/UsingTheLauncherDemo.java[tags=execution] ----- - -There is no return value for the `execute()` method, but you can easily use a listener to -aggregate the final results in an object of your own. For examples see the -`{SummaryGeneratingListener}` and `{LegacyXmlReportGeneratingListener}`. - -[[launcher-api-engines-custom]] -==== Plugging in your own Test Engine - -JUnit currently provides two `{TestEngine}` implementations. - -* `{junit-jupiter-engine}`: The core of JUnit Jupiter. -* `{junit-vintage-engine}`: A thin layer on top of JUnit 4 to allow running _vintage_ - tests with the launcher infrastructure. - -Third parties may also contribute their own `TestEngine` by implementing the interfaces -in the {junit-platform-engine} module and _registering_ their engine. By default, engine -registration is supported via Java's `java.util.ServiceLoader` mechanism. For example, -the `junit-jupiter-engine` module registers its -`org.junit.jupiter.engine.JupiterTestEngine` in a file named -`org.junit.platform.engine.TestEngine` within the `/META-INF/services` in the -`junit-jupiter-engine` JAR. - -NOTE: `{HierarchicalTestEngine}` is a convenient abstract base implementation (used by -the `{junit-jupiter-engine}`) that only requires implementors to provide the logic for -test discovery. It implements execution of `TestDescriptors` that implement the `Node` -interface, including support for parallel execution. - -[[launcher-api-engines-custom-ids]] -[WARNING] -.The `junit-` prefix is reserved for TestEngines from the JUnit Team -==== -The JUnit Platform `Launcher` enforces that only `TestEngine` implementations published -by the JUnit Team may use the `junit-` prefix for their `TestEngine` IDs. - -* If any third-party `TestEngine` claims to be `junit-jupiter` or `junit-vintage`, an - exception will be thrown, immediately halting execution of the JUnit Platform. -* If any third-party `TestEngine` uses the `junit-` prefix for its ID, a warning message - will be logged. Later releases of the JUnit Platform will throw an exception for such - violations. -==== - -[[launcher-api-post-discovery-filters-custom]] -==== Plugging in your own Post-Discovery Filters - -In addition to specifying post-discovery filters as part of a `{LauncherDiscoveryRequest}` -passed to the `{Launcher}` API, by default custom `{PostDiscoveryFilter}` implementations -will be discovered at runtime via Java's `java.util.ServiceLoader` mechanism and -automatically applied by the `Launcher` in addition to those that are part of the request. -For example, an `example.CustomTagFilter` class implementing `{PostDiscoveryFilter}` and -declared within the `/META-INF/services/org.junit.platform.launcher.PostDiscoveryFilter` -file is loaded and applied automatically. - -[[launcher-api-listeners-custom]] -==== Plugging in your own Test Execution Listener - -In addition to the public `{Launcher}` API method for registering test execution -listeners programmatically, by default custom `{TestExecutionListener}` implementations -will be discovered at runtime via Java's `java.util.ServiceLoader` mechanism and -automatically registered with the `Launcher` created via the `LauncherFactory`. For -example, an `example.TestInfoPrinter` class implementing `{TestExecutionListener}` and -declared within the -`/META-INF/services/org.junit.platform.launcher.TestExecutionListener` file is loaded and -registered automatically. - -[[launcher-api-listeners-custom-deactivation]] -==== Deactivating Test Execution Listeners - -Sometimes it can be useful to run a test suite _without_ certain execution listeners being -active. For example, you might have custom a `TestExecutionListener` that sends the test -results to an external system for reporting purposes, and while debugging you might not -want these _debug_ results to be reported. To do this, provide a pattern for the -`junit.platform.execution.listeners.deactivate` _configuration parameter_ to specify which -execution listeners should be deactivated (i.e. not registered) for the current test run. - -[NOTE] -==== -Only listeners registered via the `ServiceLoader` mechanism within the -`/META-INF/services/org.junit.platform.launcher.TestExecutionListener` file can be -deactivated. In other words, any `TestExecutionListener` registered explicitly via the -`LauncherDiscoveryRequest` cannot be deactivated via the -`junit.platform.execution.listeners.deactivate` _configuration parameter_. - -In addition, since execution listeners are registered before the test run starts, the -`junit.platform.execution.listeners.deactivate` _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`. -==== - -[[launcher-api-listeners-custom-deactivation-pattern]] -===== Pattern Matching Syntax - -Refer to <> for details. - -[[launcher-api-listeners-reporting]] -==== JUnit Platform Reporting - -The `junit-platform-reporting` artifact contains `{TestExecutionListener}` -implementations that generate test reports. These listeners are typically used by IDEs -and build tools. The package `org.junit.platform.reporting.legacy.xml` currently contains -the following implementation. - -* `{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. - -NOTE: The `{junit-platform-launcher}` module also contains `{TestExecutionListener}` -implementations that can be used for reporting purposes. See `{LoggingListener}` and -`{SummaryGeneratingListener}` for details. - -[[launcher-api-launcher-config]] -==== Configuring the Launcher - -If you require fine-grained control over automatic detection and registration of test -engines and test execution listeners, you may create an instance of `LauncherConfig` and -supply that to the `LauncherFactory.create(LauncherConfig)` method. Typically an instance -of `LauncherConfig` is created via the built-in fluent _builder_ API, as demonstrated in -the following example. - -[source,java,indent=0] ----- -include::{testDir}/example/UsingTheLauncherDemo.java[tags=launcherConfig] ----- 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 b3fb1ded6ed8..98e0b056af93 100644 --- a/documentation/src/docs/asciidoc/user-guide/migration-from-junit4.adoc +++ b/documentation/src/docs/asciidoc/user-guide/migration-from-junit4.adoc @@ -1,26 +1,26 @@ [[migrating-from-junit4]] == Migrating from JUnit 4 -Although the JUnit Jupiter programming model and extension model will not support JUnit 4 +Although the JUnit Jupiter programming model and extension model do not support JUnit 4 features such as `Rules` and `Runners` natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and custom build test infrastructure to migrate to JUnit Jupiter. Instead, JUnit provides a gentle migration path via a _JUnit Vintage test engine_ which -allows existing tests based on JUnit 3 and JUnit 4 to be executed using the JUnit -Platform infrastructure. Since all classes and annotations specific to JUnit Jupiter -reside under a new `org.junit.jupiter` base package, having both JUnit 4 and JUnit -Jupiter in the classpath does not lead to any conflicts. It is therefore safe to maintain -existing JUnit 4 tests alongside JUnit Jupiter tests. Furthermore, since the JUnit team -will continue to provide maintenance and bug fix releases for the JUnit 4.x baseline, -developers have plenty of time to migrate to JUnit Jupiter on their own schedule. +allows existing tests based on JUnit 3 and JUnit 4 to be executed using the JUnit Platform +infrastructure. Since all classes and annotations specific to JUnit Jupiter reside under +the `org.junit.jupiter` base package, having both JUnit 4 and JUnit Jupiter in the +classpath does not lead to any conflicts. It is therefore safe to maintain existing JUnit +4 tests alongside JUnit Jupiter tests. Furthermore, since the JUnit team will continue to +provide maintenance and bug fix releases for the JUnit 4.x baseline, developers have +plenty of time to migrate to JUnit Jupiter on their own schedule. [[migrating-from-junit4-running]] === Running JUnit 4 Tests on the JUnit Platform -Just make sure that the `junit-vintage-engine` artifact is in your test runtime path. In -that case JUnit 3 and JUnit 4 tests will automatically be picked up by the JUnit Platform +Make sure that the `junit-vintage-engine` artifact is in your test runtime path. In that +case JUnit 3 and JUnit 4 tests will automatically be picked up by the JUnit Platform launcher. See the example projects in the {junit5-samples-repo}[`junit5-samples`] repository to @@ -30,11 +30,11 @@ find out how this is done with Gradle and Maven. ==== Categories Support For test classes or methods that are annotated with `@Category`, the _JUnit Vintage test -engine_ exposes the category's fully qualified class name as a tag of the corresponding -test identifier. For example, if a test method is annotated with -`@Category(Example.class)`, it will be tagged with `"com.acme.Example"`. Similar to the -`Categories` runner in JUnit 4, this information can be used to filter the discovered -tests before executing them (see <> for details). +engine_ exposes the category's fully qualified class name as a <> +for the corresponding test class or test method. For example, if a test method is +annotated with `@Category(Example.class)`, it will be tagged with `"com.acme.Example"`. +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-tips]] @@ -61,8 +61,15 @@ tests to JUnit Jupiter. * `@Category` no longer exists; use `@Tag` instead. * `@RunWith` no longer exists; superseded by `@ExtendWith`. * `@Rule` and `@ClassRule` no longer exist; superseded by `@ExtendWith` and - `@RegisterExtension` + `@RegisterExtension`. - See also <>. +* `@Test(expected = ...)` and the `ExpectedException` rule no longer exist; use + `Assertions.assertThrows(...)` instead. + - See <> if you still need to use + `ExpectedException`. +* Assertions and assumptions in JUnit Jupiter accept the failure message as their last + argument instead of the first one. + - See <> for details. [[migrating-from-junit4-rule-support]] @@ -95,12 +102,9 @@ all rule migration support extensions: `VerifierSupport`, `ExternalResourceSuppo `@EnableJUnit4MigrationSupport` which registers migration support for rules _and_ JUnit 4's `@Ignore` annotation (see <>). -However, if you intend to develop a new extension for JUnit 5 please use the new +However, if you intend to develop a new extension for JUnit Jupiter please use the new extension model of JUnit Jupiter instead of the rule-based model of JUnit 4. -WARNING: JUnit 4 `Rule` support in JUnit Jupiter is currently an _experimental_ feature. -Consult the table in <> for detail. - [[migrating-from-junit4-ignore-annotation-support]] === JUnit 4 @Ignore Support @@ -122,5 +126,34 @@ automatically registers the `IgnoreCondition` along with include::{testDir}/example/IgnoredTestsDemo.java[tags=user_guide] ---- -WARNING: JUnit 4 `@Ignore` support in JUnit Jupiter is currently an _experimental_ -feature. Consult the table in <> for detail. + +[[migrating-from-junit4-failure-message-arguments]] +=== Failure Message Arguments + +The `Assumptions` and `Assertions` classes in JUnit Jupiter declare arguments in a +different order than in JUnit 4. In JUnit 4 assertion and assumption methods accept the +failure message as the first argument; whereas, in JUnit Jupiter assertion and assumption +methods accept the failure message as the last argument. + +For instance, the method `assertEquals` in JUnit 4 is declared as `assertEquals(String +message, Object expected, Object actual)`, but in JUnit Jupiter it is declared as +`assertEquals(Object expected, Object actual, String message)`. The rationale for this is +that a failure message is _optional_, and optional arguments should be declared after +required arguments in a method signature. + +The methods affected by this change are the following: + +- Assertions + * `assertTrue` + * `assertFalse` + * `assertNull` + * `assertNotNull` + * `assertEquals` + * `assertNotEquals` + * `assertArrayEquals` + * `assertSame` + * `assertNotSame` + * `assertThrows` +- Assumptions + * `assumeTrue` + * `assumeFalse` diff --git a/documentation/src/docs/asciidoc/user-guide/overview.adoc b/documentation/src/docs/asciidoc/user-guide/overview.adoc index afad42176c2f..d06a5eb53eda 100644 --- a/documentation/src/docs/asciidoc/user-guide/overview.adoc +++ b/documentation/src/docs/asciidoc/user-guide/overview.adoc @@ -23,20 +23,20 @@ The **JUnit Platform** serves as a foundation for <> on the JVM. It also defines the `{TestEngine}` API for developing a testing framework that runs on the platform. Furthermore, the platform provides a <> to launch the platform from the -command line and a <> for -running any `TestEngine` on the platform in a JUnit 4 based environment. First-class -support for the JUnit Platform also exists in popular IDEs (see -<>, <>, -<>, and <>) and build tools (see -<>, <>, and -<>). - -**JUnit Jupiter** is the combination of the new <> and +command line and the <> for running a custom test suite using +one or more test engines on the platform. First-class support for the JUnit Platform also +exists in popular IDEs (see <>, +<>, <>, and +<>) and build tools (see <>, +<>, and <>). + +**JUnit Jupiter** is the combination of the <> and <> for writing tests and extensions in JUnit 5. The Jupiter sub-project provides a `TestEngine` for running Jupiter based tests on the platform. **JUnit Vintage** provides a `TestEngine` for running JUnit 3 and JUnit 4 based tests on -the platform. +the platform. It requires JUnit 4.12 or later to be present on the class path or module +path. [[overview-java-versions]] === Supported Java Versions @@ -47,7 +47,7 @@ has been compiled with previous versions of the JDK. [[overview-getting-help]] === Getting Help -Ask JUnit 5 related questions on {StackOverflow} or chat with us on {Gitter}. +Ask JUnit 5 related questions on {StackOverflow} or chat with the community on {Gitter}. [[overview-getting-started]] === Getting Started diff --git a/documentation/src/docs/asciidoc/user-guide/running-tests.adoc b/documentation/src/docs/asciidoc/user-guide/running-tests.adoc index 5adb80db0fc7..1900545858fc 100644 --- a/documentation/src/docs/asciidoc/user-guide/running-tests.adoc +++ b/documentation/src/docs/asciidoc/user-guide/running-tests.adoc @@ -28,35 +28,48 @@ include the corresponding versions of the `junit-platform-launcher`, [source,groovy] [subs=attributes+] ---- -// Only needed to run tests in a version of IntelliJ IDEA that bundles older versions -testRuntimeOnly("org.junit.platform:junit-platform-launcher:{platform-version}") -testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:{jupiter-version}") -testRuntimeOnly("org.junit.vintage:junit-vintage-engine:{vintage-version}") +testImplementation(platform("org.junit:junit-bom:{bom-version}")) +testRuntimeOnly("org.junit.platform:junit-platform-launcher") { + because("Only needed to run tests in a version of IntelliJ IDEA that bundles older versions") +} +testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") +testRuntimeOnly("org.junit.vintage:junit-vintage-engine") ---- .Additional Maven Dependencies [source,xml] [subs=attributes+] ---- - - - org.junit.platform - junit-platform-launcher - {platform-version} - test - - - org.junit.jupiter - junit-jupiter-engine - {jupiter-version} - test - - - org.junit.vintage - junit-vintage-engine - {vintage-version} - test - + + + + + org.junit.platform + junit-platform-launcher + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.vintage + junit-vintage-engine + test + + + + + + org.junit + junit-bom + {bom-version} + pom + import + + + ---- [[running-tests-ide-eclipse]] @@ -109,37 +122,31 @@ your IDE has built-in support for JUnit 4. [[running-tests-build-gradle]] ==== Gradle -[WARNING] -.The JUnit Platform Gradle Plugin has been discontinued -==== -The `junit-platform-gradle-plugin` developed by the JUnit team was deprecated in JUnit -Platform 1.2 and discontinued in 1.3. Please switch to Gradle's standard `test` task. -==== - Starting with https://docs.gradle.org/4.6/release-notes.html[version 4.6], Gradle provides https://docs.gradle.org/current/userguide/java_testing.html#using_junit5[native support] -for executing tests on the JUnit Platform. To enable it, you just need to specify +for executing tests on the JUnit Platform. To enable it, you need to specify `useJUnitPlatform()` within a `test` task declaration in `build.gradle`: -[source,java,indent=0] +[source,groovy,indent=0] [subs=attributes+] ---- test { - useJUnitPlatform() + useJUnitPlatform() } ---- -Filtering by tags or engines is also supported: +Filtering by <>, +<>, or engines is also supported: -[source,java,indent=0] +[source,groovy,indent=0] [subs=attributes+] ---- test { - useJUnitPlatform { - includeTags 'fast', 'smoke & feature-a' - // excludeTags 'slow', 'ci' - includeEngines 'junit-jupiter' - // excludeEngines 'junit-vintage' + useJUnitPlatform { + includeTags("fast", "smoke & feature-a") + // excludeTags("slow", "ci") + includeEngines("junit-jupiter") + // excludeEngines("junit-vintage") } } ---- @@ -148,6 +155,26 @@ Please refer to the https://docs.gradle.org/current/userguide/java_plugin.html#sec:java_test[official Gradle documentation] for a comprehensive list of options. +[[running-tests-build-gradle-bom]] +===== Aligning dependency versions + +Unless you're using Spring Boot which defines its own way of managing dependencies, it is +recommended to use the JUnit Platform BOM to align the versions of all JUnit 5 artifacts. + +[source,groovy,indent=0] +[subs=attributes+] +---- +dependencies { + testImplementation(platform("org.junit:junit-bom:{bom-version}")) +} +---- + +Using the BOM allows you to omit the version when declaring dependencies on all artifacts +with the `org.junit.platform`, `org.junit.jupiter`, and `org.junit.vintage` group IDs. + +TIP: See <> for details on how to override the version +of JUnit used in your Spring Boot application. + [[running-tests-build-gradle-config-params]] ===== Configuration Parameters @@ -157,15 +184,13 @@ discovery and execution. However, you can provide configuration parameters withi build script via system properties (as shown below) or via the `junit-platform.properties` file. -[source,java,indent=0] +[source,groovy,indent=0] ---- test { // ... - systemProperty 'junit.jupiter.conditions.deactivate', '*' - systemProperties = [ - 'junit.jupiter.extensions.autodetection.enabled': 'true', - 'junit.jupiter.testinstance.lifecycle.default': 'per_class' - ] + systemProperty("junit.jupiter.conditions.deactivate", "*") + systemProperty("junit.jupiter.extensions.autodetection.enabled", true) + systemProperty("junit.jupiter.testinstance.lifecycle.default", "per_class") // ... } ---- @@ -176,15 +201,13 @@ test { In order to run any tests at all, a `TestEngine` implementation must be on the classpath. To configure support for JUnit Jupiter based tests, configure a `testImplementation` dependency -on the JUnit Jupiter API and a `testRuntimeOnly` dependency on the JUnit Jupiter `TestEngine` -implementation similar to the following. +on the dependency-aggregating JUnit Jupiter artifact similar to the following. -[source,java,indent=0] +[source,groovy,indent=0] [subs=attributes+] ---- dependencies { - testImplementation("org.junit.jupiter:junit-jupiter-api:{jupiter-version}") - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:{jupiter-version}") + testImplementation("org.junit.jupiter:junit-jupiter:{jupiter-version}") // version can be omitted when using the BOM } ---- @@ -192,12 +215,12 @@ The JUnit Platform can run JUnit 4 based tests as long as you configure a `testI dependency on JUnit 4 and a `testRuntimeOnly` dependency on the JUnit Vintage `TestEngine` implementation similar to the following. -[source,java,indent=0] +[source,groovy,indent=0] [subs=attributes+] ---- dependencies { testImplementation("junit:junit:{junit4-version}") - testRuntimeOnly("org.junit.vintage:junit-vintage-engine:{vintage-version}") + testRuntimeOnly("org.junit.vintage:junit-vintage-engine:{vintage-version}") // version can be omitted when using the BOM } ---- @@ -215,11 +238,11 @@ qualified class name_ of the `{LogManager}` implementation to use. The example b demonstrates how to configure Log4j{nbsp}2.x (see {Log4j_JDK_Logging_Adapter} for details). -[source,java,indent=0] +[source,groovy,indent=0] [subs=attributes+] ---- test { - systemProperty 'java.util.logging.manager', 'org.apache.logging.log4j.jul.LogManager' + systemProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager") } ---- @@ -231,14 +254,6 @@ additional dependency to the runtime classpath. [[running-tests-build-maven]] ==== Maven -[WARNING] -.The JUnit Platform Maven Surefire Provider has been discontinued -==== -The `junit-platform-surefire-provider`, which was originally developed by the JUnit team, -was deprecated in JUnit Platform 1.3 and discontinued in 1.4. Please use Maven Surefire's -native support instead. -==== - Starting with https://issues.apache.org/jira/browse/SUREFIRE-1330[version 2.22.0], Maven Surefire and Maven Failsafe provide https://maven.apache.org/surefire/maven-surefire-plugin/examples/junit-platform.html[native support] @@ -246,6 +261,58 @@ for executing tests on the JUnit Platform. The `pom.xml` file in the `{junit5-jupiter-starter-maven}` project demonstrates how to use the Maven Surefire plugin and can serve as a starting point for configuring your Maven build. +[WARNING] +.Use Maven Surefire/Failsafe 3.0.0-M4 or later to avoid interoperability issues +==== +Maven Surefire/Failsafe 3.0.0-M4 +https://issues.apache.org/jira/browse/SUREFIRE-1585[introduced support] for aligning the +version of the JUnit Platform Launcher it uses with the JUnit Platform version found on +the test runtime classpath. Therefore, it is recommended to use version 3.0.0-M4 or later +to avoid interoperability issues. + +Alternatively, you can add a test dependency on the matching version of the JUnit Platform +Launcher to your Maven build as follows. + +[source,xml] +[subs=attributes+] +---- + + org.junit.platform + junit-platform-launcher + {platform-version} + test + +---- +==== + +[[running-tests-build-maven-bom]] +===== Aligning dependency versions + +Unless you're using Spring Boot which defines its own way of managing dependencies, it is +recommended to use the JUnit Platform BOM to align the versions of all JUnit 5 artifacts. + +[source,xml,indent=0] +[subs=attributes+] +---- + + + + org.junit + junit-bom + {bom-version} + pom + import + + + +---- + +Using the BOM allows you to omit the version when declaring dependencies on all artifacts +with the `org.junit.platform`, `org.junit.jupiter`, and `org.junit.vintage` group IDs. + +TIP: See <> for details on how to override the version +of JUnit used in your Spring Boot application. + [[running-tests-build-maven-engines-configure]] ===== Configuring Test Engines @@ -259,6 +326,17 @@ following. [source,xml,indent=0] [subs=attributes+] ---- + + + + + org.junit.jupiter + junit-jupiter + {jupiter-version} + test + + + @@ -272,23 +350,6 @@ following. - - - - org.junit.jupiter - junit-jupiter-api - {jupiter-version} - test - - - org.junit.jupiter - junit-jupiter-engine - {jupiter-version} - test - - - - ---- Maven Surefire and Maven Failsafe can run JUnit 4 based tests alongside Jupiter tests as @@ -298,19 +359,6 @@ long as you configure `test` scoped dependencies on JUnit 4 and the JUnit Vintag [source,xml,indent=0] [subs=attributes+] ---- - - - - - maven-surefire-plugin - {surefire-version} - - - maven-failsafe-plugin - {surefire-version} - - - @@ -323,12 +371,25 @@ long as you configure `test` scoped dependencies on JUnit 4 and the JUnit Vintag org.junit.vintage junit-vintage-engine - {vintage-version} + {vintage-version} test + + + + maven-surefire-plugin + {surefire-version} + + + maven-failsafe-plugin + {surefire-version} + + + + ---- [[running-tests-build-maven-filter-test-class-names]] @@ -337,10 +398,10 @@ long as you configure `test` scoped dependencies on JUnit 4 and the JUnit Vintag The Maven Surefire Plugin will scan for test classes whose fully qualified names match the following patterns. -- `+**/Test*.java+` -- `+**/*Test.java+` -- `+**/*Tests.java+` -- `+**/*TestCase.java+` +- `+++**/Test*.java+++` +- `+++**/*Test.java+++` +- `+++**/*Tests.java+++` +- `+++**/*TestCase.java+++` Moreover, it will exclude all nested classes (including static member classes) by default. @@ -376,8 +437,9 @@ documentation for Maven Surefire for details. [[running-tests-build-maven-filter-tags]] ===== Filtering by Tags -You can filter tests by tags or <> using -the following configuration properties. +You can filter tests by <> or +<> using the following configuration +properties. - to include _tags_ or _tag expressions_, use `groups`. - to exclude _tags_ or _tag expressions_, use `excludedGroups`. @@ -394,7 +456,7 @@ the following configuration properties. acceptance | !feature-a integration, regression - + @@ -436,18 +498,18 @@ below) or via the `junit-platform.properties` file. [[running-tests-build-ant]] ==== Ant -Starting with version `1.10.3` of link:https://ant.apache.org/[Ant], a new -link:https://ant.apache.org/manual/Tasks/junitlauncher.html[`junitlauncher`] task has -been introduced to provide native support for launching tests on the JUnit Platform. The -`junitlauncher` task is solely responsible for launching the JUnit Platform and passing -it the selected collection of tests. The JUnit Platform then delegates to registered test -engines to discover and execute the tests. +Starting with version `1.10.3`, link:https://ant.apache.org/[Ant] has a +link:https://ant.apache.org/manual/Tasks/junitlauncher.html[`junitlauncher`] task that +provides native support for launching tests on the JUnit Platform. The `junitlauncher` +task is solely responsible for launching the JUnit Platform and passing it the selected +collection of tests. The JUnit Platform then delegates to registered test engines to +discover and execute the tests. -The `junitlauncher` task attempts to align as close as possible with native Ant +The `junitlauncher` task attempts to align as closely as possible with native Ant constructs such as link:https://ant.apache.org/manual/Types/resources.html#collection[resource collections] -for allowing users to select the tests that they want executed by test engines. This -gives the task a consistent and natural feel when compared to many other core Ant tasks. +for allowing users to select the tests that they want executed by test engines. This gives +the task a consistent and natural feel when compared to many other core Ant tasks. Starting with version `1.10.6` of Ant, the `junitlauncher` task supports link:https://ant.apache.org/manual/Tasks/junitlauncher.html#fork[forking the tests in a separate JVM]. @@ -510,6 +572,48 @@ For further details on usage and configuration options please refer to the offic documentation for the link:https://ant.apache.org/manual/Tasks/junitlauncher.html[`junitlauncher` task]. +[[running-tests-build-spring-boot]] +==== Spring Boot + +link:https://spring.io/projects/spring-boot[Spring Boot] provides automatic support for +managing the version of JUnit used in your project. In addition, the +`spring-boot-starter-test` artifact automatically includes testing libraries such as JUnit +Jupiter, AssertJ, Mockito, etc. + +If your build relies on dependency management support from Spring Boot, you should not +import the <> in your build script since that +will result in duplicate (and potentially conflicting) management of JUnit dependencies. + +If you need to override the version of a dependency used in your Spring Boot application, +you have to override the exact name of the +link:https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#appendix.dependency-versions.properties[version property] +defined in the BOM used by the Spring Boot plugin. For example, the name of the JUnit +Jupiter version property in Spring Boot is `junit-jupiter.version`. The mechanism for +changing a dependency version is documented for both +link:https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/htmlsingle/#managing-dependencies.dependency-management-plugin.customizing[Gradle] +and +link:https://docs.spring.io/spring-boot/docs/current/maven-plugin/reference/htmlsingle/#using.parent-pom[Maven]. + +With Gradle you can override the JUnit Jupiter version by including the following in your +`build.gradle` file. + +[source,groovy,indent=0] +[subs=attributes+] +---- + ext['junit-jupiter.version'] = '{jupiter-version}' +---- + +With Maven you can override the JUnit Jupiter version by including the following in your +`pom.xml` file. + +[source,xml,indent=0] +[subs=attributes+] +---- + + {jupiter-version} + +---- + [[running-tests-console-launcher]] === Console Launcher @@ -520,14 +624,17 @@ Jupiter tests and print test execution results to the console. An executable `junit-platform-console-standalone-{platform-version}.jar` with all dependencies included is published in the {Maven_Central} repository under the https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone[junit-platform-console-standalone] -directory. You can https://docs.oracle.com/javase/tutorial/deployment/jar/run.html[run] -the standalone `ConsoleLauncher` as shown below. +directory. It includes the following dependencies: + +include::{standaloneConsoleLauncherShadowedArtifactsFile}[] -`java -jar junit-platform-console-standalone-{platform-version}.jar <<>>` +You can https://docs.oracle.com/javase/tutorial/deployment/jar/run.html[run] the +standalone `ConsoleLauncher` as shown below. -Here's an example of its output: +[source,console,subs=attributes+] +---- +$ java -jar junit-platform-console-standalone-{platform-version}.jar execute -.... ├─ JUnit Vintage │ └─ example.JUnit4Tests │ └─ standardJUnit4Test ✔ @@ -553,21 +660,49 @@ Test run finished after 64 ms [ 0 tests aborted ] [ 5 tests successful ] [ 0 tests failed ] -.... +---- + +You can also run the standalone `ConsoleLauncher` as shown below (for example, to include +all jars in a directory): + +[source,console,subs=attributes+] +---- +$ java -cp classes:testlib/* org.junit.platform.console.ConsoleLauncher +---- .Exit Code NOTE: The `{ConsoleLauncher}` exits with a status code of `1` if any containers or tests failed. If no tests are discovered and the `--fail-if-no-tests` command-line option is -supplied, the `ConsoleLauncher` exits with a status code of `2`. Otherwise the exit code +supplied, the `ConsoleLauncher` exits with a status code of `2`. Otherwise, the exit code is `0`. [[running-tests-console-launcher-options]] -==== Options +==== Subcommands and Options + +The `{ConsoleLauncher}` provides the following subcommands: ---- include::{consoleLauncherOptionsFile}[] ---- +===== Discovering tests + +---- +include::{consoleLauncherDiscoverOptionsFile}[] +---- + +===== Executing tests + +---- +include::{consoleLauncherExecuteOptionsFile}[] +---- + +===== Listing test engines + +---- +include::{consoleLauncherEnginesOptionsFile}[] +---- + [[running-tests-console-launcher-argument-files]] ==== Argument Files (@-files) @@ -596,10 +731,50 @@ You can pass a real parameter with an initial `@` character by escaping it with additional `@` symbol. For example, `@@somearg` will become `@somearg` and will not be subject to expansion. +[[running-tests-console-launcher-color-customization]] +==== Color customization + +The colors used in the output of the `{ConsoleLauncher}` can be customized. +The option `--single-color` will apply a built-in monochrome style, while +`--color-palette` will accept a properties file to override the +https://en.wikipedia.org/wiki/ANSI_escape_code#Colors[ANSI SGR] color styling. +The properties file below demonstrates the default style: + +[source,properties,indent=0] +---- +SUCCESSFUL = 32 +ABORTED = 33 +FAILED = 31 +SKIPPED = 35 +CONTAINER = 35 +TEST = 34 +DYNAMIC = 35 +REPORTED = 37 +---- + [[running-tests-junit-platform-runner]] === Using JUnit 4 to run the JUnit Platform +[WARNING] +.The `JUnitPlatform` runner has been deprecated +==== +The `JUnitPlatform` runner was developed by the JUnit team as an interim solution for +running test suites and tests on the JUnit Platform in a JUnit 4 environment. + +In recent years, all mainstream build tools and IDEs provide built-in support for running +tests directly on the JUnit Platform. + +In addition, the introduction of `@Suite` support provided by the +`junit-platform-suite-engine` module makes the `JUnitPlatform` runner obsolete. See +<> for details. + +The `JUnitPlatform` runner and `@UseTechnicalNames` annotation have therefore been +deprecated in JUnit Platform 1.8 and will be removed in JUnit Platform 2.0. + +If you are using the `JUnitPlatform` runner, please migrate to the `@Suite` support. +==== + The `JUnitPlatform` runner is a JUnit 4 based `Runner` which enables you to run any test whose programming model is supported on the JUnit Platform in a JUnit 4 environment -- for example, a JUnit Jupiter test class. @@ -609,8 +784,7 @@ build systems that support JUnit 4 but do not yet support the JUnit Platform dir NOTE: Since the JUnit Platform has features that JUnit 4 does not have, the runner is only able to support a subset of the JUnit Platform functionality, especially with regard -to reporting (see <>). But for the -time being the `JUnitPlatform` runner is an easy way to get started. +to reporting (see <>). [[running-tests-junit-platform-runner-setup]] ==== Setup @@ -618,6 +792,7 @@ time being the `JUnitPlatform` runner is an easy way to get started. You need the following artifacts and their dependencies on the classpath. See <> for details regarding group IDs, artifact IDs, and versions. +[[running-tests-junit-platform-runner-setup-explicit-dependencies]] ===== Explicit Dependencies * `junit-platform-runner` in _test_ scope: location of the `JUnitPlatform` runner @@ -627,9 +802,11 @@ You need the following artifacts and their dependencies on the classpath. See * `junit-jupiter-engine` in _test runtime_ scope: implementation of the `TestEngine` API for JUnit Jupiter +[[running-tests-junit-platform-runner-setup-transitive-dependencies]] ===== Transitive Dependencies * `junit-platform-suite-api` in _test_ scope +* `junit-platform-suite-commons` in _test_ scope * `junit-platform-launcher` in _test_ scope * `junit-platform-engine` in _test_ scope * `junit-platform-commons` in _test_ scope @@ -639,14 +816,14 @@ You need the following artifacts and their dependencies on the classpath. See ==== Display Names vs. Technical Names To define a custom _display name_ for the class run via `@RunWith(JUnitPlatform.class)` -simply annotate the class with `@SuiteDisplayName` and provide a custom value. +annotate the class with `@SuiteDisplayName` and provide a custom value. By default, _display names_ will be used for test artifacts; however, when the `JUnitPlatform` runner is used to execute tests with a build tool such as Gradle or Maven, the generated test report often needs to include the _technical names_ of test artifacts — for example, fully qualified class names — instead of shorter display names like the simple name of a test class or a custom display name containing special -characters. To enable technical names for reporting purposes, simply declare the +characters. To enable technical names for reporting purposes, declare the `@UseTechnicalNames` annotation alongside `@RunWith(JUnitPlatform.class)`. Note that the presence of `@UseTechnicalNames` overrides any custom display name @@ -696,8 +873,8 @@ infrastructure. In addition to instructing the platform which test classes and test engines to include, which packages to scan, etc., it is sometimes necessary to provide additional custom -configuration parameters that are specific to a particular test engine or registered -extension. For example, the JUnit Jupiter `TestEngine` supports _configuration +configuration parameters that are specific to a particular test engine, listener, or +registered extension. For example, the JUnit Jupiter `TestEngine` supports _configuration parameters_ for the following use cases. - <> @@ -736,32 +913,64 @@ 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 will -be treated as a comma-separated list of patterns where each pattern will be matched +(`+++*+++`), the pattern will match against all candidate classes. Otherwise, the value +will be treated as a comma-separated list of patterns where each pattern will be matched against the fully qualified class name (_FQCN_) of each candidate class. Any dot (`.`) in a pattern will match against a dot (`.`) or a dollar sign (`$`) in a FQCN. Any asterisk -(`+*+`) will match against one or more characters in a FQCN. All other characters in a +(`+++*+++`) 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: -- `+*+`: matches all candidate classes. -- `+org.junit.*+`: matches all candidate classes under the `org.junit` base package and +- `+++*+++`: matches all candidate classes. +- `+++org.junit.*+++`: matches all candidate classes under the `org.junit` base package and any of its subpackages. -- `+*.MyCustomImpl+`: matches every candidate class whose simple class name is exactly +- `+++*.MyCustomImpl+++`: matches every candidate class whose simple class name is exactly `MyCustomImpl`. -- `+*System*+`: matches every candidate class whose FQCN contains `System`. -- `+*System*+, +*Unit*+`: matches every candidate class whose FQCN contains `System` or - `Unit`. +- `+++*System*+++`: matches every candidate class whose FQCN contains `System`. +- `+++*System*+++, +++*Unit*+++`: matches every candidate class whose FQCN contains + `System` or `Unit`. - `org.example.MyCustomImpl`: matches the candidate class whose FQCN is exactly `org.example.MyCustomImpl`. - `org.example.MyCustomImpl, org.example.TheirCustomImpl`: matches candidate classes whose FQCN is exactly `org.example.MyCustomImpl` or `org.example.TheirCustomImpl`. +[[running-tests-tags]] +=== Tags + +Tags are a JUnit Platform concept for marking and filtering tests. The programming model +for adding tags to containers and tests is defined by the testing framework. For example, +in JUnit Jupiter based tests, the `@Tag` annotation (see +<>) should be used. For JUnit 4 based tests, the +Vintage engine maps `@Category` annotations to tags (see +<>). Other testing frameworks may define their +own annotation or other means for users to specify tags. + +[[running-tests-tag-syntax-rules]] +==== Syntax Rules for Tags + +Regardless how a tag is specified, the JUnit Platform enforces the following rules: + +* A tag must not be `null` or _blank_. +* A _trimmed_ tag must not contain whitespace. +* A _trimmed_ tag must not contain ISO control characters. +* A _trimmed_ tag must not contain any of the following _reserved characters_. +- `,`: _comma_ +- `(`: _left parenthesis_ +- `)`: _right parenthesis_ +- `&`: _ampersand_ +- `|`: _vertical bar_ +- `!`: _exclamation point_ + +NOTE: In the above context, "trimmed" means that leading and trailing whitespace +characters have been removed. + [[running-tests-tag-expressions]] -=== Tag Expressions +==== Tag Expressions + Tag expressions are boolean expressions with the operators `!`, `&` and `|`. In addition, `(` and `)` can be used to adjust for operator precedence. @@ -788,19 +997,19 @@ expressions can be useful. | Tag Expression | Selection -| +product+ +| `+++product+++` | all tests for *product* -| +catalog \| shipping+ +| `+++catalog \| shipping+++` | all tests for *catalog* plus all tests for *shipping* -| +catalog & shipping+ +| `+++catalog & shipping+++` | all tests for the intersection between *catalog* and *shipping* -| +product & !end-to-end+ +| `+++product & !end-to-end+++` | all tests for *product*, but not the _end-to-end_ tests -| +(micro \| integration) & (product \| shipping)+ +| `+++(micro \| integration) & (product \| shipping)+++` | all _micro_ or _integration_ tests for *product* or *shipping* |=== @@ -808,7 +1017,7 @@ expressions can be useful. === Capturing Standard Output/Error Since version 1.3, the JUnit Platform provides opt-in support for capturing output -printed to `System.out` and `System.err`. To enable it, simply set the +printed to `System.out` and `System.err`. To enable it, set the `junit.platform.output.capture.stdout` and/or `junit.platform.output.capture.stderr` <> to `true`. In addition, you may configure the maximum number of buffered bytes to be used per executed test or container @@ -825,12 +1034,72 @@ because particularly when <> it would be impossible to attribute it to a specific test or container. -WARNING: Capturing output 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. - -[[running-tests-flight-recorder]] -=== Flight Recorder Support +[[running-tests-listeners]] +=== Using Listeners and Interceptors + +The JUnit Platform provides the following listener APIs that allow JUnit, third parties, +and custom user code to react to events fired at various points during the discovery and +execution of a `TestPlan`. + +* `{LauncherSessionListener}`: receives events when a `{LauncherSession}` is opened and + closed. +* `{LauncherInterceptor}`: intercepts test discovery and execution in the context of a + `LauncherSession`. +* `{LauncherDiscoveryListener}`: receives events that occur during test discovery. +* `{TestExecutionListener}`: receives events that occur during test execution. + +The `LauncherSessionListener` API is typically implemented by build tools or IDEs and +registered automatically for you in order to support some feature of the build tool or IDE. + +The `LauncherDiscoveryListener` and `TestExecutionListener` APIs are often implemented in +order to produce some form of report or to display a graphical representation of the test +plan in an IDE. Such listeners may be implemented and automatically registered by a build +tool or IDE, or they may be included in a third-party library – potentially registered +for you automatically. You can also implement and register your own listeners. + +For details on registering and configuring listeners, see the following sections of this +guide. + +* <> +* <> +* <> +* <> +* <> +* <> + +The JUnit Platform provides the following listeners which you may wish to use with your +test suite. + +<> :: + `{LegacyXmlReportGeneratingListener}` can be used via the + <> or registered manually to generate XML reports + compatible with the de facto standard for JUnit 4 based test reports. ++ +`{OpenTestReportGeneratingListener}` generates an XML report in the event-based format +specified by {OpenTestReporting}. It is auto-registered and can be enabled and +configured via <>. ++ +See <> for details. + +<> :: + `FlightRecordingExecutionListener` and `FlightRecordingDiscoveryListener` that generate + Java Flight Recorder events during test discovery and execution. + +`{LoggingListener}` :: + `TestExecutionListener` for logging informational messages for all events via a + `BiConsumer` that consumes `Throwable` and `Supplier`. + +`{SummaryGeneratingListener}` :: + `TestExecutionListener` that generates a summary of the test execution which can be + printed via a `PrintWriter`. + +`{UniqueIdTrackingListener}` :: + `TestExecutionListener` that that tracks the unique IDs of all tests that were skipped + or executed during the execution of the `TestPlan` and generates a file containing the + unique IDs once execution of the `TestPlan` has finished. + +[[running-tests-listeners-flight-recorder]] +==== Flight Recorder Support Since version 1.7, the JUnit Platform provides opt-in support for generating Flight Recorder events. https://openjdk.java.net/jeps/328[JEP 328] describes the Java Flight @@ -843,9 +1112,10 @@ to a problem. In order to record Flight Recorder events generated while running tests, you need to: -1. Provide module `org.junit.platform.jfr` (`junit-platform-jfr-{platform-version}.jar`) +1. Ensure that you are using either Java 8 Update 262 or higher or Java 11 or later. +2. Provide the `org.junit.platform.jfr` module (`junit-platform-jfr-{platform-version}.jar`) on the class-path or module-path at test runtime. -2. Start flight recording when launching a test run. Flight Recorder can be started via +3. Start flight recording when launching a test run. Flight Recorder can be started via java command line option: -XX:StartFlightRecording:filename=... @@ -857,6 +1127,22 @@ https://docs.oracle.com/en/java/javase/14/docs/specs/man/jfr.html[jfr] command line tool shipped with recent JDKs or open the recording file with https://jdk.java.net/jmc/[JDK Mission Control]. -WARNING: Flight Recorder support 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. +WARNING: Flight Recorder support 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. + +[[stacktrace-pruning]] +=== Stack Trace Pruning + +Since version 1.10, the JUnit Platform provides built-in support for pruning stack traces +produced by failing tests. This feature is enabled by default but can be disabled by +setting the `junit.platform.stacktrace.pruning.enabled` _configuration parameter_ to +`false`. + +When enabled, all calls from the `org.junit`, `jdk.internal.reflect`, and `sun.reflect` +packages are removed from the stack trace, unless the calls occur after the test itself +or any of its ancestors. For that reason, calls to `{Assertions}` or `{Assumptions}` will +never be excluded. + +In addition, all elements prior to and including the first call from the JUnit Platform +Launcher will be removed. diff --git a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc index 3c6a686f3038..0a0862b6f2b8 100644 --- a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc +++ b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc @@ -29,15 +29,16 @@ in the `junit-jupiter-api` module. | `@RepeatedTest` | Denotes that a method is a test template for a <>. Such methods are _inherited_ unless they are _overridden_. | `@TestFactory` | Denotes that a method is a test factory for <>. Such methods are _inherited_ unless they are _overridden_. | `@TestTemplate` | Denotes that a method is a <> designed to be invoked multiple times depending on the number of invocation contexts returned by the registered <>. Such methods are _inherited_ unless they are _overridden_. -| `@TestMethodOrder` | Used to configure the <> for the annotated test class; similar to JUnit 4's `@FixMethodOrder`. Such annotations are _inherited_. +| `@TestClassOrder` | Used to configure the <> for `@Nested` test classes in the annotated test class. Such annotations are _inherited_. +| `@TestMethodOrder` | Used to configure the <> for the annotated test class; similar to JUnit 4's `@FixMethodOrder`. Such annotations are _inherited_. | `@TestInstance` | Used to configure the <> for the annotated test class. Such annotations are _inherited_. | `@DisplayName` | Declares a custom <> for the test class or test method. Such annotations are not _inherited_. | `@DisplayNameGeneration` | Declares a custom <> for the test class. Such annotations are _inherited_. -| `@BeforeEach` | Denotes that the annotated method should be executed _before_ *each* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, or `@TestFactory` method in the current class; analogous to JUnit 4's `@Before`. Such methods are _inherited_ unless they are _overridden_. -| `@AfterEach` | Denotes that the annotated method should be executed _after_ *each* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, or `@TestFactory` method in the current class; analogous to JUnit 4's `@After`. Such methods are _inherited_ unless they are _overridden_. -| `@BeforeAll` | Denotes that the annotated method should be executed _before_ *all* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, and `@TestFactory` methods in the current class; analogous to JUnit 4's `@BeforeClass`. Such methods are _inherited_ (unless they are _hidden_ or _overridden_) and must be `static` (unless the "per-class" <> is used). -| `@AfterAll` | Denotes that the annotated method should be executed _after_ *all* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, and `@TestFactory` methods in the current class; analogous to JUnit 4's `@AfterClass`. Such methods are _inherited_ (unless they are _hidden_ or _overridden_) and must be `static` (unless the "per-class" <> is used). -| `@Nested` | Denotes that the annotated class is a non-static <>. `@BeforeAll` and `@AfterAll` methods cannot be used directly in a `@Nested` test class unless the "per-class" <> is used. Such annotations are not _inherited_. +| `@BeforeEach` | Denotes that the annotated method should be executed _before_ *each* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, or `@TestFactory` method in the current class; analogous to JUnit 4's `@Before`. Such methods are _inherited_ – unless they are _overridden_ or _superseded_ (i.e., replaced based on signature only, irrespective of Java's visibility rules). +| `@AfterEach` | Denotes that the annotated method should be executed _after_ *each* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, or `@TestFactory` method in the current class; analogous to JUnit 4's `@After`. Such methods are _inherited_ – unless they are _overridden_ or _superseded_ (i.e., replaced based on signature only, irrespective of Java's visibility rules). +| `@BeforeAll` | Denotes that the annotated method should be executed _before_ *all* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, and `@TestFactory` methods in the current class; analogous to JUnit 4's `@BeforeClass`. Such methods are _inherited_ – unless they are _hidden_, _overridden_, or _superseded_, (i.e., replaced based on signature only, irrespective of Java's visibility rules) – and must be `static` unless the "per-class" <> is used. +| `@AfterAll` | Denotes that the annotated method should be executed _after_ *all* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, and `@TestFactory` methods in the current class; analogous to JUnit 4's `@AfterClass`. Such methods are _inherited_ – unless they are _hidden_, _overridden_, or _superseded_, (i.e., replaced based on signature only, irrespective of Java's visibility rules) – and must be `static` unless the "per-class" <> is used. +| `@Nested` | Denotes that the annotated class is a non-static <>. On Java 8 through Java 15, `@BeforeAll` and `@AfterAll` methods cannot be used directly in a `@Nested` test class unless the "per-class" <> is used. Beginning with Java 16, `@BeforeAll` and `@AfterAll` methods can be declared as `static` in a `@Nested` test class with either test instance lifecycle mode. Such annotations are not _inherited_. | `@Tag` | Used to declare <>, either at the class or method level; analogous to test groups in TestNG or Categories in JUnit 4. Such annotations are _inherited_ at the class level but not at the method level. | `@Disabled` | Used to <> a test class or test method; analogous to JUnit 4's `@Ignore`. Such annotations are not _inherited_. | `@Timeout` | Used to fail a test, test factory, test template, or lifecycle method if its execution exceeds a given duration. Such annotations are _inherited_. @@ -96,27 +97,57 @@ void myFastTest() { } ---- -[[writing-tests-classes-and-methods]] -=== Test Classes and Methods +[[writing-tests-definitions]] +=== Definitions + +.Platform Concepts +**** +Container:: +a node in the test tree that contains other containers or tests as its children (e.g. a _test class_). -**Test Class**: any top-level class, `static` member class, or <> that contains at least one _test method_. +Test:: +a node in the test tree that verifies expected behavior when executed (e.g. a `@Test` method). +**** +.Jupiter Concepts +**** +Lifecycle Method:: +any method that is directly annotated or meta-annotated with +`@BeforeAll`, `@AfterAll`, `@BeforeEach`, or `@AfterEach`. + +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. -**Test Method**: any instance method that is directly annotated or meta-annotated with +Test Method:: +any instance method that is directly annotated or meta-annotated with `@Test`, `@RepeatedTest`, `@ParameterizedTest`, `@TestFactory`, or `@TestTemplate`. +With the exception of `@Test`, these create a _container_ in the test tree that groups +_tests_ or, potentially (for `@TestFactory`), other _containers_. +**** -**Lifecycle Method**: any method that is directly annotated or meta-annotated with -`@BeforeAll`, `@AfterAll`, `@BeforeEach`, or `@AfterEach`. +[[writing-tests-classes-and-methods]] +=== Test Classes and Methods Test methods and lifecycle methods may be declared locally within the current test class, inherited from superclasses, or inherited from interfaces (see <>). In addition, test methods and -lifecycle methods must not be `abstract` and must not return a value. +lifecycle methods must not be `abstract` and must not return a value (except `@TestFactory` +methods which are required to return a value). -NOTE: Test classes, test methods, and lifecycle methods are not required to be `public`, -but they must _not_ be `private`. +[NOTE] +.Class and method visibility +==== +Test classes, test methods, and lifecycle methods are not required to be `public`, but +they must _not_ be `private`. + +It is generally recommended to omit the `public` modifier for test classes, test methods, +and lifecycle methods unless there is a technical reason for doing so – for example, when +a test class is extended by a test class in another package. Another technical reason for +making classes and methods `public` is to simplify testing on the module path when using +the Java Module System. +==== The following test class demonstrates the use of `@Test` methods and all supported lifecycle methods. For further information on runtime semantics, see @@ -239,11 +270,10 @@ include::{testDir}/example/AssertionsDemo.java[tags=user_guide] [WARNING] .Preemptive Timeouts with `assertTimeoutPreemptively()` ==== -Contrary to <>, the various -`assertTimeoutPreemptively()` methods in the `Assertions` class execute the provided -`executable` or `supplier` in a different thread than that of the calling code. This -behavior can lead to undesirable side effects if the code that is executed within the -`executable` or `supplier` relies on `java.lang.ThreadLocal` storage. +The various `assertTimeoutPreemptively()` methods in the `Assertions` class execute +the provided `executable` or `supplier` in a different thread than that of the calling +code. This behavior can lead to undesirable side effects if the code that is executed +within the `executable` or `supplier` relies on `java.lang.ThreadLocal` storage. One common example of this is the transactional testing support in the Spring Framework. Specifically, Spring's testing support binds transaction state to the current thread (via @@ -388,17 +418,26 @@ the `org.junit.jupiter.api.condition` package. ==== [[writing-tests-conditional-execution-os]] -==== Operating System Conditions +==== Operating System and Architecture Conditions -A container or test may be enabled or disabled on a particular operating system via the -`{EnabledOnOs}` and `{DisabledOnOs}` annotations. +A container or test may be enabled or disabled on a particular operating system, +architecture, or combination of both via the `{EnabledOnOs}` and `{DisabledOnOs}` +annotations. [[writing-tests-conditional-execution-os-demo]] [source,java,indent=0] +.Conditional execution based on operating system ---- include::{testDir}/example/ConditionalTestExecutionDemo.java[tags=user_guide_os] ---- +[[writing-tests-conditional-execution-architectures-demo]] +[source,java,indent=0] +.Conditional execution based on architecture +---- +include::{testDir}/example/ConditionalTestExecutionDemo.java[tags=user_guide_architecture] +---- + [[writing-tests-conditional-execution-jre]] ==== Java Runtime Environment Conditions @@ -414,6 +453,21 @@ half open ranges. include::{testDir}/example/ConditionalTestExecutionDemo.java[tags=user_guide_jre] ---- +[[writing-tests-conditional-execution-native]] +==== Native Image Conditions + +A container or test may be enabled or disabled within a +https://www.graalvm.org/reference-manual/native-image/[GraalVM native image] via the +`{EnabledInNativeImage}` and `{DisabledInNativeImage}` annotations. These annotations are +typically used when running tests within a native image using the Gradle and Maven +plug-ins from the GraalVM https://graalvm.github.io/native-build-tools/latest/[Native +Build Tools] project. + +[source,java,indent=0] +---- +include::{testDir}/example/ConditionalTestExecutionDemo.java[tags=user_guide_native] +---- + [[writing-tests-conditional-execution-system-properties]] ==== System Property Conditions @@ -460,45 +514,67 @@ indirectly present, or meta-present on a given element. [[writing-tests-conditional-execution-custom]] ==== Custom Conditions -A container or test may be enabled or disabled based on the boolean return of a -method via the `{EnabledIf}` and `{DisabledIf}` annotations. The method is provided to -the annotation via its name, or its fully qualified name if located outside the test -class. -If needed, the condition method can take a single parameter of type `ExtensionContext`. +As an alternative to implementing an <>, a +container or test may be enabled or disabled based on a _condition method_ configured via +the `{EnabledIf}` and `{DisabledIf}` annotations. A condition method must have a `boolean` +return type and may accept either no arguments or a single `ExtensionContext` argument. + +The following test class demonstrates how to configure a local method named +`customCondition` via `@EnabledIf` and `@DisabledIf`. [source,java,indent=0] ---- include::{testDir}/example/ConditionalTestExecutionDemo.java[tags=user_guide_custom] ---- -NOTE: When `{EnabledIf}` or `{DisabledIf}` is used at class level, the condition method -must always be `static`. Condition methods located in external classes must also be -`static`. In any other case, you can use both static or instance methods. +Alternatively, the condition method can be located outside the test class. In this case, +it must be referenced by its _fully qualified name_ as demonstrated in the following +example. + +[source,java,indent=0] +---- +package example; + +include::{testDir}/example/ExternalCustomConditionDemo.java[tags=user_guide_external_custom_condition] +---- + +[NOTE] +==== +There are several cases where a condition method would need to be `static`: +- when `@EnabledIf` or `@DisabledIf` is used at class level +- when `@EnabledIf` or `@DisabledIf` is used on a `@ParameterizedTest` or a + `@TestTemplate` method +- when the condition method is located in an external class -[[writing-tests-tagging-and-filtering]] -=== Tagging and Filtering +In any other case, you can use either static methods or instance methods as condition +methods. +==== -Test classes and methods can be tagged via the `@Tag` annotation. Those tags can later be -used to filter <>. +[TIP] +==== +It is often the case that you can use an existing static method in a utility class as a +custom condition. -TIP: See also: <> +For example, `java.awt.GraphicsEnvironment` provides a `public static boolean isHeadless()` +method that can be used to determine if the current environment does not support a +graphical display. Thus, if you have a test that depends on graphical support you can +disable it when such support is unavailable as follows. -==== Syntax Rules for Tags +[source,java,indent=0] +---- +@DisabledIf(value = "java.awt.GraphicsEnvironment#isHeadless", + disabledReason = "headless environment") +---- +==== -* A tag must not be `null` or _blank_. -* A _trimmed_ tag must not contain whitespace. -* A _trimmed_ tag must not contain ISO control characters. -* A _trimmed_ tag must not contain any of the following _reserved characters_. - - `,`: _comma_ - - `(`: _left parenthesis_ - - `)`: _right parenthesis_ - - `&`: _ampersand_ - - `|`: _vertical bar_ - - `!`: _exclamation point_ +[[writing-tests-tagging-and-filtering]] +=== Tagging and Filtering -NOTE: In the above context, "trimmed" means that leading and trailing whitespace -characters have been removed. +Test classes and methods can be tagged via the `@Tag` annotation. Those tags can later be +used to filter <>. Please refer to the +<> section for more information about tag support in the JUnit +Platform. [source,java,indent=0] ---- @@ -511,11 +587,15 @@ custom annotations for tags. [[writing-tests-test-execution-order]] === Test Execution Order -By default, test methods will be ordered using an algorithm that is deterministic but -intentionally nonobvious. This ensures that subsequent runs of a test suite execute test -methods in the same order, thereby allowing for repeatable builds. +By default, test classes and methods will be ordered using an algorithm that is +deterministic but intentionally nonobvious. This ensures that subsequent runs of a test +suite execute test classes and test methods in the same order, thereby allowing for +repeatable builds. -NOTE: See <> for a definition of _test method_. +NOTE: See <> for a definition of _test method_ and _test class_. + +[[writing-tests-test-execution-order-methods]] +==== Method Order Although true _unit tests_ typically should not rely on the order in which they are executed, there are times when it is necessary to enforce a specific test method execution @@ -528,17 +608,18 @@ interface with `{TestMethodOrder}` and specify the desired `{MethodOrderer}` implementation. You can implement your own custom `MethodOrderer` or use one of the following built-in `MethodOrderer` implementations. -* `{DisplayName}`: sorts test methods _alphanumerically_ based on their display names (see - <>) -* `{MethodName}`: sorts test methods _alphanumerically_ based on their method name and formal - parameter lists. -* `{OrderAnnotation}`: sorts test methods _numerically_ based on values specified via the - `{Order}` annotation. -* `{Random}`: orders test methods _pseudo-randomly_ and supports configuration of a custom - _seed_. -* `{Alphanumeric}`: sorts test methods _alphanumerically_ based on their names and formal - parameter lists. **Deprecated in favor of `{MethodName}`. Will be removed in 6.0** +* `{MethodOrderer_DisplayName}`: sorts test methods _alphanumerically_ based on their + display names (see <>) +* `{MethodOrderer_MethodName}`: sorts test methods _alphanumerically_ based on their names + and formal parameter lists +* `{MethodOrderer_OrderAnnotation}`: sorts test methods _numerically_ based on values + specified via the `{Order}` annotation +* `{MethodOrderer_Random}`: orders test methods _pseudo-randomly_ and supports + configuration of a custom _seed_ +* `{MethodOrderer_Alphanumeric}`: sorts test methods _alphanumerically_ based on their + names and formal parameter lists; **deprecated in favor of `{MethodOrderer_MethodName}`, + to be removed in 6.0** NOTE: See also: <> @@ -550,8 +631,8 @@ order specified via the `@Order` annotation. include::{testDir}/example/OrderedTestsDemo.java[tags=user_guide] ---- -[[writing-tests-test-execution-order-default]] -==== Setting the Default Method Orderer +[[writing-tests-test-execution-order-methods-default]] +===== Setting the Default Method Orderer You can use the `junit.jupiter.testmethod.order.default` <> to specify the fully qualified class name of the @@ -560,9 +641,9 @@ via the `{TestMethodOrder}` annotation, the supplied class has to implement the `MethodOrderer` interface. The default orderer will be used for all tests unless the `@TestMethodOrder` annotation is present on an enclosing test class or test interface. -For example, to use the `{OrderAnnotation}` method orderer by default, you should set the -configuration parameter to the corresponding fully qualified class name (e.g., in -`src/test/resources/junit-platform.properties`): +For example, to use the `{MethodOrderer_OrderAnnotation}` method orderer by default, you +should set the configuration parameter to the corresponding fully qualified class name +(e.g., in `src/test/resources/junit-platform.properties`): [source,properties,indent=0] ---- @@ -573,14 +654,81 @@ junit.jupiter.testmethod.order.default = \ Similarly, you can specify the fully qualified name of any custom class that implements `MethodOrderer`. +[[writing-tests-test-execution-order-classes]] +==== Class Order + +Although test classes typically should not rely on the order in which they are executed, +there are times when it is desirable to enforce a specific test class execution order. You +may wish to execute test classes in a random order to ensure there are no accidental +dependencies between test classes, or you may wish to order test classes to optimize build +time as outlined in the following scenarios. + +* Run previously failing tests and faster tests first: "fail fast" mode +* With parallel execution enabled, schedule longer tests first: "shortest test plan + execution duration" mode +* Various other use cases + +To configure test class execution order _globally_ for the entire test suite, use the +`junit.jupiter.testclass.order.default` <> to specify the fully qualified class name of the `{ClassOrderer}` you would +like to use. The supplied class must implement the `ClassOrderer` interface. + +You can implement your own custom `ClassOrderer` or use one of the following built-in +`ClassOrderer` implementations. + +* `{ClassOrderer_ClassName}`: sorts test classes _alphanumerically_ based on their fully + qualified class names +* `{ClassOrderer_DisplayName}`: sorts test classes _alphanumerically_ based on their + display names (see <>) +* `{ClassOrderer_OrderAnnotation}`: sorts test classes _numerically_ based on values + specified via the `{Order}` annotation +* `{ClassOrderer_Random}`: orders test classes _pseudo-randomly_ and supports + configuration of a custom _seed_ + +For example, for the `@Order` annotation to be honored on _test classes_, you should +configure the `{ClassOrderer_OrderAnnotation}` class orderer using the configuration +parameter with the corresponding fully qualified class name (e.g., in +`src/test/resources/junit-platform.properties`): + +[source,properties,indent=0] +---- +junit.jupiter.testclass.order.default = \ + org.junit.jupiter.api.ClassOrderer$OrderAnnotation +---- + +The configured `ClassOrderer` will be applied to all top-level test classes (including +`static` nested test classes) and `@Nested` test classes. + +NOTE: Top-level test classes will be ordered relative to each other; whereas, `@Nested` +test classes will be ordered relative to other `@Nested` test classes sharing the same +_enclosing class_. + +To configure test class execution order _locally_ for `@Nested` test classes, declare the +`{TestClassOrder}` annotation on the enclosing class for the `@Nested` test classes you +want to order, and supply a class reference to the `ClassOrderer` implementation you would +like to use directly in the `@TestClassOrder` annotation. The configured `ClassOrderer` +will be applied recursively to `@Nested` test classes and their `@Nested` test classes. +Note that a local `@TestClassOrder` declaration always overrides an inherited +`@TestClassOrder` declaration or a `ClassOrderer` configured globally via the +`junit.jupiter.testclass.order.default` configuration parameter. + +The following example demonstrates how to guarantee that `@Nested` test classes are +executed in the order specified via the `@Order` annotation. + +[source,java,indent=0] +---- +include::{testDir}/example/OrderedNestedTestClassesDemo.java[tags=user_guide] +---- + [[writing-tests-test-instance-lifecycle]] === Test Instance Lifecycle In order to allow individual test methods to be executed in isolation and to avoid unexpected side effects due to mutable test instance state, JUnit creates a new instance of each test class before executing each _test method_ (see -<>). This "per-method" test instance lifecycle is the -default behavior in JUnit Jupiter and is analogous to all previous versions of JUnit. +<>). This "per-method" test instance lifecycle is the default +behavior in JUnit Jupiter and is analogous to all previous versions of JUnit. NOTE: Please note that the test class will still be instantiated if a given _test method_ is _disabled_ via a <> (e.g., `@Disabled`, @@ -598,9 +746,13 @@ Specifically, with the "per-class" mode it becomes possible to declare `@BeforeA "per-class" mode therefore also makes it possible to use `@BeforeAll` and `@AfterAll` methods in `@Nested` test classes. +NOTE: Beginning with Java 16, `@BeforeAll` and `@AfterAll` methods can be declared as +`static` in `@Nested` test classes. + If you are authoring tests using the Kotlin programming language, you may also find it -easier to implement `@BeforeAll` and `@AfterAll` methods by switching to the "per-class" -test instance lifecycle mode. +easier to implement non-static `@BeforeAll` and `@AfterAll` lifecycle methods as well as +`@MethodSource` factory methods by switching to the "per-class" test instance lifecycle +mode. [[writing-tests-test-instance-lifecycle-changing-default]] ==== Changing the Default Test Instance Lifecycle @@ -642,7 +794,9 @@ configuration file instead of via a JVM system property. === Nested Tests `@Nested` tests give the test writer more capabilities to express the relationship among -several groups of tests. Here's an elaborate example. +several groups of tests. Such nested tests make use of Java's nested classes and +facilitate hierarchical thinking about the test structure. Here's an elaborate example, +both as source code and as a screenshot of the execution within an IDE. [source,java,indent=0] .Nested test suite for testing a stack @@ -650,13 +804,29 @@ several groups of tests. Here's an elaborate example. include::{testDir}/example/TestingAStackDemo.java[tags=user_guide] ---- +When executing this example in an IDE, the test execution tree in the GUI will look +similar to the following image. + +image::writing-tests_nested_test_ide.png[caption='',title='Executing a nested test in an IDE'] + +In this example, preconditions from outer tests are used in inner tests by defining +hierarchical lifecycle methods for the setup code. For example, `createNewStack()` is a +`@BeforeEach` lifecycle method that is used in the test class in which it is defined and +in all levels in the nesting tree below the class in which it is defined. + +The fact that setup code from outer tests is run before inner tests are executed gives you +the ability to run all tests independently. You can even run inner tests alone without +running the outer tests, because the setup code from the outer tests is always executed. + NOTE: _Only non-static nested classes_ (i.e. _inner classes_) can serve as `@Nested` test -classes. Nesting can be arbitrarily deep, and those inner classes are considered to be -full members of the test class family with one exception: `@BeforeAll` and `@AfterAll` -methods do not work _by default_. The reason is that Java does not allow `static` members -in inner classes. However, this restriction can be circumvented by annotating a `@Nested` -test class with `@TestInstance(Lifecycle.PER_CLASS)` (see -<>). +classes. Nesting can be arbitrarily deep, and those inner classes are subject to full +lifecycle support with one exception: `@BeforeAll` and `@AfterAll` methods do not work _by +default_. The reason is that Java does not allow `static` members in inner classes prior +to Java 16. However, this restriction can be circumvented by annotating a `@Nested` test +class with `@TestInstance(Lifecycle.PER_CLASS)` (see +<>). If you are using Java 16 or higher, +`@BeforeAll` and `@AfterAll` methods can be declared as `static` in `@Nested` test +classes, and this restriction no longer applies. [[writing-tests-dependency-injection]] === Dependency Injection for Constructors and Methods @@ -669,8 +839,8 @@ constructors and methods. `{ParameterResolver}` defines the API for test extensions that wish to _dynamically_ resolve parameters at runtime. If a _test class_ constructor, a _test method_, or a -_lifecycle method_ (see <>) accepts a parameter, the -parameter must be resolved at runtime by a registered `ParameterResolver`. +_lifecycle method_ (see <>) accepts a parameter, the parameter +must be resolved at runtime by a registered `ParameterResolver`. There are currently three built-in resolvers that are registered automatically. @@ -691,13 +861,13 @@ following demonstrates how to have `TestInfo` injected into a test constructor, include::{testDir}/example/TestInfoDemo.java[tags=user_guide] ---- -* `{RepetitionInfoParameterResolver}`: if a method parameter in a `@RepeatedTest`, - `@BeforeEach`, or `@AfterEach` method is of type `{RepetitionInfo}`, the - `RepetitionInfoParameterResolver` will supply an instance of `RepetitionInfo`. - `RepetitionInfo` can then be used to retrieve information about the current repetition - and the total number of repetitions for the corresponding `@RepeatedTest`. Note, - however, that `RepetitionInfoParameterResolver` is not registered outside the context - of a `@RepeatedTest`. See <>. +* `{RepetitionExtension}`: if a method parameter in a `@RepeatedTest`, `@BeforeEach`, or + `@AfterEach` method is of type `{RepetitionInfo}`, the `RepetitionExtension` will supply + an instance of `RepetitionInfo`. `RepetitionInfo` can then be used to retrieve + information about the current repetition, the total number of repetitions, the number of + repetitions that have failed, and the failure threshold for the corresponding + `@RepeatedTest`. Note, however, that `RepetitionExtension` is not registered outside the + context of a `@RepeatedTest`. See <>. * `{TestReporterParameterResolver}`: if a constructor or method parameter is of type `{TestReporter}`, the `TestReporterParameterResolver` will supply an instance of @@ -849,10 +1019,32 @@ void repeatedTest() { } ---- -In addition to specifying the number of repetitions, a custom display name can be -configured for each repetition via the `name` attribute of the `@RepeatedTest` -annotation. Furthermore, the display name can be a pattern composed of a combination of -static text and dynamic placeholders. The following placeholders are currently supported. +Since JUnit Jupiter 5.10, `@RepeatedTest` can be configured with a failure threshold which +signifies the number of failures after which remaining repetitions will be automatically +skipped. Set the `failureThreshold` attribute to a positive number less than the total +number of repetitions in order to skip the invocations of remaining repetitions after the +specified number of failures has been encountered. + +For example, if you are using `@RepeatedTest` to repeatedly invoke a test that you suspect +to be _flaky_, a single failure is sufficient to demonstrate that the test is flaky, and +there is no need to invoke the remaining repetitions. To support that specific use case, +set `failureThreshold = 1`. You can alternatively set the threshold to a number greater +than 1 depending on your use case. + +By default, the `failureThreshold` attribute is set to `Integer.MAX_VALUE`, signaling that +no failure threshold will be applied, which effectively means that the specified number of +repetitions will be invoked regardless of whether any repetitions fail. + +WARNING: If the repetitions of a `@RepeatedTest` method are executed in parallel, no +guarantees can be made regarding the failure threshold. It is therefore recommended that a +`@RepeatedTest` method be annotated with `@Execution(SAME_THREAD)` when parallel execution +is configured. See <> for further details. + +In addition to specifying the number of repetitions and failure threshold, a custom +display name can be configured for each repetition via the `name` attribute of the +`@RepeatedTest` annotation. Furthermore, the display name can be a pattern composed of a +combination of static text and dynamic placeholders. The following placeholders are +currently supported. - `{displayName}`: display name of the `@RepeatedTest` method - `{currentRepetition}`: the current repetition count @@ -868,9 +1060,10 @@ latter is equal to `"{displayName} :: repetition {currentRepetition} of {totalRepetitions}"` which results in display names for individual repetitions like `repeatedTest() :: repetition 1 of 10`, `repeatedTest() :: repetition 2 of 10`, etc. -In order to retrieve information about the current repetition and the total number of -repetitions programmatically, a developer can choose to have an instance of -`RepetitionInfo` injected into a `@RepeatedTest`, `@BeforeEach`, or `@AfterEach` method. +In order to retrieve information about the current repetition, the total number of +repetitions, the number of repetitions that have failed, and the failure threshold, a +developer can choose to have an instance of `{RepetitionInfo}` injected into a +`@RepeatedTest`, `@BeforeEach`, or `@AfterEach` method. [[writing-tests-repeated-tests-examples]] ==== Repeated Test Examples @@ -878,11 +1071,15 @@ repetitions programmatically, a developer can choose to have an instance of The `RepeatedTestsDemo` class at the end of this section demonstrates several examples of repeated tests. -The `repeatedTest()` method is identical to example from the previous section; whereas, +The `repeatedTest()` method is identical to the example from the previous section; whereas, `repeatedTestWithRepetitionInfo()` demonstrates how to have an instance of `RepetitionInfo` injected into a test to access the total number of repetitions for the current repeated test. +`repeatedTestWithFailureThreshold()` demonstrates how to set a failure threshold and +simulates an unexpected failure for every second repetition. The resulting behavior can be +viewed in the `ConsoleLauncher` output at the end of this section. + The next two methods demonstrate how to include a custom `@DisplayName` for the `@RepeatedTest` method in the display name of each repetition. `customDisplayName()` combines a custom display name with a custom pattern and then uses `TestInfo` to verify @@ -918,6 +1115,10 @@ INFO: About to execute repetition 2 of 5 for repeatedTestWithRepetitionInfo INFO: About to execute repetition 3 of 5 for repeatedTestWithRepetitionInfo INFO: About to execute repetition 4 of 5 for repeatedTestWithRepetitionInfo INFO: About to execute repetition 5 of 5 for repeatedTestWithRepetitionInfo +INFO: About to execute repetition 1 of 8 for repeatedTestWithFailureThreshold +INFO: About to execute repetition 2 of 8 for repeatedTestWithFailureThreshold +INFO: About to execute repetition 3 of 8 for repeatedTestWithFailureThreshold +INFO: About to execute repetition 4 of 8 for repeatedTestWithFailureThreshold INFO: About to execute repetition 1 of 1 for customDisplayName INFO: About to execute repetition 1 of 1 for customDisplayNameWithLongPattern INFO: About to execute repetition 1 of 5 for repeatedTestInGerman @@ -954,6 +1155,15 @@ When using the `ConsoleLauncher` with the unicode theme enabled, execution of │ │ ├─ repetition 3 of 5 ✔ │ │ ├─ repetition 4 of 5 ✔ │ │ └─ repetition 5 of 5 ✔ +│ ├─ repeatedTestWithFailureThreshold(RepetitionInfo) ✔ +│ │ ├─ repetition 1 of 8 ✔ +│ │ ├─ repetition 2 of 8 ✘ Boom! +│ │ ├─ repetition 3 of 8 ✔ +│ │ ├─ repetition 4 of 8 ✘ Boom! +│ │ ├─ repetition 5 of 8 ↷ Failure threshold [2] exceeded +│ │ ├─ repetition 6 of 8 ↷ Failure threshold [2] exceeded +│ │ ├─ repetition 7 of 8 ↷ Failure threshold [2] exceeded +│ │ └─ repetition 8 of 8 ↷ Failure threshold [2] exceeded │ ├─ Repeat! ✔ │ │ └─ Repeat! 1/1 ✔ │ ├─ Details... ✔ @@ -995,9 +1205,6 @@ palindromes(String) ✔ └─ [3] candidate=able was I ere I saw elba ✔ .... -WARNING: Parameterized tests are currently an _experimental_ feature. Consult the table -in <> for details. - [[writing-tests-parameterized-tests-setup]] ==== Required Setup @@ -1027,6 +1234,21 @@ parameterized method at the same index in the method's formal parameter list. An _aggregator_ is any parameter of type `ArgumentsAccessor` or any parameter annotated with `@AggregateWith`. +[NOTE] +.AutoCloseable arguments +==== +Arguments that implement `java.lang.AutoCloseable` (or `java.io.Closeable` which extends +`java.lang.AutoCloseable`) will be automatically closed after `@AfterEach` methods and +`AfterEachCallback` extensions have been called for the current parameterized test +invocation. + +To prevent this from happening, set the `autoCloseArguments` attribute in +`@ParameterizedTest` to `false`. Specifically, if an argument that implements +`AutoCloseable` is reused for multiple invocations of the same parameterized test method, +you must annotate the method with `@ParameterizedTest(autoCloseArguments = false)` to +ensure that the argument is not closed between invocations. +==== + [[writing-tests-parameterized-tests-sources]] ==== Sources of Arguments @@ -1074,11 +1296,13 @@ for parameterized tests that accept a single argument. * `{NullSource}`: provides a single `null` argument to the annotated `@ParameterizedTest` method. - `@NullSource` cannot be used for a parameter that has a primitive type. -* `{EmptySource}`: provides a single _empty_ argument to the annotated `@ParameterizedTest` - method for parameters of the following types: `java.lang.String`, `java.util.List`, - `java.util.Set`, `java.util.Map`, primitive arrays (e.g., `int[]`, `char[][]`, etc.), - object arrays (e.g.,`String[]`, `Integer[][]`, etc.). - - Subtypes of the supported types are not supported. +* `{EmptySource}`: provides a single _empty_ argument to the annotated + `@ParameterizedTest` method for parameters of the following types: `java.lang.String`, + `java.util.Collection` (and concrete subtypes with a `public` no-arg constructor), + `java.util.List`, `java.util.Set`, `java.util.SortedSet`, `java.util.NavigableSet`, + `java.util.Map` (and concrete subtypes with a `public` no-arg constructor), + `java.util.SortedMap`, `java.util.NavigableMap`, primitive arrays (e.g., `int[]`, + `char[][]`, etc.), object arrays (e.g., `String[]`, `Integer[][]`, etc.). * `{NullAndEmptySource}`: a _composed annotation_ that combines the functionality of `@NullSource` and `@EmptySource`. @@ -1161,7 +1385,7 @@ or external classes. Factory methods within the test class must be `static` unless the test class is annotated with `@TestInstance(Lifecycle.PER_CLASS)`; whereas, factory methods in external classes -must always be `static`. In addition, such factory methods must not accept any arguments. +must always be `static`. Each factory method must generate a _stream_ of _arguments_, and each set of arguments within the stream will be provided as the physical arguments for individual invocations @@ -1211,8 +1435,8 @@ interface. In addition, `Arguments.of(Object...)` may be used as an alternative include::{testDir}/example/ParameterizedTestDemo.java[tags=multi_arg_MethodSource_example] ---- -An external, `static` _factory_ method can be referenced by providing its _fully -qualified method name_ as demonstrated in the following example. +An external, `static` _factory_ method can be referenced by providing its _fully qualified +method name_ as demonstrated in the following example. [source,java,indent=0] ---- @@ -1221,11 +1445,29 @@ package example; include::{testDir}/example/ExternalMethodSourceDemo.java[tags=external_MethodSource_example] ---- +Factory methods can declare parameters, which will be provided by registered +implementations of the `ParameterResolver` extension API. In the following example, the +factory method is referenced by its name since there is only one such method in the test +class. If there are several local methods with the same name, parameters can also be +provided to differentiate them – for example, `@MethodSource("factoryMethod()")` or +`@MethodSource("factoryMethod(java.lang.String)")`. Alternatively, the factory method +can be referenced by its fully qualified method name, e.g. +`@MethodSource("example.MyTests#factoryMethod(java.lang.String)")`. + +[source,java,indent=0] +---- +include::{testDir}/example/MethodSourceParameterResolutionDemo.java[tags=parameter_resolution_MethodSource_example] +---- + + [[writing-tests-parameterized-tests-sources-CsvSource]] ===== @CsvSource -`@CsvSource` allows you to express argument lists as comma-separated values (i.e., -`String` literals). +`@CsvSource` allows you to express argument lists as comma-separated values (i.e., CSV +`String` literals). Each string provided via the `value` attribute in `@CsvSource` +represents a CSV record and results in one invocation of the parameterized test. The first +record may optionally be used to supply CSV headers (see the Javadoc for the +`useHeadersInDisplayName` attribute for details and an example). [source,java,indent=0] ---- @@ -1237,33 +1479,115 @@ The default delimiter is a comma (`,`), but you can use another character by set `String` delimiter instead of a single character. However, both delimiter attributes cannot be set simultaneously. -`@CsvSource` uses a single quote `'` as its quote character. See the `'lemon, lime'` value -in the example above and in the table below. An empty, quoted value `''` results in an -empty `String` unless the `emptyValue` attribute is set; whereas, an entirely _empty_ -value is interpreted as a `null` reference. By specifying one or more `nullValues`, a -custom value can be interpreted as a `null` reference (see the `NIL` example in the table -below). An `ArgumentConversionException` is thrown if the target type of a `null` -reference is a primitive type. +By default, `@CsvSource` uses a single quote (`'`) as its quote character, but this can be +changed via the `quoteCharacter` attribute. See the `'lemon, lime'` value in the example +above and in the table below. An empty, quoted value (`''`) results in an empty `String` +unless the `emptyValue` attribute is set; whereas, an entirely _empty_ value is +interpreted as a `null` reference. By specifying one or more `nullValues`, a custom value +can be interpreted as a `null` reference (see the `NIL` example in the table below). An +`ArgumentConversionException` is thrown if the target type of a `null` reference is a +primitive type. NOTE: An _unquoted_ empty value will always be converted to a `null` reference regardless of any custom values configured via the `nullValues` attribute. +Except within a quoted string, leading and trailing whitespace in a CSV column is trimmed +by default. This behavior can be changed by setting the +`ignoreLeadingAndTrailingWhitespace` attribute to `true`. + [cols="50,50"] |=== -| Example Input | Resulting Argument List - -| `@CsvSource({ "apple, banana" })` | `"apple"`, `"banana"` -| `@CsvSource({ "apple, 'lemon, lime'" })` | `"apple"`, `"lemon, lime"` -| `@CsvSource({ "apple, ''" })` | `"apple"`, `""` -| `@CsvSource({ "apple, " })` | `"apple"`, `null` -| `@CsvSource(value = { "apple, banana, NIL" }, nullValues = "NIL")` | `"apple"`, `"banana"`, `null` +| Example Input | Resulting Argument List + +| `@CsvSource({ "apple, banana" })` | `"apple"`, `"banana"` +| `@CsvSource({ "apple, 'lemon, lime'" })` | `"apple"`, `"lemon, lime"` +| `@CsvSource({ "apple, ''" })` | `"apple"`, `""` +| `@CsvSource({ "apple, " })` | `"apple"`, `null` +| `@CsvSource(value = { "apple, banana, NIL" }, nullValues = "NIL")` | `"apple"`, `"banana"`, `null` +| `@CsvSource(value = { " apple , banana" }, ignoreLeadingAndTrailingWhitespace = false)` | `" apple "`, `" banana"` |=== +If the programming language you are using supports _text blocks_ -- for example, Java SE +15 or higher -- you can alternatively use the `textBlock` attribute of `@CsvSource`. Each +record within a text block represents a CSV record and results in one invocation of the +parameterized test. The first record may optionally be used to supply CSV headers by +setting the `useHeadersInDisplayName` attribute to `true` as in the example below. + +Using a text block, the previous example can be implemented as follows. + +[source,java,indent=0] +---- +@ParameterizedTest(name = "[{index}] {arguments}") +@CsvSource(useHeadersInDisplayName = true, textBlock = """ + FRUIT, RANK + apple, 1 + banana, 2 + 'lemon, lime', 0xF1 + strawberry, 700_000 + """) +void testWithCsvSource(String fruit, int rank) { + // ... +} +---- + +The generated display names for the previous example include the CSV header names. + +---- +[1] FRUIT = apple, RANK = 1 +[2] FRUIT = banana, RANK = 2 +[3] FRUIT = lemon, lime, RANK = 0xF1 +[4] FRUIT = strawberry, RANK = 700_000 +---- + +In contrast to CSV records supplied via the `value` attribute, a text block can contain +comments. Any line beginning with a `+++#+++` symbol will be treated as a comment and +ignored. Note, however, that the `+++#+++` symbol must be the first character on the line +without any leading whitespace. It is therefore recommended that the closing text block +delimiter (`"""`) be placed either at the end of the last line of input or on the +following line, left aligned with the rest of the input (as can be seen in the example +below which demonstrates formatting similar to a table). + +[source,java,indent=0] +---- +@ParameterizedTest +@CsvSource(delimiter = '|', quoteCharacter = '"', textBlock = """ + #----------------------------- + # FRUIT | RANK + #----------------------------- + apple | 1 + #----------------------------- + banana | 2 + #----------------------------- + "lemon lime" | 0xF1 + #----------------------------- + strawberry | 700_000 + #----------------------------- + """) +void testWithCsvSource(String fruit, int rank) { + // ... +} +---- + +[NOTE] +==== +Java's https://docs.oracle.com/en/java/javase/15/text-blocks/index.html[text block] +feature automatically removes _incidental whitespace_ when the code is compiled. +However other JVM languages such as Groovy and Kotlin do not. Thus, if you are using a +programming language other than Java and your text block contains comments or new lines +within quoted strings, you will need to ensure that there is no leading whitespace within +your text block. +==== + [[writing-tests-parameterized-tests-sources-CsvFileSource]] ===== @CsvFileSource -`@CsvFileSource` lets you use CSV files from the classpath or the local file system. Each -line from a CSV file results in one invocation of the parameterized test. +`@CsvFileSource` lets you use comma-separated value (CSV) files from the classpath or the +local file system. Each record from a CSV file results in one invocation of the +parameterized test. The first record may optionally be used to supply CSV headers. You can +instruct JUnit to ignore the headers via the `numLinesToSkip` attribute. If you would like +for the headers to be used in the display names, you can set the `useHeadersInDisplayName` +attribute to `true`. The examples below demonstrate the use of `numLinesToSkip` and +`useHeadersInDisplayName`. The default delimiter is a comma (`,`), but you can use another character by setting the `delimiter` attribute. Alternatively, the `delimiterString` attribute allows you to use a @@ -1271,8 +1595,8 @@ The default delimiter is a comma (`,`), but you can use another character by set cannot be set simultaneously. .Comments in CSV files -NOTE: Any line beginning with a `#` symbol will be interpreted as a comment and will be -ignored. +NOTE: Any line beginning with a `+++#+++` symbol will be interpreted as a comment and will +be ignored. [source,java,indent=0] ---- @@ -1285,17 +1609,42 @@ include::{testDir}/example/ParameterizedTestDemo.java[tags=CsvFileSource_example include::{testResourcesDir}/two-column.csv[] ---- -In contrast to the syntax used in `@CsvSource`, `@CsvFileSource` uses a double quote `"` -as the quote character. See the `"United States of America"` value in the example above. -An empty, quoted value `""` results in an empty `String` unless the `emptyValue` attribute -is set; whereas, an entirely _empty_ value is interpreted as a `null` reference. By -specifying one or more `nullValues`, a custom value can be interpreted as a `null` -reference. An `ArgumentConversionException` is thrown if the target type of a `null` -reference is a primitive type. +The following listing shows the generated display names for the first two parameterized +test methods above. + +---- +[1] country=Sweden, reference=1 +[2] country=Poland, reference=2 +[3] country=United States of America, reference=3 +[4] country=France, reference=700_000 +---- + +The following listing shows the generated display names for the last parameterized test +method above that uses CSV header names. + +---- +[1] COUNTRY = Sweden, REFERENCE = 1 +[2] COUNTRY = Poland, REFERENCE = 2 +[3] COUNTRY = United States of America, REFERENCE = 3 +[4] COUNTRY = France, REFERENCE = 700_000 +---- + +In contrast to the default syntax used in `@CsvSource`, `@CsvFileSource` uses a double +quote (`+++"+++`) as the quote character by default, but this can be changed via the +`quoteCharacter` attribute. See the `"United States of America"` value in the example +above. An empty, quoted value (`+++""+++`) results in an empty `String` unless the +`emptyValue` attribute is set; whereas, an entirely _empty_ value is interpreted as a +`null` reference. By specifying one or more `nullValues`, a custom value can be +interpreted as a `null` reference. An `ArgumentConversionException` is thrown if the +target type of a `null` reference is a primitive type. NOTE: An _unquoted_ empty value will always be converted to a `null` reference regardless of any custom values configured via the `nullValues` attribute. +Except within a quoted string, leading and trailing whitespace in a CSV column is trimmed +by default. This behavior can be changed by setting the +`ignoreLeadingAndTrailingWhitespace` attribute to `true`. + [[writing-tests-parameterized-tests-sources-ArgumentsSource]] ===== @ArgumentsSource @@ -1313,6 +1662,9 @@ include::{testDir}/example/ParameterizedTestDemo.java[tags=ArgumentsSource_examp include::{testDir}/example/ParameterizedTestDemo.java[tags=ArgumentsProvider_example] ---- +If you wish to implement a custom `ArgumentsProvider` that also consumes an annotation +(like built-in providers such as `{ValueArgumentsProvider}` or `{CsvArgumentsProvider}`), +you have the possibility to extend the `{AnnotationBasedArgumentsProvider}` class. [[writing-tests-parameterized-tests-argument-conversion]] ==== Argument Conversion @@ -1353,7 +1705,7 @@ integral types: `byte`, `short`, `int`, `long`, and their boxed counterparts. |=== | Target Type | Example -| `boolean`/`Boolean` | `"true"` -> `true` +| `boolean`/`Boolean` | `"true"` -> `true` _(only accepts values 'true' or 'false', case-insensitive)_ | `byte`/`Byte` | `"15"`, `"0xF"`, or `"017"` -> `(byte) 15` | `char`/`Character` | `"o"` -> `'o'` | `short`/`Short` | `"15"`, `"0xF"`, or `"017"` -> `(short) 15` @@ -1369,7 +1721,7 @@ integral types: `byte`, `short`, `int`, `long`, and their boxed counterparts. | `java.math.BigDecimal` | `"123.456e789"` -> `new BigDecimal("123.456e789")` | `java.math.BigInteger` | `"1234567890123456789"` -> `new BigInteger("1234567890123456789")` | `java.net.URI` | `"https://junit.org/"` -> `URI.create("https://junit.org/")` -| `java.net.URL` | `"https://junit.org/"` -> `new URL("https://junit.org/")` +| `java.net.URL` | `"https://junit.org/"` -> `URI.create("https://junit.org/").toURL()` | `java.nio.charset.Charset` | `"UTF-8"` -> `Charset.forName("UTF-8")` | `java.nio.file.Path` | `"/path/to/file"` -> `Paths.get("/path/to/file")` | `java.time.Duration` | `"PT3S"` -> `Duration.ofSeconds(3)` @@ -1460,6 +1812,10 @@ composed annotation `JavaTimeConversionPattern`. include::{testDir}/example/ParameterizedTestDemo.java[tags=explicit_java_time_converter] ---- +If you wish to implement a custom `ArgumentConverter` that also consumes an annotation +(like `JavaTimeArgumentConverter`), you have the possibility to extend the +`{AnnotationBasedArgumentConverter}` class. + [[writing-tests-parameterized-tests-argument-aggregation]] ==== Argument Aggregation @@ -1472,6 +1828,9 @@ this API, you can access the provided arguments through a single argument passed test method. In addition, type conversion is supported as discussed in <>. +Besides, you can retrieve the current test invocation index with +`ArgumentsAccessor.getInvocationIndex()`. + [source,java,indent=0] ---- include::{testDir}/example/ParameterizedTestDemo.java[tags=ArgumentsAccessor_example] @@ -1548,7 +1907,8 @@ Display name of container ✔ └─ 3 ==> the rank of 'lemon, lime' is 3 ✔ .... -Please note that `name` is a `MessageFormat` pattern. Thus, a single quote (`'`) needs to be represented as a doubled single quote (`''`) in order to be displayed. +Please note that `name` is a `MessageFormat` pattern. Thus, a single quote (`'`) needs to +be represented as a doubled single quote (`''`) in order to be displayed. The following placeholders are supported within custom display names. @@ -1568,6 +1928,37 @@ if they exceed the configured maximum length. The limit is configurable via the `junit.jupiter.params.displayname.argument.maxlength` configuration parameter and defaults to 512 characters. +When using `@MethodSource` or `@ArgumentsSource`, you can provide custom names for +arguments using the `{Named}` API. A custom name will be used if the argument is included +in the invocation display name, like in the example below. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=named_arguments] +---- + +.... +A parameterized test with named arguments ✔ +├─ 1: An important file ✔ +└─ 2: Another file ✔ +.... + +If you'd like to set a default name pattern for all parameterized tests in your project, +you can declare the `junit.jupiter.params.displayname.default` configuration parameter in +the `junit-platform.properties` file as demonstrated in the following example (see +<> for other options). + +[source,properties,indent=0] +---- +junit.jupiter.params.displayname.default = {index} +---- + +The display name for a parameterized test is determined according to the following +precedence rules: + +1. `name` attribute in `@ParameterizedTest`, if present +2. value of the `junit.jupiter.params.displayname.default` configuration parameter, if present +3. `DEFAULT_DISPLAY_NAME` constant defined in `@ParameterizedTest` [[writing-tests-parameterized-tests-lifecycle-interop]] ==== Lifecycle and Interoperability @@ -1662,12 +2053,11 @@ and dynamic tests. The first method returns an invalid return type. Since an invalid return type cannot be detected at compile time, a `JUnitException` is thrown when it is detected at runtime. -The next five methods are very simple examples that demonstrate the generation of a -`Collection`, `Iterable`, `Iterator`, or `Stream` of `DynamicTest` instances. Most of -these examples do not really exhibit dynamic behavior but merely demonstrate the -supported return types in principle. However, `dynamicTestsFromStream()` and -`dynamicTestsFromIntStream()` demonstrate how easy it is to generate dynamic tests for a -given set of strings or a range of input numbers. +The next six methods demonstrate the generation of a `Collection`, `Iterable`, `Iterator`, +array, or `Stream` of `DynamicTest` instances. Most of these examples do not really +exhibit dynamic behavior but merely demonstrate the supported return types in principle. +However, `dynamicTestsFromStream()` and `dynamicTestsFromIntStream()` demonstrate how to +generate dynamic tests for a given set of strings or a range of input numbers. The next method is truly dynamic in nature. `generateRandomNumberOfTests()` implements an `Iterator` that generates random numbers, a display name generator, and a test executor @@ -1717,6 +2107,10 @@ implementations. refer to the Javadoc for `DiscoverySelectors.selectMethod(String)` for the supported formats for a FQMN. +`ClassSource` :: + If the `URI` contains the `class` scheme and the fully qualified class name -- + for example, `class:org.junit.Foo?line=42`. + `UriSource` :: If none of the above `TestSource` implementations are applicable. @@ -1724,10 +2118,6 @@ implementations. [[writing-tests-declarative-timeouts]] === Timeouts -.Declarative timeouts are an experimental feature -WARNING: You're invited to give it a try and provide feedback to the JUnit team so they -can improve and eventually <> this feature. - The `@Timeout` annotation allows one to declare that a test, test factory, test template, or lifecycle method should fail if its execution time exceeds a given duration. The time unit for the duration defaults to seconds but is configurable. @@ -1739,12 +2129,6 @@ The following example shows how `@Timeout` is applied to lifecycle and test meth include::{testDir}/example/TimeoutDemo.java[tags=user_guide] ---- -Contrary to the `assertTimeoutPreemptively()` assertion, the execution of the annotated -method proceeds in the main thread of the test. If the timeout is exceeded, the main -thread is interrupted from another thread. This is done to ensure interoperability with -frameworks such as Spring that make use of mechanisms that are sensitive to the currently -running thread — for example, `ThreadLocal` transaction management. - To apply the same timeout to all test methods within a test class and all of its `@Nested` classes, you can declare the `@Timeout` annotation at the class level. It will then be applied to all test, test factory, and test template methods within that class and its @@ -1760,8 +2144,32 @@ within the specified duration but does not verify the execution time of each ind If `@Timeout` is present on a `@TestTemplate` method — for example, a `@RepeatedTest` or `@ParameterizedTest` — each invocation will have the given timeout applied to it. +[[writing-tests-declarative-timeouts-thread-mode]] +==== Thread mode + +The timeout can be applied using one of the following three thread modes: `SAME_THREAD`, +`SEPARATE_THREAD`, or `INFERRED`. + +When `SAME_THREAD` is used, the execution of the annotated method proceeds in the main +thread of the test. If the timeout is exceeded, the main thread is interrupted from +another thread. This is done to ensure interoperability with frameworks such as Spring +that make use of mechanisms that are sensitive to the currently running thread — for +example, `ThreadLocal` transaction management. + +On the contrary when `SEPARATE_THREAD` is used, like the `assertTimeoutPreemptively()` +assertion, the execution of the annotated method proceeds in a separate thread, this +can lead to undesirable side effects, see <>. + +When `INFERRED` (default) thread mode is used, the thread mode is resolved via the +`junit.jupiter.execution.timeout.thread.mode.default` configuration parameter. If the +provided configuration parameter is invalid or not present then `SAME_THREAD` is used as +fallback. + +[[writing-tests-declarative-timeouts-default-timeouts]] +==== Default Timeouts + The following <> can be used to -specify global timeouts for all methods of a certain category unless they or an enclosing +specify default timeouts for all methods of a certain category unless they or an enclosing test class is annotated with `@Timeout`: `junit.jupiter.execution.timeout.default`:: @@ -1846,16 +2254,13 @@ JUnit Jupiter supports the `junit.jupiter.execution.timeout.mode` configuration to configure when timeouts are applied. There are three modes: `enabled`, `disabled`, and `disabled_on_debug`. The default mode is `enabled`. A VM runtime is considered to run in debug mode when one of its input parameters starts -with `-agentlib:jdwp`. This heuristic is queried by the `disabled_on_debug` mode. +with `-agentlib:jdwp` or `-Xrunjdwp`. +This heuristic is queried by the `disabled_on_debug` mode. [[writing-tests-parallel-execution]] === Parallel Execution -.Parallel test execution is an experimental feature -WARNING: 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, JUnit Jupiter tests are run sequentially in a single thread. Running tests in parallel -- for example, to speed up execution -- is available as an opt-in feature since version 5.3. To enable parallel execution, set the @@ -1891,11 +2296,17 @@ 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 `{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}` (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. + +When parallel execution is enabled and a default `{ClassOrderer}` is registered (see +<> for details), top-level test classes will +initially be sorted accordingly and scheduled in that order. However, they are not +guaranteed to be started in exactly that order since the threads they are executed on are +not controlled directly by JUnit. All nodes of the test tree that are configured with the `CONCURRENT` execution mode will be executed fully in parallel according to the provided @@ -1985,10 +2396,14 @@ configuration parameter to one of the following options. Computes the desired parallelism based on the number of available processors/cores multiplied by the `junit.jupiter.execution.parallel.config.dynamic.factor` configuration parameter (defaults to `1`). + The optional `junit.jupiter.execution.parallel.config.dynamic.max-pool-size-factor` + configuration parameter can be used to limit the maximum number of threads. `fixed`:: Uses the mandatory `junit.jupiter.execution.parallel.config.fixed.parallelism` configuration parameter as the desired parallelism. + The optional `junit.jupiter.execution.parallel.config.fixed.max-pool-size` + configuration parameter can be used to limit the maximum number of threads. `custom`:: Allows you to specify a custom `{ParallelExecutionConfigurationStrategy}` @@ -1999,13 +2414,102 @@ If no configuration strategy is set, JUnit Jupiter uses the `dynamic` configurat strategy with a factor of `1`. Consequently, the desired parallelism will be equal to the number of available processors/cores. -.Parallelism does not imply maximum number of concurrent threads -NOTE: JUnit Jupiter does not guarantee that the number of concurrently executing tests -will not exceed the configured parallelism. For example, when using one of the -synchronization mechanisms described in the next section, the `ForkJoinPool` that is used -behind the scenes may spawn additional threads to ensure execution continues with -sufficient parallelism. Thus, if you require such guarantees in a test class, please use -your own means of controlling concurrency. +.Parallelism alone does not imply maximum number of concurrent threads +NOTE: By default JUnit Jupiter does not guarantee that the number of concurrently +executing tests will not exceed the configured parallelism. For example, when using one +of the synchronization mechanisms described in the next section, the `ForkJoinPool` that +is used behind the scenes may spawn additional threads to ensure execution continues with +sufficient parallelism. +If you require such guarantees, with Java 9+, it is possible to limit the maximum number +of concurrent threads by controlling the maximum pool size of the `dynamic`, `fixed` and +`custom` strategies. + +[[writing-tests-parallel-execution-config-properties]] +===== Relevant properties + +The following table lists relevant properties for configuring parallel execution. See +<> for details on how to set such properties. + +[cols="d,d,a,d"] +|=== +|Property |Description |Supported Values |Default Value + +| ```junit.jupiter.execution.parallel.enabled``` +| Enable parallel test execution +| + * `true` + * `false` +| ```false``` + +| ```junit.jupiter.execution.parallel.mode.default``` +| Default execution mode of nodes in the test tree +| + * `concurrent` + * `same_thread` +| ```same_thread``` + +| ```junit.jupiter.execution.parallel.mode.classes.default``` +| Default execution mode of top-level classes +| + * `concurrent` + * `same_thread` +| ```same_thread``` + +| ```junit.jupiter.execution.parallel.config.strategy``` +| Execution strategy for desired parallelism and maximum pool size +| + * `dynamic` + * `fixed` + * `custom` +| ```dynamic``` + +| ```junit.jupiter.execution.parallel.config.dynamic.factor``` +| Factor to be multiplied by the number of available processors/cores to determine the + desired parallelism for the ```dynamic``` configuration strategy +| a positive decimal number +| ```1.0``` + +| ```junit.jupiter.execution.parallel.config.dynamic.max-pool-size-factor``` +| Factor to be multiplied by the number of available processors/cores and the value of + `junit.jupiter.execution.parallel.config.dynamic.factor` to determine the desired + parallelism for the ```dynamic``` configuration strategy +| a positive decimal number, must be greater than or equal to `1.0` +| 256 + the value of `junit.jupiter.execution.parallel.config.dynamic.factor` multiplied + by the number of available processors/cores + +| ```junit.jupiter.execution.parallel.config.dynamic.saturate``` +| Disable saturation of the underlying fork-join pool for the ```dynamic``` configuration +strategy +| +* `true` +* `false` +| ```true``` + +| ```junit.jupiter.execution.parallel.config.fixed.parallelism``` +| Desired parallelism for the ```fixed``` configuration strategy +| a positive integer +| no default value + +| ```junit.jupiter.execution.parallel.config.fixed.max-pool-size``` +| Desired maximum pool size of the underlying fork-join pool for the ```fixed``` + configuration strategy +| a positive integer, must be greater than or equal to `junit.jupiter.execution.parallel.config.fixed.parallelism` +| 256 + the value of `junit.jupiter.execution.parallel.config.fixed.parallelism` + +| ```junit.jupiter.execution.parallel.config.fixed.saturate``` +| Disable saturation of the underlying fork-join pool for the ```fixed``` configuration + strategy +| + * `true` + * `false` +| ```true``` + +| ```junit.jupiter.execution.parallel.config.custom.class``` +| Fully qualified class name of the _ParallelExecutionConfigurationStrategy_ to be + used for the ```custom``` configuration strategy +| for example, _org.example.CustomStrategy_ +| no default value +|=== [[writing-tests-parallel-execution-synchronization]] ==== Synchronization @@ -2058,13 +2562,9 @@ another dependency. [[writing-tests-built-in-extensions-TempDirectory]] ==== The TempDirectory Extension -.`@TempDir` is an experimental feature -WARNING: You're invited to give it a try and provide feedback to the JUnit team so they -can improve and eventually <> this feature. - The built-in `{TempDirectory}` extension is used to create and clean up a temporary directory for an individual test or all tests in a test class. It is registered by -default. To use it, annotate a non-private field of type `java.nio.file.Path` or +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. @@ -2078,16 +2578,117 @@ its content. include::{testDir}/example/TempDirectoryDemo.java[tags=user_guide_parameter_injection] ---- -WARNING: `@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 a non-private instance field with `@TempDir`. +You can inject multiple temporary directories by specifying multiple annotated parameters. + +[source,java,indent=0] +.A test method that requires multiple temporary directories +---- +include::{testDir}/example/TempDirectoryDemo.java[tags=user_guide_multiple_directories] +---- + +WARNING: To revert to the old behavior of using a single temporary directory for the +entire test class or method (depending on which level the annotation is used), you can set +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. +the test class. For better isolation, you should use an instance field so that each test +method uses a separate directory. [source,java,indent=0] .A test class that shares a temporary directory across test methods ---- include::{testDir}/example/TempDirectoryDemo.java[tags=user_guide_field_injection] ---- + +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. + +The default cleanup mode is `ALWAYS`. You can use the +`junit.jupiter.tempdir.cleanup.mode.default` +<> to override this default. + +[source,java,indent=0] +.A test class with a temporary directory that doesn't get cleaned up +---- +include::{testDir}/example/TempDirectoryDemo.java[tags=user_guide_cleanup_mode] +---- + +`@TempDir` supports the programmatic creation of temporary directories via the optional +`factory` attribute. This is typically used to gain control over the temporary directory +creation, like defining the parent directory or the file system that should be used. + +Factories can be created by implementing `TempDirFactory`. Implementations must provide a +no-args constructor and should not make any assumptions regarding when and how many times +they are instantiated, but they can assume that their `createTempDirectory(...)` and +`close()` methods will both be called once per instance, in this order, and from the same +thread. + +The default implementation available in Jupiter delegates the directory creation to +`java.nio.file.Files::createTempDirectory`, passing `junit` as the prefix string to be +used in generating the directory's name. + +The following example defines a factory that uses the test name as the directory name +prefix instead of the `junit` constant value. + +[source,java,indent=0] +.A test class with a temporary directory having the test name as the directory name prefix +---- +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 +temporary directory. The following example demonstrates how to achieve that. + +[source,java,indent=0] +.A test class with a temporary directory created with the Jimfs in-memory file system +---- +include::{testDir}/example/TempDirectoryDemo.java[tags=user_guide_factory_jimfs] +---- + +`@TempDir` can also be used as a <> to +reduce repetition. The following code listing shows how to create a custom `@JimfsTempDir` +annotation that can be used as a drop-in replacement for +`@TempDir(factory = JimfsTempDirFactory.class)`. + +[source,java,indent=0] +.A custom annotation meta-annotated with `@TempDir` +---- +include::{testDir}/example/TempDirectoryDemo.java[tags=user_guide_composed_annotation] +---- + +The following example demonstrates how to use the custom `@JimfsTempDir` annotation. + +[source,java,indent=0] +.A test class using the custom annotation +---- +include::{testDir}/example/TempDirectoryDemo.java[tags=user_guide_composed_annotation_usage] +---- + +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`. + +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: + +1. The `factory` attribute of the `@TempDir` annotation, if present +2. The default `TempDirFactory` configured via the configuration +parameter, if present +3. Otherwise, `org.junit.jupiter.api.io.TempDirFactory$Standard` will be used. diff --git a/documentation/src/javadoc/junit-stylesheet.css b/documentation/src/javadoc/junit-stylesheet.css index 4ad313bfeb8c..19fe4f0cee0a 100644 --- a/documentation/src/javadoc/junit-stylesheet.css +++ b/documentation/src/javadoc/junit-stylesheet.css @@ -36,96 +36,120 @@ pre, code, tt, dt code, table tr td dt code { background-color:#25a162; } -.topNav { +.top-nav { background-color:#25a162; } -.bottomNav { +.bottom-nav { background-color:#25a162; } -.subNav { +.sub-nav { background-color:#f5f5f5; } -.topNav a:hover, .bottomNav a:hover { +.top-nav a:hover, .bottom-nav a:hover { text-decoration:underline; color:inherit; } -.navBarCell1Rev { +.nav-bar-cell1-rev { background-color:#fff; color:#dc524a; border-radius: 6px; } -.indexNav { +.index-nav { background-color:#eee; } -div.details ul.blockList ul.blockList ul.blockList li.blockList h4, div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 { - background-color:#ddd; - border:1px solid #ddd; +body.class-declaration-page .summary h2, +body.class-declaration-page .details h2, +body.class-use-page h2, +body.module-declaration-page .block-list h2 { + font-style: italic; + padding:0; + margin:15px 0; } - -ul.blockList ul.blockList ul.blockList li.blockList h3 { +body.class-declaration-page .summary h3, +body.class-declaration-page .details h3, +body.class-declaration-page .summary .inherited-list h2, +div.details ul.block-list ul.block-list ul.block-list li.block-list h4, +ul.block-list ul.block-list ul.block-list li.block-list h3 { background-color:#ddd; border:1px solid #ddd; } -.constantsSummary caption a:link, .constantsSummary caption a:visited, -.useSummary caption a:link, .useSummary caption a:visited { +.constants-summary caption a:link, .constants-summary caption a:visited, +.use-summary caption a:link, .use-summary caption a:visited { color:#fff; } -.overviewSummary caption span, .memberSummary caption span, .typeSummary caption span, -.useSummary caption span, .constantsSummary caption span, .deprecatedSummary caption span, -.requiresSummary caption span, .packagesSummary caption span, .providesSummary caption span, -.usesSummary caption span, -.memberSummary caption span.activeTableTab span, .packagesSummary caption span.activeTableTab span, -.overviewSummary caption span.activeTableTab span, .typeSummary caption span.activeTableTab span, -.overviewSummary .tabEnd, .memberSummary .tabEnd, .typeSummary .tabEnd, -.useSummary .tabEnd, .constantsSummary .tabEnd, .deprecatedSummary .tabEnd, -.requiresSummary .tabEnd, .packagesSummary .tabEnd, .providesSummary .tabEnd, .usesSummary .tabEnd, -.memberSummary .activeTableTab .tabEnd, .packagesSummary .activeTableTab .tabEnd, -.overviewSummary .activeTableTab .tabEnd, .typeSummary .activeTableTab .tabEnd { +.overview-summary caption span, .member-summary caption span, .type-summary caption span, +.use-summary caption span, .constants-summary caption span, .deprecated-summary caption span, +.requires-summary caption span, .packages-summary caption span, .provides-summary caption span, +.uses-summary caption span, +.member-summary caption span.active-table-tab span, .packages-summary caption span.active-table-tab span, +.overview-summary caption span.active-table-tab span, .type-summary caption span.active-table-tab span, +div.table-tabs > button.active-table-tab +{ background-color:#dc524a; color: #fff; } -.memberSummary caption span.tableTab span, .packagesSummary caption span.tableTab span, -.overviewSummary caption span.tableTab span, .typeSummary caption span.tableTab span, -.memberSummary .tableTab .tabEnd, .packagesSummary .tableTab .tabEnd, -.overviewSummary .tableTab .tabEnd, .typeSummary .tableTab .tabEnd, -.ui-autocomplete-category { +.ui-state-active, +.ui-widget-content .ui-state-active, +.ui-widget-header .ui-state-active, +a.ui-button:active, +.ui-button:active, +.ui-button.ui-state-active:hover { + /* Overrides the color of selection used in jQuery UI */ + background: #dc524a !important; + color: #fff !important; +} + +main a[href*="://"]::after, +main a[href*="://"]:hover::after, +main a[href*="://"]:focus::after { + background-image:url('data:image/svg+xml; utf8, \ + \ + \ + '); +} + +.member-summary caption span.table-tab span, .packages-summary caption span.table-tab span, +.overview-summary caption span.table-tab span, .type-summary caption span.table-tab span, +.ui-autocomplete-category, +div.table-tabs > button.table-tab { background-color:#aaa; color: #fff; } -th.colFirst, th.colSecond, th.colLast, th.colConstructorName, th.colDeprecatedItemName, .constantsSummary th, -.packagesSummary th { +th.col-first, th.col-second, th.col-last, th.col-constructor-name, th.col-deprecated-item-name, .constants-summary th, +.packages-summary th { background:#eee; } -.tableSubHeadingColor { +.table-sub-heading-color { background-color:#eee; } -.altColor, .altColor th { +.alt-color, .alt-color th { background-color:#fff; } -.rowColor, .rowColor th { +.row-color, .row-color th { background-color:#eee; } .block { - margin:0px 10px 5px 0px; + margin:0 10px 5px 0; } -th.colFirst, th.colSecond, th.colLast, th.colConstructorName, th.colDeprecatedItemName, .constantsSummary th, -.packagesSummary th, .overviewSummary td, .memberSummary td, .typeSummary td, -.useSummary td, .constantsSummary td, .deprecatedSummary td, -.requiresSummary td, .packagesSummary td, .providesSummary td, .usesSummary td { +th.col-first, th.col-second, th.col-last, th.col-constructor-name, th.col-deprecated-item-name, .constants-summary th, +.packages-summary th, .overview-summary td, .member-summary td, .type-summary td, +.use-summary td, .constants-summary td, .deprecated-summary td, +.requires-summary td, .packages-summary td, .provides-summary td, .uses-summary td { padding-left:7px; } diff --git a/documentation/src/main/java/example/domain/Person.java b/documentation/src/main/java/example/domain/Person.java index f34c00d1b1f6..b628febd36cf 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/main/java/example/registration/WebClient.java b/documentation/src/main/java/example/registration/WebClient.java index 26088b910f47..b907c2c58e95 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/main/java/example/registration/WebResponse.java b/documentation/src/main/java/example/registration/WebResponse.java index 0cd2fb438f20..598239f44c24 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/main/java/example/registration/WebServerExtension.java b/documentation/src/main/java/example/registration/WebServerExtension.java index ed9a0c5b7e80..80fefe787b89 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/main/java/example/util/Calculator.java b/documentation/src/main/java/example/util/Calculator.java index 8f6cc877d020..98291f6a78fe 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/main/java/example/util/ListWriter.java b/documentation/src/main/java/example/util/ListWriter.java index b2f0e26177d0..88fb73137ff6 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/main/java/example/util/StringUtils.java b/documentation/src/main/java/example/util/StringUtils.java index e489cd7be79c..b622aa3efb90 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/AssertionsDemo.java b/documentation/src/test/java/example/AssertionsDemo.java index 22311e974840..476434da44f6 100644 --- a/documentation/src/test/java/example/AssertionsDemo.java +++ b/documentation/src/test/java/example/AssertionsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -27,6 +27,7 @@ import example.domain.Person; import example.util.Calculator; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; class AssertionsDemo { @@ -91,6 +92,9 @@ void exceptionTesting() { assertEquals("/ by zero", exception.getMessage()); } + // end::user_guide[] + @Tag("timeout") + // tag::user_guide[] @Test void timeoutNotExceeded() { // The following assertion succeeds. @@ -99,6 +103,9 @@ void timeoutNotExceeded() { }); } + // end::user_guide[] + @Tag("timeout") + // tag::user_guide[] @Test void timeoutNotExceededWithResult() { // The following assertion succeeds, and returns the supplied object. @@ -108,6 +115,9 @@ void timeoutNotExceededWithResult() { assertEquals("a result", actualResult); } + // end::user_guide[] + @Tag("timeout") + // tag::user_guide[] @Test void timeoutNotExceededWithMethod() { // The following assertion invokes a method reference and returns an object. @@ -116,6 +126,7 @@ void timeoutNotExceededWithMethod() { } // end::user_guide[] + @Tag("timeout") @extensions.ExpectToFail // tag::user_guide[] @Test @@ -129,6 +140,7 @@ void timeoutExceeded() { } // end::user_guide[] + @Tag("timeout") @extensions.ExpectToFail // tag::user_guide[] @Test diff --git a/documentation/src/test/java/example/AssumptionsDemo.java b/documentation/src/test/java/example/AssumptionsDemo.java index 75914749a7bf..41438feabb2f 100644 --- a/documentation/src/test/java/example/AssumptionsDemo.java +++ b/documentation/src/test/java/example/AssumptionsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/ConditionalTestExecutionDemo.java b/documentation/src/test/java/example/ConditionalTestExecutionDemo.java index fcd7a2dcc4be..146443c260c1 100644 --- a/documentation/src/test/java/example/ConditionalTestExecutionDemo.java +++ b/documentation/src/test/java/example/ConditionalTestExecutionDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -28,12 +28,14 @@ import org.junit.jupiter.api.condition.DisabledIf; import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable; import org.junit.jupiter.api.condition.DisabledIfSystemProperty; +import org.junit.jupiter.api.condition.DisabledInNativeImage; import org.junit.jupiter.api.condition.DisabledOnJre; import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.EnabledForJreRange; import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import org.junit.jupiter.api.condition.EnabledInNativeImage; import org.junit.jupiter.api.condition.EnabledOnJre; import org.junit.jupiter.api.condition.EnabledOnOs; @@ -71,6 +73,32 @@ void notOnWindows() { } // end::user_guide_os[] + // tag::user_guide_architecture[] + @Test + @EnabledOnOs(architectures = "aarch64") + void onAarch64() { + // ... + } + + @Test + @DisabledOnOs(architectures = "x86_64") + void notOnX86_64() { + // ... + } + + @Test + @EnabledOnOs(value = MAC, architectures = "aarch64") + void onNewMacs() { + // ... + } + + @Test + @DisabledOnOs(value = MAC, architectures = "aarch64") + void notOnNewMacs() { + // ... + } + // end::user_guide_architecture[] + // tag::user_guide_jre[] @Test @EnabledOnJre(JAVA_8) @@ -127,6 +155,20 @@ void notFromJava8to11() { } // end::user_guide_jre[] + // tag::user_guide_native[] + @Test + @EnabledInNativeImage + void onlyWithinNativeImage() { + // ... + } + + @Test + @DisabledInNativeImage + void neverWithinNativeImage() { + // ... + } + // end::user_guide_native[] + // tag::user_guide_system_property[] @Test @EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*") diff --git a/documentation/src/test/java/example/CustomLauncherInterceptor.java b/documentation/src/test/java/example/CustomLauncherInterceptor.java new file mode 100644 index 000000000000..149cf7e45440 --- /dev/null +++ b/documentation/src/test/java/example/CustomLauncherInterceptor.java @@ -0,0 +1,55 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide[] + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URI; +import java.net.URL; +import java.net.URLClassLoader; + +import org.junit.platform.launcher.LauncherInterceptor; + +public class CustomLauncherInterceptor implements LauncherInterceptor { + + private final URLClassLoader customClassLoader; + + public CustomLauncherInterceptor() throws Exception { + ClassLoader parent = Thread.currentThread().getContextClassLoader(); + customClassLoader = new URLClassLoader(new URL[] { URI.create("some.jar").toURL() }, parent); + } + + @Override + public T intercept(Invocation invocation) { + Thread currentThread = Thread.currentThread(); + ClassLoader originalClassLoader = currentThread.getContextClassLoader(); + currentThread.setContextClassLoader(customClassLoader); + try { + return invocation.proceed(); + } + finally { + currentThread.setContextClassLoader(originalClassLoader); + } + } + + @Override + public void close() { + try { + customClassLoader.close(); + } + catch (IOException e) { + throw new UncheckedIOException("Failed to close custom class loader", e); + } + } +} +// end::user_guide[] diff --git a/documentation/src/test/java/example/CustomTestEngine.java b/documentation/src/test/java/example/CustomTestEngine.java index b12a8e862c49..a1d0eb31bb47 100644 --- a/documentation/src/test/java/example/CustomTestEngine.java +++ b/documentation/src/test/java/example/CustomTestEngine.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/DisabledClassDemo.java b/documentation/src/test/java/example/DisabledClassDemo.java index 761547508255..a2453a2aac27 100644 --- a/documentation/src/test/java/example/DisabledClassDemo.java +++ b/documentation/src/test/java/example/DisabledClassDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/DisabledTestsDemo.java b/documentation/src/test/java/example/DisabledTestsDemo.java index 93e11e251b51..e1a7f6c0ad68 100644 --- a/documentation/src/test/java/example/DisabledTestsDemo.java +++ b/documentation/src/test/java/example/DisabledTestsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/DisplayNameDemo.java b/documentation/src/test/java/example/DisplayNameDemo.java index b30092e8d7d5..c9ee6fed5ddc 100644 --- a/documentation/src/test/java/example/DisplayNameDemo.java +++ b/documentation/src/test/java/example/DisplayNameDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/DisplayNameGeneratorDemo.java b/documentation/src/test/java/example/DisplayNameGeneratorDemo.java index a59f4fd3f157..0ccfb762e24b 100644 --- a/documentation/src/test/java/example/DisplayNameGeneratorDemo.java +++ b/documentation/src/test/java/example/DisplayNameGeneratorDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -15,6 +15,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; import org.junit.jupiter.api.IndicativeSentencesGeneration; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -40,7 +41,7 @@ void if_it_is_negative(int year) { } @Nested - @IndicativeSentencesGeneration(separator = " -> ", generator = DisplayNameGenerator.ReplaceUnderscores.class) + @IndicativeSentencesGeneration(separator = " -> ", generator = ReplaceUnderscores.class) class A_year_is_a_leap_year { @Test diff --git a/documentation/src/test/java/example/DocumentationTestSuite.java b/documentation/src/test/java/example/DocumentationTestSuite.java index 1b2f9d575883..b3bc6dd0029b 100644 --- a/documentation/src/test/java/example/DocumentationTestSuite.java +++ b/documentation/src/test/java/example/DocumentationTestSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -10,14 +10,13 @@ package example; -import org.junit.platform.runner.JUnitPlatform; import org.junit.platform.suite.api.ExcludeTags; import org.junit.platform.suite.api.IncludeClassNamePatterns; import org.junit.platform.suite.api.SelectPackages; -import org.junit.runner.RunWith; +import org.junit.platform.suite.api.Suite; /** - *

Logging Configuration

+ *

Logging Configuration

* *

In order for our log4j2 configuration to be used in an IDE, you must * set the following system property before running any tests — for @@ -29,9 +28,9 @@ * * @since 5.0 */ -@RunWith(JUnitPlatform.class) +@Suite @SelectPackages("example") @IncludeClassNamePatterns(".+(Tests|Demo)$") @ExcludeTags("exclude") -public class DocumentationTestSuite { +class DocumentationTestSuite { } diff --git a/documentation/src/test/java/example/DynamicTestsDemo.java b/documentation/src/test/java/example/DynamicTestsDemo.java index 8aa063a57dbe..8a8d9bef9ac0 100644 --- a/documentation/src/test/java/example/DynamicTestsDemo.java +++ b/documentation/src/test/java/example/DynamicTestsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -19,6 +19,7 @@ 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; @@ -33,6 +34,7 @@ import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Named; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.function.ThrowingConsumer; @@ -151,6 +153,21 @@ 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 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 e08950b1df9b..2a898b33e08b 100644 --- a/documentation/src/test/java/example/ExampleTestCase.java +++ b/documentation/src/test/java/example/ExampleTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/ExternalCustomConditionDemo.java b/documentation/src/test/java/example/ExternalCustomConditionDemo.java new file mode 100644 index 000000000000..024b07d27854 --- /dev/null +++ b/documentation/src/test/java/example/ExternalCustomConditionDemo.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide_external_custom_condition[] +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIf; + +class ExternalCustomConditionDemo { + + @Test + @EnabledIf("example.ExternalCondition#customCondition") + void enabled() { + // ... + } + +} + +class ExternalCondition { + + static boolean customCondition() { + return true; + } + +} +// end::user_guide_external_custom_condition[] diff --git a/documentation/src/test/java/example/ExternalMethodSourceDemo.java b/documentation/src/test/java/example/ExternalMethodSourceDemo.java index cda486d775de..1d8299367e9b 100644 --- a/documentation/src/test/java/example/ExternalMethodSourceDemo.java +++ b/documentation/src/test/java/example/ExternalMethodSourceDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/Fast.java b/documentation/src/test/java/example/Fast.java index 911fdb2df0d7..1e7c78e6f6b6 100644 --- a/documentation/src/test/java/example/Fast.java +++ b/documentation/src/test/java/example/Fast.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/FastTest.java b/documentation/src/test/java/example/FastTest.java index f85066f7db29..af31b49d5699 100644 --- a/documentation/src/test/java/example/FastTest.java +++ b/documentation/src/test/java/example/FastTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/HamcrestAssertionsDemo.java b/documentation/src/test/java/example/HamcrestAssertionsDemo.java index 5f9cd857cb81..8fa219829ce3 100644 --- a/documentation/src/test/java/example/HamcrestAssertionsDemo.java +++ b/documentation/src/test/java/example/HamcrestAssertionsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/IgnoredTestsDemo.java b/documentation/src/test/java/example/IgnoredTestsDemo.java index 6bddc9134c9f..96fca01c8ade 100644 --- a/documentation/src/test/java/example/IgnoredTestsDemo.java +++ b/documentation/src/test/java/example/IgnoredTestsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/JUnit4Tests.java b/documentation/src/test/java/example/JUnit4Tests.java index 2caac8b99351..d229e639fa2d 100644 --- a/documentation/src/test/java/example/JUnit4Tests.java +++ b/documentation/src/test/java/example/JUnit4Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/JUnitPlatformClassDemo.java b/documentation/src/test/java/example/JUnitPlatformClassDemo.java index 09acf1b0e24d..0bf0aeb06220 100644 --- a/documentation/src/test/java/example/JUnitPlatformClassDemo.java +++ b/documentation/src/test/java/example/JUnitPlatformClassDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -14,10 +14,12 @@ import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.Test; -import org.junit.platform.runner.JUnitPlatform; import org.junit.runner.RunWith; -@RunWith(JUnitPlatform.class) +//end::user_guide[] +@SuppressWarnings("deprecation") +//tag::user_guide[] +@RunWith(org.junit.platform.runner.JUnitPlatform.class) public class JUnitPlatformClassDemo { @Test diff --git a/documentation/src/test/java/example/JUnitPlatformSuiteDemo.java b/documentation/src/test/java/example/JUnitPlatformSuiteDemo.java index a5c698558967..b563204fff2b 100644 --- a/documentation/src/test/java/example/JUnitPlatformSuiteDemo.java +++ b/documentation/src/test/java/example/JUnitPlatformSuiteDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -11,15 +11,15 @@ package example; //tag::user_guide[] -import org.junit.platform.runner.JUnitPlatform; import org.junit.platform.suite.api.SelectPackages; import org.junit.platform.suite.api.SuiteDisplayName; import org.junit.runner.RunWith; -@RunWith(JUnitPlatform.class) +@RunWith(org.junit.platform.runner.JUnitPlatform.class) @SuiteDisplayName("JUnit Platform Suite Demo") @SelectPackages("example") //end::user_guide[] +@SuppressWarnings("deprecation") @org.junit.platform.suite.api.ExcludeTags("exclude") //tag::user_guide[] public class JUnitPlatformSuiteDemo { diff --git a/documentation/src/test/java/example/MethodSourceParameterResolutionDemo.java b/documentation/src/test/java/example/MethodSourceParameterResolutionDemo.java new file mode 100644 index 000000000000..45b35dbcc964 --- /dev/null +++ b/documentation/src/test/java/example/MethodSourceParameterResolutionDemo.java @@ -0,0 +1,66 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.util.stream.Stream; + +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.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class MethodSourceParameterResolutionDemo { + + // @formatter:off + // tag::parameter_resolution_MethodSource_example[] + @RegisterExtension + static final IntegerResolver integerResolver = new IntegerResolver(); + + @ParameterizedTest + @MethodSource("factoryMethodWithArguments") + void testWithFactoryMethodWithArguments(String argument) { + assertTrue(argument.startsWith("2")); + } + + static Stream factoryMethodWithArguments(int quantity) { + return Stream.of( + arguments(quantity + " apples"), + arguments(quantity + " lemons") + ); + } + + static class IntegerResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, + ExtensionContext extensionContext) { + + return parameterContext.getParameter().getType() == int.class; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, + ExtensionContext extensionContext) { + + return 2; + } + + } + // end::parameter_resolution_MethodSource_example[] + // @formatter:on + +} diff --git a/documentation/src/test/java/example/MyFirstJUnitJupiterTests.java b/documentation/src/test/java/example/MyFirstJUnitJupiterTests.java index 6eea9d7210d6..77632abc3be5 100644 --- a/documentation/src/test/java/example/MyFirstJUnitJupiterTests.java +++ b/documentation/src/test/java/example/MyFirstJUnitJupiterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/OrderedNestedTestClassesDemo.java b/documentation/src/test/java/example/OrderedNestedTestClassesDemo.java new file mode 100644 index 000000000000..b5e987daca65 --- /dev/null +++ b/documentation/src/test/java/example/OrderedNestedTestClassesDemo.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide[] +import org.junit.jupiter.api.ClassOrderer; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestClassOrder; + +@TestClassOrder(ClassOrderer.OrderAnnotation.class) +class OrderedNestedTestClassesDemo { + + @Nested + @Order(1) + class PrimaryTests { + + @Test + void test1() { + } + } + + @Nested + @Order(2) + class SecondaryTests { + + @Test + void test2() { + } + } +} +//end::user_guide[] diff --git a/documentation/src/test/java/example/OrderedTestsDemo.java b/documentation/src/test/java/example/OrderedTestsDemo.java index d54ff5b3fc82..2ee01d3335cf 100644 --- a/documentation/src/test/java/example/OrderedTestsDemo.java +++ b/documentation/src/test/java/example/OrderedTestsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/ParameterizedTestDemo.java b/documentation/src/test/java/example/ParameterizedTestDemo.java index b0f2b76fde99..3afd887265e2 100644 --- a/documentation/src/test/java/example/ParameterizedTestDemo.java +++ b/documentation/src/test/java/example/ParameterizedTestDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -15,10 +15,12 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Named.named; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.junit.jupiter.params.provider.EnumSource.Mode.EXCLUDE; import static org.junit.jupiter.params.provider.EnumSource.Mode.MATCH_ALL; +import java.io.File; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -210,7 +212,8 @@ static Stream stringIntAndListProvider() { @CsvSource({ "apple, 1", "banana, 2", - "'lemon, lime', 0xF1" + "'lemon, lime', 0xF1", + "strawberry, 700_000" }) void testWithCsvSource(String fruit, int rank) { assertNotNull(fruit); @@ -233,6 +236,13 @@ void testWithCsvFileSourceFromFile(String country, int reference) { assertNotNull(country); assertNotEquals(0, reference); } + + @ParameterizedTest(name = "[{index}] {arguments}") + @CsvFileSource(resources = "/two-column.csv", useHeadersInDisplayName = true) + void testWithCsvFileSourceAndHeaders(String country, int reference) { + assertNotNull(country); + assertNotEquals(0, reference); + } // end::CsvFileSource_example[] // tag::ArgumentsSource_example[] @@ -344,7 +354,7 @@ protected ToLengthArgumentConverter() { @Override protected Integer convert(String source) { - return source.length(); + return (source != null ? source.length() : 0); } } @@ -440,4 +450,21 @@ void testWithCustomAggregatorAnnotation(@CsvToPerson Person person) { void testWithCustomDisplayNames(String fruit, int rank) { } // end::custom_display_names[] + + // @formatter:off + // tag::named_arguments[] + @DisplayName("A parameterized test with named arguments") + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("namedArguments") + void testWithNamedArguments(File file) { + } + + static Stream namedArguments() { + return Stream.of( + arguments(named("An important file", new File("path1"))), + arguments(named("Another file", new File("path2"))) + ); + } + // end::named_arguments[] + // @formatter:on } diff --git a/documentation/src/test/java/example/PollingTimeoutDemo.java b/documentation/src/test/java/example/PollingTimeoutDemo.java index 2242b6b86c84..a10b7cec8edd 100644 --- a/documentation/src/test/java/example/PollingTimeoutDemo.java +++ b/documentation/src/test/java/example/PollingTimeoutDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/RepeatedTestsDemo.java b/documentation/src/test/java/example/RepeatedTestsDemo.java index 98d552952f4d..49bf59274aa7 100644 --- a/documentation/src/test/java/example/RepeatedTestsDemo.java +++ b/documentation/src/test/java/example/RepeatedTestsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -12,6 +12,7 @@ // tag::user_guide[] import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; import java.util.logging.Logger; @@ -21,6 +22,10 @@ import org.junit.jupiter.api.RepetitionInfo; import org.junit.jupiter.api.TestInfo; +// end::user_guide[] +// Use fully-qualified names to avoid having them show up in the imports. +@org.junit.jupiter.api.parallel.Execution(org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD) +// tag::user_guide[] class RepeatedTestsDemo { private Logger logger = // ... @@ -47,6 +52,18 @@ void repeatedTestWithRepetitionInfo(RepetitionInfo repetitionInfo) { assertEquals(5, repetitionInfo.getTotalRepetitions()); } + // end::user_guide[] + // Use fully-qualified name to avoid having it show up in the imports. + @org.junit.jupiter.api.Disabled("intentional failures would break the build") + // tag::user_guide[] + @RepeatedTest(value = 8, failureThreshold = 2) + void repeatedTestWithFailureThreshold(RepetitionInfo repetitionInfo) { + // Simulate unexpected failure every second repetition + if (repetitionInfo.getCurrentRepetition() % 2 == 0) { + fail("Boom!"); + } + } + @RepeatedTest(value = 1, name = "{displayName} {currentRepetition}/{totalRepetitions}") @DisplayName("Repeat!") void customDisplayName(TestInfo testInfo) { diff --git a/documentation/src/test/java/example/SharedResourcesDemo.java b/documentation/src/test/java/example/SharedResourcesDemo.java index 99d3727437cc..be3d12b3ce99 100644 --- a/documentation/src/test/java/example/SharedResourcesDemo.java +++ b/documentation/src/test/java/example/SharedResourcesDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/SlowTests.java b/documentation/src/test/java/example/SlowTests.java index a1fba4358f35..6fef77843145 100644 --- a/documentation/src/test/java/example/SlowTests.java +++ b/documentation/src/test/java/example/SlowTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -10,15 +10,16 @@ package example; -// tag::user_guide[] import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import java.util.stream.IntStream; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Execution; +@Tag("exclude") @Disabled class SlowTests { @@ -123,4 +124,3 @@ private void foo() { IntStream.range(1, 100_000_000).mapToDouble(i -> Math.pow(i, i)).map(Math::sqrt).max(); } } -// end::user_guide[] diff --git a/documentation/src/test/java/example/StandardTests.java b/documentation/src/test/java/example/StandardTests.java index 91bd871f9ffa..4a6a660ea932 100644 --- a/documentation/src/test/java/example/StandardTests.java +++ b/documentation/src/test/java/example/StandardTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/SuiteDemo.java b/documentation/src/test/java/example/SuiteDemo.java new file mode 100644 index 000000000000..617806b3d035 --- /dev/null +++ b/documentation/src/test/java/example/SuiteDemo.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +//tag::user_guide[] +import org.junit.platform.suite.api.IncludeClassNamePatterns; +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.api.SuiteDisplayName; + +@Suite +@SuiteDisplayName("JUnit Platform Suite Demo") +@SelectPackages("example") +@IncludeClassNamePatterns(".*Tests") +//end::user_guide[] +@org.junit.platform.suite.api.ExcludeTags("exclude") +//tag::user_guide[] +class SuiteDemo { +} +//end::user_guide[] diff --git a/documentation/src/test/java/example/TaggingDemo.java b/documentation/src/test/java/example/TaggingDemo.java index 6ab3e162badf..ebe9594b1c2d 100644 --- a/documentation/src/test/java/example/TaggingDemo.java +++ b/documentation/src/test/java/example/TaggingDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/TempDirectoryDemo.java b/documentation/src/test/java/example/TempDirectoryDemo.java index e656b44142dc..027242993e58 100644 --- a/documentation/src/test/java/example/TempDirectoryDemo.java +++ b/documentation/src/test/java/example/TempDirectoryDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -12,15 +12,30 @@ import static java.util.Collections.singletonList; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; import java.io.IOException; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; +import com.google.common.jimfs.Configuration; +import com.google.common.jimfs.Jimfs; + +import example.TempDirectoryDemo.InMemoryTempDirDemo.JimfsTempDirFactory; import example.util.ListWriter; 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.io.TempDir; +import org.junit.jupiter.api.io.TempDirFactory; class TempDirectoryDemo { @@ -35,6 +50,19 @@ void writeItemsToFile(@TempDir Path tempDir) throws IOException { } // end::user_guide_parameter_injection[] + // tag::user_guide_multiple_directories[] + @Test + void copyFileFromSourceToTarget(@TempDir Path source, @TempDir Path target) throws IOException { + Path sourceFile = source.resolve("test.txt"); + new ListWriter(sourceFile).write("a", "b", "c"); + + Path targetFile = Files.copy(sourceFile, target.resolve("test.txt")); + + assertNotEquals(sourceFile, targetFile); + assertEquals(singletonList("a,b,c"), Files.readAllLines(targetFile)); + } + // end::user_guide_multiple_directories[] + static // tag::user_guide_field_injection[] class SharedTempDirectoryDemo { @@ -59,4 +87,87 @@ void anotherTestThatUsesTheSameTempDir() { } // end::user_guide_field_injection[] + static + // tag::user_guide_cleanup_mode[] + class CleanupModeDemo { + + @Test + void fileTest(@TempDir(cleanup = ON_SUCCESS) Path tempDir) { + // perform test + } + + } + // end::user_guide_cleanup_mode[] + + static + // tag::user_guide_factory_name_prefix[] + class TempDirFactoryDemo { + + @Test + void factoryTest(@TempDir(factory = Factory.class) Path tempDir) { + assertTrue(tempDir.getFileName().toString().startsWith("factoryTest")); + } + + static class Factory implements TempDirFactory { + + @Override + public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) + throws IOException { + return Files.createTempDirectory(extensionContext.getRequiredTestMethod().getName()); + } + + } + + } + // end::user_guide_factory_name_prefix[] + + static + // tag::user_guide_factory_jimfs[] + class InMemoryTempDirDemo { + + @Test + void test(@TempDir(factory = JimfsTempDirFactory.class) Path tempDir) { + // perform test + } + + static class JimfsTempDirFactory implements TempDirFactory { + + private final FileSystem fileSystem = Jimfs.newFileSystem(Configuration.unix()); + + @Override + public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) + throws IOException { + return Files.createTempDirectory(fileSystem.getPath("/"), "junit"); + } + + @Override + public void close() throws IOException { + fileSystem.close(); + } + + } + + } + // end::user_guide_factory_jimfs[] + + // tag::user_guide_composed_annotation[] + @Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.PARAMETER }) + @Retention(RetentionPolicy.RUNTIME) + @TempDir(factory = JimfsTempDirFactory.class) + @interface JimfsTempDir { + } + // end::user_guide_composed_annotation[] + + static + // tag::user_guide_composed_annotation_usage[] + class JimfsTempDirAnnotationDemo { + + @Test + void test(@JimfsTempDir Path tempDir) { + // perform test + } + + } + // end::user_guide_composed_annotation_usage[] + } diff --git a/documentation/src/test/java/example/TestInfoDemo.java b/documentation/src/test/java/example/TestInfoDemo.java index 806397b9408d..ac8f044edf17 100644 --- a/documentation/src/test/java/example/TestInfoDemo.java +++ b/documentation/src/test/java/example/TestInfoDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/TestReporterDemo.java b/documentation/src/test/java/example/TestReporterDemo.java index 5638d24f19d8..dbd78d94aa94 100644 --- a/documentation/src/test/java/example/TestReporterDemo.java +++ b/documentation/src/test/java/example/TestReporterDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/TestTemplateDemo.java b/documentation/src/test/java/example/TestTemplateDemo.java index e017af9d7db0..5f0c56a24a37 100644 --- a/documentation/src/test/java/example/TestTemplateDemo.java +++ b/documentation/src/test/java/example/TestTemplateDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/TestingAStackDemo.java b/documentation/src/test/java/example/TestingAStackDemo.java index c9cc1e0adc06..ab7b8339c989 100644 --- a/documentation/src/test/java/example/TestingAStackDemo.java +++ b/documentation/src/test/java/example/TestingAStackDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/TimeoutDemo.java b/documentation/src/test/java/example/TimeoutDemo.java index 5959befda41f..82e89ce48479 100644 --- a/documentation/src/test/java/example/TimeoutDemo.java +++ b/documentation/src/test/java/example/TimeoutDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -13,9 +13,12 @@ import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.Timeout.ThreadMode; +@Tag("timeout") // tag::user_guide[] class TimeoutDemo { @@ -26,9 +29,15 @@ void setUp() { } @Test - @Timeout(value = 100, unit = TimeUnit.MILLISECONDS) - void failsIfExecutionTimeExceeds100Milliseconds() { - // fails if execution time exceeds 100 milliseconds + @Timeout(value = 500, unit = TimeUnit.MILLISECONDS) + void failsIfExecutionTimeExceeds500Milliseconds() { + // fails if execution time exceeds 500 milliseconds + } + + @Test + @Timeout(value = 500, unit = TimeUnit.MILLISECONDS, threadMode = ThreadMode.SEPARATE_THREAD) + void failsIfExecutionTimeExceeds500MillisecondsInSeparateThread() { + // fails if execution time exceeds 500 milliseconds, the test code is executed in a separate thread } } diff --git a/documentation/src/test/java/example/UsingTheLauncherDemo.java b/documentation/src/test/java/example/UsingTheLauncherDemo.java index e7e2dbe001b3..403e9084c610 100644 --- a/documentation/src/test/java/example/UsingTheLauncherDemo.java +++ b/documentation/src/test/java/example/UsingTheLauncherDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -19,8 +19,14 @@ import java.nio.file.Path; import java.nio.file.Paths; +import org.junit.platform.engine.FilterResult; +import org.junit.platform.engine.TestDescriptor; import org.junit.platform.launcher.Launcher; +import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.LauncherSession; +import org.junit.platform.launcher.LauncherSessionListener; +import org.junit.platform.launcher.PostDiscoveryFilter; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; import org.junit.platform.launcher.core.LauncherConfig; @@ -51,13 +57,16 @@ void discovery() { ) .build(); - Launcher launcher = LauncherFactory.create(); + try (LauncherSession session = LauncherFactory.openSession()) { + TestPlan testPlan = session.getLauncher().discover(request); - TestPlan testPlan = launcher.discover(request); + // ... discover additional test plans or execute tests + } // end::discovery[] // @formatter:on } + @org.junit.jupiter.api.Tag("exclude") @org.junit.jupiter.api.Test @SuppressWarnings("unused") void execution() { @@ -73,16 +82,22 @@ void execution() { ) .build(); - Launcher launcher = LauncherFactory.create(); - - // Register a listener of your choice SummaryGeneratingListener listener = new SummaryGeneratingListener(); - launcher.registerTestExecutionListeners(listener); - launcher.execute(request); + try (LauncherSession session = LauncherFactory.openSession()) { + Launcher launcher = session.getLauncher(); + // Register a listener of your choice + launcher.registerTestExecutionListeners(listener); + // Discover tests and build a test plan + TestPlan testPlan = launcher.discover(request); + // Execute test plan + launcher.execute(testPlan); + // Alternatively, execute the request directly + launcher.execute(request); + } TestExecutionSummary summary = listener.getSummary(); - // Do something with the TestExecutionSummary. + // Do something with the summary... // end::execution[] // @formatter:on @@ -96,19 +111,25 @@ void launcherConfig() { // tag::launcherConfig[] LauncherConfig launcherConfig = LauncherConfig.builder() .enableTestEngineAutoRegistration(false) + .enableLauncherSessionListenerAutoRegistration(false) + .enableLauncherDiscoveryListenerAutoRegistration(false) + .enablePostDiscoveryFilterAutoRegistration(false) .enableTestExecutionListenerAutoRegistration(false) .addTestEngines(new CustomTestEngine()) + .addLauncherSessionListeners(new CustomLauncherSessionListener()) + .addLauncherDiscoveryListeners(new CustomLauncherDiscoveryListener()) + .addPostDiscoveryFilters(new CustomPostDiscoveryFilter()) .addTestExecutionListeners(new LegacyXmlReportGeneratingListener(reportsDir, out)) .addTestExecutionListeners(new CustomTestExecutionListener()) .build(); - Launcher launcher = LauncherFactory.create(launcherConfig); - LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() .selectors(selectPackage("com.example.mytests")) .build(); - launcher.execute(request); + try (LauncherSession session = LauncherFactory.openSession(launcherConfig)) { + session.getLauncher().execute(request); + } // end::launcherConfig[] // @formatter:on } @@ -120,3 +141,16 @@ class MyTestClass { class CustomTestExecutionListener implements TestExecutionListener { } + +class CustomLauncherSessionListener implements LauncherSessionListener { +} + +class CustomLauncherDiscoveryListener implements LauncherDiscoveryListener { +} + +class CustomPostDiscoveryFilter implements PostDiscoveryFilter { + @Override + public FilterResult apply(TestDescriptor object) { + return FilterResult.included("includes everything"); + } +} diff --git a/documentation/src/test/java/example/callbacks/AbstractDatabaseTests.java b/documentation/src/test/java/example/callbacks/AbstractDatabaseTests.java index aec3d20ec30a..a6eef4489d07 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/callbacks/BrokenLifecycleMethodConfigDemo.java b/documentation/src/test/java/example/callbacks/BrokenLifecycleMethodConfigDemo.java index 7d3ed08f75eb..03da5ff8f086 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/callbacks/DatabaseTestsDemo.java b/documentation/src/test/java/example/callbacks/DatabaseTestsDemo.java index aadda9995f2c..792c778810c3 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/callbacks/Extension1.java b/documentation/src/test/java/example/callbacks/Extension1.java index a02451794e86..f0f5e697ba8a 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/callbacks/Extension2.java b/documentation/src/test/java/example/callbacks/Extension2.java index ea0cf0467eb7..a7cc878f1f76 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/callbacks/Logger.java b/documentation/src/test/java/example/callbacks/Logger.java index e3e3a76dcb65..ab2271256b3f 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/defaultmethods/ComparableContract.java b/documentation/src/test/java/example/defaultmethods/ComparableContract.java index 0002836ebada..f72d80f8894c 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/defaultmethods/EqualsContract.java b/documentation/src/test/java/example/defaultmethods/EqualsContract.java index 47e1252d6236..36e30258ca92 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/defaultmethods/StringTests.java b/documentation/src/test/java/example/defaultmethods/StringTests.java index 965100debfbd..1449f28f8dbd 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/defaultmethods/Testable.java b/documentation/src/test/java/example/defaultmethods/Testable.java index 273a225c20ff..84bf397b87eb 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/exception/IgnoreIOExceptionExtension.java b/documentation/src/test/java/example/exception/IgnoreIOExceptionExtension.java index 28505bf3e7a9..f2010d1b8efb 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/exception/IgnoreIOExceptionTests.java b/documentation/src/test/java/example/exception/IgnoreIOExceptionTests.java index f4de1484fad7..ca706586e858 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/exception/MultipleHandlersTestCase.java b/documentation/src/test/java/example/exception/MultipleHandlersTestCase.java index 48ee9ec88d7d..3db997fe97a0 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/exception/RecordStateOnErrorExtension.java b/documentation/src/test/java/example/exception/RecordStateOnErrorExtension.java index 3f0422107036..ed4c76979b32 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/extensions/Random.java b/documentation/src/test/java/example/extensions/Random.java new file mode 100644 index 000000000000..4b9a904d0bb1 --- /dev/null +++ b/documentation/src/test/java/example/extensions/Random.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.extensions; + +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; + +//tag::user_guide[] +@Target({ ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(RandomNumberExtension.class) +public @interface Random { +} +//end::user_guide[] diff --git a/documentation/src/test/java/example/extensions/RandomNumberDemo.java b/documentation/src/test/java/example/extensions/RandomNumberDemo.java new file mode 100644 index 000000000000..8dd274bda73c --- /dev/null +++ b/documentation/src/test/java/example/extensions/RandomNumberDemo.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.extensions; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +// tag::user_guide[] +class RandomNumberDemo { + + // Use static randomNumber0 field anywhere in the test class, + // including @BeforeAll or @AfterEach lifecycle methods. + @Random + private static Integer randomNumber0; + + // Use randomNumber1 field in test methods and @BeforeEach + // or @AfterEach lifecycle methods. + @Random + private int randomNumber1; + + RandomNumberDemo(@Random int randomNumber2) { + // Use randomNumber2 in constructor + } + + @BeforeEach + void beforeEach(@Random int randomNumber3) { + // Use randomNumber3 in @BeforeEach method. + } + + @Test + void test(@Random int randomNumber4) { + // Use randomNumber4 in test method. + } + +} +// end::user_guide[] diff --git a/documentation/src/test/java/example/extensions/RandomNumberExtension.java b/documentation/src/test/java/example/extensions/RandomNumberExtension.java new file mode 100644 index 000000000000..3bbece08e206 --- /dev/null +++ b/documentation/src/test/java/example/extensions/RandomNumberExtension.java @@ -0,0 +1,94 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.extensions; + +// tag::user_guide[] + +import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedFields; + +import java.lang.reflect.Field; +import java.util.function.Predicate; + +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.junit.platform.commons.support.ModifierSupport; + +// end::user_guide[] +// @formatter:off +// tag::user_guide[] +class RandomNumberExtension + implements BeforeAllCallback, TestInstancePostProcessor, ParameterResolver { + + private final java.util.Random random = new java.util.Random(System.nanoTime()); + + /** + * Inject a random integer into static fields that are annotated with + * {@code @Random} and can be assigned an integer value. + */ + @Override + public void beforeAll(ExtensionContext context) { + Class testClass = context.getRequiredTestClass(); + injectFields(testClass, null, ModifierSupport::isStatic); + } + + /** + * Inject a random integer into non-static fields that are annotated with + * {@code @Random} and can be assigned an integer value. + */ + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + Class testClass = context.getRequiredTestClass(); + injectFields(testClass, testInstance, ModifierSupport::isNotStatic); + } + + /** + * Determine if the parameter is annotated with {@code @Random} and can be + * assigned an integer value. + */ + @Override + public boolean supportsParameter(ParameterContext pc, ExtensionContext ec) { + return pc.isAnnotated(Random.class) && isInteger(pc.getParameter().getType()); + } + + /** + * Resolve a random integer. + */ + @Override + public Integer resolveParameter(ParameterContext pc, ExtensionContext ec) { + return this.random.nextInt(); + } + + private void injectFields(Class testClass, Object testInstance, + Predicate predicate) { + + predicate = predicate.and(field -> isInteger(field.getType())); + findAnnotatedFields(testClass, Random.class, predicate) + .forEach(field -> { + try { + field.setAccessible(true); + field.set(testInstance, this.random.nextInt()); + } + catch (Exception ex) { + throw new RuntimeException(ex); + } + }); + } + + private static boolean isInteger(Class type) { + return int.class.isAssignableFrom(type); + } + +} +// end::user_guide[] +// @formatter:on diff --git a/documentation/src/test/java/example/interceptor/SwingEdtInterceptor.java b/documentation/src/test/java/example/interceptor/SwingEdtInterceptor.java index 78f21e8e3b66..b12094f382b9 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/registration/DocumentationDemo.java b/documentation/src/test/java/example/registration/DocumentationDemo.java index fe542cf37c28..973b4f1967e3 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/registration/WebServerDemo.java b/documentation/src/test/java/example/registration/WebServerDemo.java index c99d9c0e70f6..25a6b2797305 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/session/GlobalSetupTeardownListener.java b/documentation/src/test/java/example/session/GlobalSetupTeardownListener.java new file mode 100644 index 000000000000..595be12b4717 --- /dev/null +++ b/documentation/src/test/java/example/session/GlobalSetupTeardownListener.java @@ -0,0 +1,92 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.session; + +//tag::user_guide[] +import static java.net.InetAddress.getLoopbackAddress; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.InetSocketAddress; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import com.sun.net.httpserver.HttpServer; + +import org.junit.platform.launcher.LauncherSession; +import org.junit.platform.launcher.LauncherSessionListener; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestPlan; + +public class GlobalSetupTeardownListener implements LauncherSessionListener { + + private Fixture fixture; + + @Override + public void launcherSessionOpened(LauncherSession session) { + // Avoid setup for test discovery by delaying it until tests are about to be executed + session.getLauncher().registerTestExecutionListeners(new TestExecutionListener() { + @Override + public void testPlanExecutionStarted(TestPlan testPlan) { + //end::user_guide[] + if (!testPlan.getConfigurationParameters().getBoolean("enableHttpServer").orElse(false)) { + // avoid starting multiple HTTP servers unnecessarily from UsingTheLauncherDemo + return; + } + //tag::user_guide[] + if (fixture == null) { + fixture = new Fixture(); + fixture.setUp(); + } + } + }); + } + + @Override + public void launcherSessionClosed(LauncherSession session) { + if (fixture != null) { + fixture.tearDown(); + fixture = null; + } + } + + static class Fixture { + + private HttpServer server; + private ExecutorService executorService; + + void setUp() { + try { + server = HttpServer.create(new InetSocketAddress(getLoopbackAddress(), 0), 0); + } + catch (IOException e) { + throw new UncheckedIOException("Failed to start HTTP server", e); + } + server.createContext("/test", exchange -> { + exchange.sendResponseHeaders(204, -1); + exchange.close(); + }); + executorService = Executors.newCachedThreadPool(); + server.setExecutor(executorService); + server.start(); // <1> + int port = server.getAddress().getPort(); + System.setProperty("http.server.host", getLoopbackAddress().getHostAddress()); // <2> + System.setProperty("http.server.port", String.valueOf(port)); // <3> + } + + void tearDown() { + server.stop(0); // <4> + executorService.shutdownNow(); + } + } + +} +//end::user_guide[] diff --git a/documentation/src/test/java/example/session/HttpTests.java b/documentation/src/test/java/example/session/HttpTests.java new file mode 100644 index 000000000000..cbdf5367cdfd --- /dev/null +++ b/documentation/src/test/java/example/session/HttpTests.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.session; + +//tag::user_guide[] +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; + +import org.junit.jupiter.api.Test; + +class HttpTests { + + @Test + void respondsWith204() throws Exception { + String host = System.getProperty("http.server.host"); // <1> + String port = System.getProperty("http.server.port"); // <2> + URL url = URI.create("http://" + host + ":" + port + "/test").toURL(); + + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + int responseCode = connection.getResponseCode(); // <3> + + assertEquals(204, responseCode); // <4> + } +} +//end::user_guide[] diff --git a/documentation/src/test/java/example/testinterface/TestInterfaceDemo.java b/documentation/src/test/java/example/testinterface/TestInterfaceDemo.java index 86b64961afb3..b24ab3413733 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/testinterface/TestInterfaceDynamicTestsDemo.java b/documentation/src/test/java/example/testinterface/TestInterfaceDynamicTestsDemo.java index ee611239e61d..694192dbabf0 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/testinterface/TestLifecycleLogger.java b/documentation/src/test/java/example/testinterface/TestLifecycleLogger.java index d6dae294e16a..b269bfb69a1b 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/testinterface/TimeExecutionLogger.java b/documentation/src/test/java/example/testinterface/TimeExecutionLogger.java index 944e7c72ada7..b3a53feae91a 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/testkit/EngineTestKitAllEventsDemo.java b/documentation/src/test/java/example/testkit/EngineTestKitAllEventsDemo.java index 21f05d439caf..5df68019e433 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -41,7 +41,7 @@ class EngineTestKitAllEventsDemo { void verifyAllJupiterEvents() { Writer writer = // create a java.io.Writer for debug output // end::user_guide[] - // For the demo, we are simply swallowing the debug output. + // For the demo, we are swallowing the debug output. new StringWriter(); // tag::user_guide[] diff --git a/documentation/src/test/java/example/testkit/EngineTestKitFailedMethodDemo.java b/documentation/src/test/java/example/testkit/EngineTestKitFailedMethodDemo.java index 78d2173f2ebd..40ea51ba8a80 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/testkit/EngineTestKitSkippedMethodDemo.java b/documentation/src/test/java/example/testkit/EngineTestKitSkippedMethodDemo.java index 708dfe598c38..1d06770f1a25 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/testkit/EngineTestKitStatisticsDemo.java b/documentation/src/test/java/example/testkit/EngineTestKitStatisticsDemo.java index ef3ca601494b..b9576352389b 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/timing/TimingExtension.java b/documentation/src/test/java/example/timing/TimingExtension.java index 6c4fcc73027e..b6b51d61ce99 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/example/timing/TimingExtensionTests.java b/documentation/src/test/java/example/timing/TimingExtensionTests.java index b9b11a1582d6..ee84b196b066 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/extensions/ExpectToFail.java b/documentation/src/test/java/extensions/ExpectToFail.java index c325a81de5db..8dd21a717caa 100644 --- a/documentation/src/test/java/extensions/ExpectToFail.java +++ b/documentation/src/test/java/extensions/ExpectToFail.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/org/junit/api/tools/AbstractApiReportWriter.java b/documentation/src/test/java/org/junit/api/tools/AbstractApiReportWriter.java index ed59e28e792b..6b9a143ca463 100644 --- a/documentation/src/test/java/org/junit/api/tools/AbstractApiReportWriter.java +++ b/documentation/src/test/java/org/junit/api/tools/AbstractApiReportWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/org/junit/api/tools/ApiReport.java b/documentation/src/test/java/org/junit/api/tools/ApiReport.java index 067d5318f8eb..b21e63e8face 100644 --- a/documentation/src/test/java/org/junit/api/tools/ApiReport.java +++ b/documentation/src/test/java/org/junit/api/tools/ApiReport.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/org/junit/api/tools/ApiReportGenerator.java b/documentation/src/test/java/org/junit/api/tools/ApiReportGenerator.java index 963d0c4553cb..7bdb6c21b40d 100644 --- a/documentation/src/test/java/org/junit/api/tools/ApiReportGenerator.java +++ b/documentation/src/test/java/org/junit/api/tools/ApiReportGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/org/junit/api/tools/ApiReportWriter.java b/documentation/src/test/java/org/junit/api/tools/ApiReportWriter.java index 8fcadb342744..c231d1b4ba43 100644 --- a/documentation/src/test/java/org/junit/api/tools/ApiReportWriter.java +++ b/documentation/src/test/java/org/junit/api/tools/ApiReportWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/org/junit/api/tools/AsciidocApiReportWriter.java b/documentation/src/test/java/org/junit/api/tools/AsciidocApiReportWriter.java index 98f5f8fb2a51..05748d36aee3 100644 --- a/documentation/src/test/java/org/junit/api/tools/AsciidocApiReportWriter.java +++ b/documentation/src/test/java/org/junit/api/tools/AsciidocApiReportWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/org/junit/api/tools/HtmlApiReportWriter.java b/documentation/src/test/java/org/junit/api/tools/HtmlApiReportWriter.java index b260f8ff1ce3..48193368370e 100644 --- a/documentation/src/test/java/org/junit/api/tools/HtmlApiReportWriter.java +++ b/documentation/src/test/java/org/junit/api/tools/HtmlApiReportWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/java/org/junit/api/tools/MarkdownApiReportWriter.java b/documentation/src/test/java/org/junit/api/tools/MarkdownApiReportWriter.java index 65f0f918b2f8..6294ac58179b 100644 --- a/documentation/src/test/java/org/junit/api/tools/MarkdownApiReportWriter.java +++ b/documentation/src/test/java/org/junit/api/tools/MarkdownApiReportWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/kotlin/example/FibonacciCalculator.kt b/documentation/src/test/kotlin/example/FibonacciCalculator.kt index ae16322fc335..33ddbe089ff1 100644 --- a/documentation/src/test/kotlin/example/FibonacciCalculator.kt +++ b/documentation/src/test/kotlin/example/FibonacciCalculator.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/documentation/src/test/kotlin/example/KotlinAssertionsDemo.kt b/documentation/src/test/kotlin/example/KotlinAssertionsDemo.kt index 23c901fa1672..0ea73f7f99d8 100644 --- a/documentation/src/test/kotlin/example/KotlinAssertionsDemo.kt +++ b/documentation/src/test/kotlin/example/KotlinAssertionsDemo.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -13,15 +13,16 @@ package example import example.domain.Person import example.util.Calculator -import java.time.Duration import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue +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.assertThrows import org.junit.jupiter.api.assertTimeout import org.junit.jupiter.api.assertTimeoutPreemptively +import java.time.Duration class KotlinAssertionsDemo { @@ -48,7 +49,8 @@ class KotlinAssertionsDemo { @Test fun `grouped assertions`() { - assertAll("Person properties", + assertAll( + "Person properties", { assertEquals("Jane", person.firstName) }, { assertEquals("Doe", person.lastName) } ) @@ -56,7 +58,8 @@ class KotlinAssertionsDemo { @Test fun `grouped assertions from a stream`() { - assertAll("People with first name starting with J", + assertAll( + "People with first name starting with J", people .stream() .map { @@ -68,11 +71,15 @@ class KotlinAssertionsDemo { @Test fun `grouped assertions from a collection`() { - assertAll("People with last name of Doe", + assertAll( + "People with last name of Doe", people.map { { assertEquals("Doe", it.lastName) } } ) } + // end::user_guide[] + @Tag("timeout") + // tag::user_guide[] @Test fun `timeout not exceeded testing`() { val fibonacciCalculator = FibonacciCalculator() @@ -83,6 +90,7 @@ class KotlinAssertionsDemo { } // end::user_guide[] + @Tag("timeout") @extensions.ExpectToFail // tag::user_guide[] @Test diff --git a/documentation/src/test/kotlin/example/registration/KotlinWebServerDemo.kt b/documentation/src/test/kotlin/example/registration/KotlinWebServerDemo.kt index 73b36a59365e..52a390fe7e0f 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -17,11 +17,11 @@ import org.junit.jupiter.api.extension.RegisterExtension class KotlinWebServerDemo { companion object { - @JvmField + @JvmStatic @RegisterExtension val server = WebServerExtension.builder() - .enableSecurity(false) - .build() + .enableSecurity(false) + .build() } @Test diff --git a/documentation/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener b/documentation/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener new file mode 100644 index 000000000000..f6d297627eeb --- /dev/null +++ b/documentation/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener @@ -0,0 +1 @@ +example.session.GlobalSetupTeardownListener diff --git a/documentation/src/test/resources/two-column.csv b/documentation/src/test/resources/two-column.csv index 2ef0d215adac..7ebb4c545f1b 100644 --- a/documentation/src/test/resources/two-column.csv +++ b/documentation/src/test/resources/two-column.csv @@ -1,4 +1,5 @@ -Country, reference +COUNTRY, REFERENCE Sweden, 1 Poland, 2 "United States of America", 3 +France, 700_000 diff --git a/gradle.properties b/gradle.properties index 30e3b4488249..8bbcba21a037 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,58 +1,32 @@ group = org.junit -version = 5.7.0 +version = 5.10.0 jupiterGroup = org.junit.jupiter platformGroup = org.junit.platform -platformVersion = 1.7.0 +platformVersion = 1.10.0 vintageGroup = org.junit.vintage -vintageVersion = 5.7.0 +vintageVersion = 5.10.0 defaultBuiltBy = JUnit Team # We need more metaspace due to apparent memory leak in Asciidoctor/JRuby -org.gradle.jvmargs=-Xmx1g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError +# The exports are needed due to https://github.com/diffplug/spotless/issues/834 +org.gradle.jvmargs=-Xmx1g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError \ + --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \ + --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.caching=true org.gradle.parallel=true +org.gradle.java.installations.fromEnv=JDK8,JDK18,JDK19,JDK20,JDK21 +org.gradle.kotlin.dsl.allWarningsAsErrors=true -# Dependencies -apiguardian.version=1.1.0 -assertj.version=3.16.1 -junit4.version=4.13 -junit4Min.version=4.12 -opentest4j.version=1.2.0 -picocli.version=4.5.0 -univocity-parsers.version=2.9.0 +# Test Distribution +gradle.internal.testdistribution.writeTraceFile=true -# Test Dependencies -archunit.version=0.14.1 -bartholdy.version=0.2.3 -classgraph.version=4.8.87 -commons-io.version=2.7 -kotlinx-coroutines-core.version=1.3.9 -groovy.version=3.0.5 -log4j.version=2.13.3 -mockito.version=3.5.0 -slf4j.version=1.7.30 -spock.version=1.3-groovy-2.5 - -# Tools -checkstyle.version=8.31 -jacoco.version=0.8.5 -jmh.version=1.25 -ktlint.version=0.35.0 -surefire.version=2.22.2 -bnd.version=5.1.2 - -# Plugins -gradle.enterprise.plugin.version=3.3.1 -versioning.plugin.version=2.14.0 -versions.plugin.version=0.29.0 -spotless.plugin.version=5.1.1 -git-publish.plugin.version=2.1.3 -kotlin.plugin.version=1.4.0 -asciidoctor-pdf.version=1.5.3 -asciidoctor.plugin.version=3.2.0 -jmh.plugin.version=0.5.0 -nohttp.plugin.version=0.0.5.RELEASE +# Omit automatic compile dependency on kotlin-stdlib +# https://kotlinlang.org/docs/gradle.html#dependency-on-the-standard-library +kotlin.stdlib.default.dependency=false diff --git a/gradle/build-scan-user-data.gradle b/gradle/build-scan-user-data.gradle deleted file mode 100644 index 8436692dc512..000000000000 --- a/gradle/build-scan-user-data.gradle +++ /dev/null @@ -1,215 +0,0 @@ -// Source: https://github.com/gradle/gradle-build-scan-snippets/blob/master/guided-trials-default-custom-user-data/default-custom-user-data.gradle - -tagOs() -tagIde() -tagCiOrLocal() -addCiMetadata() -addGitMetadata() -addTestParallelization() - -// Add here other scripts, if needed -//apply from:"${rootProject.projectDir}/<>" - -void tagOs() { - buildScan.tag System.getProperty('os.name') -} - -void tagIde() { - if (project.hasProperty('android.injected.invoked.from.ide')) { - buildScan.tag 'Android Studio' - } else if (System.getProperty('idea.version')) { - buildScan.tag 'IntelliJ IDEA' - } else if (System.getProperty('eclipse.buildId')) { - buildScan.tag 'Eclipse' - } else if (!isCi()) { - buildScan.tag 'Cmd Line' - } -} - -void tagCiOrLocal() { - buildScan.tag(isCi() ? 'CI' : 'LOCAL') -} - -void addCiMetadata() { - if (isJenkins()) { - if (System.getenv('BUILD_URL')) { - buildScan.link 'Jenkins build', System.getenv('BUILD_URL') - } - if (System.getenv('BUILD_NUMBER')) { - buildScan.value 'CI build number', System.getenv('BUILD_NUMBER') - } - if (System.getenv('NODE_NAME')) { - def agentName = System.getenv('NODE_NAME') == 'master' ? 'master-node' : System.getenv('NODE_NAME') - buildScan.tag agentName - buildScan.value 'CI node name', agentName - } - if (System.getenv('JOB_NAME')) { - def jobNameLabel = 'CI job' - def jobName = System.getenv('JOB_NAME') - buildScan.value jobNameLabel, jobName - addCustomValueSearchLink 'CI job build scans', [(jobNameLabel): jobName] - } - if (System.getenv('STAGE_NAME')) { - def stageNameLabel = 'CI stage' - def stageName = System.getenv('STAGE_NAME') - buildScan.value stageNameLabel, stageName - addCustomValueSearchLink 'CI stage build scans', [(stageNameLabel): stageName] - } - } - - if (isTeamCity()) { - def teamCityConfigurationFileProp = 'teamcity.configuration.properties.file' - if (project.hasProperty(teamCityConfigurationFileProp)) { - def properties = new Properties() - properties.load(new FileInputStream("${project.property(teamCityConfigurationFileProp)}")) - def teamCityServerUrl = properties.getProperty("teamcity.serverUrl") - if (teamCityServerUrl && project.hasProperty('build.number') && project.hasProperty('teamcity.buildType.id')) { - def teamCityBuildNumber = project.property('build.number') - def teamCityBuildTypeId = project.property('teamcity.buildType.id') - buildScan.link 'TeamCity build', "${appendIfMissing(teamCityServerUrl, '/')}viewLog.html?buildNumber=${teamCityBuildNumber}&buildTypeId=${teamCityBuildTypeId}" - } - } - if (project.hasProperty('build.number')) { - buildScan.value 'CI build number', project.property('build.number') - } - if (project.hasProperty('agent.name')) { - def agentName = project.property('agent.name') - buildScan.tag agentName - buildScan.value 'CI agent name', agentName - } - } - - if (isCircleCI()) { - if (System.getenv('CIRCLE_BUILD_URL')) { - buildScan.link 'CircleCI build', System.getenv('CIRCLE_BUILD_URL') - } - if (System.getenv('CIRCLE_BUILD_NUM')) { - buildScan.value 'CI build number', System.getenv('CIRCLE_BUILD_NUM') - } - if (System.getenv('CIRCLE_JOB')) { - def jobLabel = 'CI job' - def job = System.getenv('CIRCLE_JOB') - buildScan.value jobLabel, job - addCustomValueSearchLink 'CI job build scans', [(jobLabel): job] - } - if (System.getenv('CIRCLE_WORKFLOW_ID')) { - def workflowIdLabel = 'CI workflow' - def workflowId = System.getenv('CIRCLE_WORKFLOW_ID') - buildScan.value workflowIdLabel, workflowId - addCustomValueSearchLink 'CI workflow build scans', [(workflowIdLabel): workflowId] - } - } - - if (isBamboo()) { - if (System.getenv('bamboo_resultsUrl')) { - buildScan.link 'Bamboo build', System.getenv('bamboo_resultsUrl') - } - if (System.getenv('bamboo_buildNumber')) { - buildScan.value 'CI build number', System.getenv('bamboo_buildNumber') - } - if (System.getenv('bamboo_planName')) { - def planNameLabel = 'CI plan' - def planName = System.getenv('bamboo_planName') - buildScan.value planNameLabel, planName - addCustomValueSearchLink 'CI plan build scans', [(planNameLabel): planName] - } - if (System.getenv('bamboo_buildPlanName')) { - def jobNameLabel = 'CI job' - def jobName = System.getenv('bamboo_buildPlanName') - buildScan.value jobNameLabel, jobName - addCustomValueSearchLink 'CI job build scans', [(jobNameLabel): jobName] - } - if (System.getenv('bamboo_agentId')) { - def agentId = System.getenv('bamboo_agentId') - buildScan.tag agentId - buildScan.value 'CI agent ID', agentId - } - } - - if (isGitHubActions()) { - def repo = System.getenv('GITHUB_REPOSITORY') - def runId = System.getenv('GITHUB_RUN_ID') - if (repo && runId) { - buildScan.link 'GitHub Actions run', "https://github.com/$repo/actions/runs/$runId" - } - if (System.getenv('GITHUB_WORKFLOW')) { - def ghActionWorkflowLabel = 'GitHub workflow' - def ghActionWorkflowName = System.getenv('GITHUB_WORKFLOW') - buildScan.value ghActionWorkflowLabel, ghActionWorkflowName - addCustomValueSearchLink 'GitHub workflow build scans', [(ghActionWorkflowLabel): ghActionWorkflowName] - } - } -} - -void addGitMetadata() { - buildScan.background { - def gitCommitId = versioning.info.commit - def gitBranchName = versioning.info.branch - def gitDirty = versioning.info.dirty - - if (gitCommitId) { - def commitIdLabel = 'Git commit id' - value commitIdLabel, gitCommitId - addCustomValueSearchLink 'Git commit id build scans', [(commitIdLabel): gitCommitId] - link 'Github Source', "https://github.com/junit-team/junit5/tree/" + gitCommitId - } - if (gitBranchName) { - tag gitBranchName - value 'Git branch', gitBranchName - } - if (gitDirty) { - tag 'Dirty' - } - } -} - -void addTestParallelization() { - allprojects { p -> - p.tasks.withType(Test).configureEach { t -> doFirst { buildScan.value "${t.identityPath}#maxParallelForks", t.maxParallelForks.toString() } } - } -} - -static boolean isCi() { - isJenkins() || isTeamCity() || isCircleCI() || isBamboo() || isGitHubActions() -} - -static boolean isJenkins() { - System.getenv('JENKINS_URL') -} - -static boolean isTeamCity() { - System.getenv('TEAMCITY_VERSION') -} - -static boolean isCircleCI() { - System.getenv('CIRCLECI') -} - -static boolean isBamboo() { - System.getenv('bamboo_resultsUrl') -} - -static boolean isGitHubActions() { - System.getenv('GITHUB_ACTIONS') -} - -void addCustomValueSearchLink(String title, Map search) { - if (buildScan.server) { - buildScan.link title, customValueSearchUrl(search) - } -} - -String customValueSearchUrl(Map search) { - def query = search.collect { name, value -> - "search.names=${encodeURL(name)}&search.values=${encodeURL(value)}" - }.join('&') - "${appendIfMissing(buildScan.server, '/')}scans?$query#selection.buildScanB=%7BSCAN_ID%7D" -} - -static String encodeURL(String url) { - URLEncoder.encode(url, 'UTF-8') -} - -static String appendIfMissing(String str, String suffix) { - str.endsWith(suffix) ? str : str + suffix -} diff --git a/src/checkstyle/checkstyleMain.xml b/gradle/config/checkstyle/checkstyleMain.xml similarity index 100% rename from src/checkstyle/checkstyleMain.xml rename to gradle/config/checkstyle/checkstyleMain.xml diff --git a/src/checkstyle/checkstyleTest.xml b/gradle/config/checkstyle/checkstyleTest.xml similarity index 100% rename from src/checkstyle/checkstyleTest.xml rename to gradle/config/checkstyle/checkstyleTest.xml diff --git a/src/checkstyle/suppressions.xml b/gradle/config/checkstyle/suppressions.xml similarity index 50% rename from src/checkstyle/suppressions.xml rename to gradle/config/checkstyle/suppressions.xml index b15726f91ffb..05801fccce21 100644 --- a/src/checkstyle/suppressions.xml +++ b/gradle/config/checkstyle/suppressions.xml @@ -1,7 +1,7 @@ + files="junit-platform-commons[\\/]src[\\/]main[\\/]java.+?[\\/]org[\\/]junit[\\/]platform[\\/]commons[\\/]util[\\/]*"/> + files="junit-platform-console[\\/]src[\\/]main[\\/]java.+?[\\/]org[\\/]junit[\\/]platform[\\/]console[\\/]*"/> diff --git a/src/eclipse/junit-eclipse-formatter-settings.xml b/gradle/config/eclipse/junit-eclipse-formatter-settings.xml similarity index 100% rename from src/eclipse/junit-eclipse-formatter-settings.xml rename to gradle/config/eclipse/junit-eclipse-formatter-settings.xml diff --git a/gradle/config/eclipse/junit-eclipse.importorder b/gradle/config/eclipse/junit-eclipse.importorder new file mode 100644 index 000000000000..56cb0619d774 --- /dev/null +++ b/gradle/config/eclipse/junit-eclipse.importorder @@ -0,0 +1,12 @@ +#Organize Import Order +0=java +1=javax +2=jdk +3=aQute +4=junit +5=de +6=com +7=example +8=extensions +9=io +10=org diff --git a/src/spotless/eclipse-public-license-2.0.java b/gradle/config/spotless/eclipse-public-license-2.0.java similarity index 82% rename from src/spotless/eclipse-public-license-2.0.java rename to gradle/config/spotless/eclipse-public-license-2.0.java index 5d687704ddb7..9bbea1d478a3 100644 --- a/src/spotless/eclipse-public-license-2.0.java +++ b/gradle/config/spotless/eclipse-public-license-2.0.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 000000000000..d69b3f911c10 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,81 @@ +[versions] +ant = "1.10.13" +apiguardian = "1.1.2" +asciidoctor-pdf = "1.5.3" +asciidoctor-plugins = "4.0.0-alpha.1" # Check if workaround in documentation.gradle.kts can be removed when upgrading +assertj = "3.24.2" +bnd = "6.4.0" +checkstyle = "10.12.1" +gradleVersionsPlugin = "0.47.0" +jacoco = "0.8.7" +jmh = "1.36" +junit4 = "4.13.2" +junit4Osgi = "4.13.2_1" +junit4Min = "4.12" +ktlint = "0.48.2" +log4j = "2.20.0" +opentest4j = "1.3.0" +openTestReporting = "0.1.0-M1" +surefire = "3.1.2" +xmlunit = "2.9.1" + +[libraries] +ant = { module = "org.apache.ant:ant", version.ref = "ant" } +ant-junit = { module = "org.apache.ant:ant-junit", version.ref = "ant" } +ant-junitlauncher = { module = "org.apache.ant:ant-junitlauncher", version.ref = "ant" } +apiguardian = { module = "org.apiguardian:apiguardian-api", version.ref = "apiguardian" } +archunit = { module = "com.tngtech.archunit:archunit-junit5", version = "1.0.1" } +assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" } +bartholdy = { module = "de.sormuras:bartholdy", version = "0.2.3" } +bndlib = { module = "biz.aQute.bnd:biz.aQute.bndlib", version.ref = "bnd" } +checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" } +classgraph = { module = "io.github.classgraph:classgraph", version = "4.8.161" } +commons-io = { module = "commons-io:commons-io", version = "2.13.0" } +gradle-commonCustomUserData = { module = "com.gradle:common-custom-user-data-gradle-plugin", version = "1.11" } +gradle-foojayResolver = { module = "org.gradle.toolchains:foojay-resolver", version = "0.6.0" } +gradle-enterprise = { module = "com.gradle:gradle-enterprise-gradle-plugin", version = "3.14" } +gradle-bnd = { module = "biz.aQute.bnd:biz.aQute.bnd.gradle", version.ref = "bnd" } +gradle-shadow = { module = "com.github.johnrengelman:shadow", version = "8.1.1" } +gradle-spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version = "6.19.0" } +gradle-versions = { module = "com.github.ben-manes:gradle-versions-plugin", version.ref = "gradleVersionsPlugin" } +groovy4 = { module = "org.apache.groovy:groovy", version = "4.0.13" } +groovy2-bom = { module = "org.codehaus.groovy:groovy-bom", version = "2.5.21" } +hamcrest = { module = "org.hamcrest:hamcrest", version = "2.2" } +jfrunit = { module = "org.moditect.jfrunit:jfrunit-core", version = "1.0.0.Alpha2" } +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.0" } +junit4 = { module = "junit:junit", version = { require = "[4.12,)", prefer = "4.13.2" } } +kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.7.2" } +log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } +log4j-jul = { module = "org.apache.logging.log4j:log4j-jul", version.ref = "log4j" } +maven = { module = "org.apache.maven:apache-maven", version = "3.9.3" } +mavenSurefirePlugin = { module = "org.apache.maven.plugins:maven-surefire-plugin", version.ref = "surefire" } +memoryfilesystem = { module = "com.github.marschall:memoryfilesystem", version = "2.6.1" } +mockito = { module = "org.mockito:mockito-junit-jupiter", version = "5.4.0" } +opentest4j = { module = "org.opentest4j:opentest4j", version.ref = "opentest4j" } +openTestReporting-events = { module = "org.opentest4j.reporting:open-test-reporting-events", version.ref = "openTestReporting" } +openTestReporting-tooling = { module = "org.opentest4j.reporting:open-test-reporting-tooling", version.ref = "openTestReporting" } +picocli = { module = "info.picocli:picocli", version = "4.7.4" } +slf4j-julBinding = { module = "org.slf4j:slf4j-jdk14", version = "2.0.7" } +spock1 = { module = "org.spockframework:spock-core", version = "1.3-groovy-2.5" } +univocity-parsers = { module = "com.univocity:univocity-parsers", version = "2.9.1" } +xmlunit-assertj = { module = "org.xmlunit:xmlunit-assertj3", version.ref = "xmlunit" } +xmlunit-placeholders = { module = "org.xmlunit:xmlunit-placeholders", version.ref = "xmlunit" } +testingAnnotations = { module = "com.gradle:gradle-enterprise-testing-annotations", version = "1.1" } + +[bundles] +ant = ["ant", "ant-junit", "ant-junitlauncher"] +log4j = ["log4j-core", "log4j-jul"] +xmlunit = ["xmlunit-assertj", "xmlunit-placeholders"] + +[plugins] +asciidoctorConvert = { id = "org.asciidoctor.jvm.convert", version.ref = "asciidoctor-plugins" } +asciidoctorPdf = { id = "org.asciidoctor.jvm.pdf", version.ref = "asciidoctor-plugins" } +buildParameters = { id = "org.gradlex.build-parameters", version = "1.4.3" } +gitPublish = { id = "org.ajoberstar.git-publish", version = "4.2.0" } +jmh = { id = "me.champeau.jmh", version = "0.7.1" } +nohttp = { id = "io.spring.nohttp", version = "0.0.11" } +nexusPublish = { id = "io.github.gradle-nexus.publish-plugin", version = "2.0.0-rc-1" } +versions = { id = "com.github.ben-manes.versions", version.ref = "gradleVersionsPlugin" } diff --git a/gradle/plugins/build-parameters/build.gradle.kts b/gradle/plugins/build-parameters/build.gradle.kts new file mode 100644 index 000000000000..f70f2d8bc062 --- /dev/null +++ b/gradle/plugins/build-parameters/build.gradle.kts @@ -0,0 +1,75 @@ +plugins { + alias(libs.plugins.buildParameters) +} + +group = "junitbuild" + +buildParameters { + pluginId("junitbuild.build-parameters") + bool("ci") { + description = "Whether or not this build is running in a CI environment" + defaultValue = false + fromEnvironment() + } + integer("javaToolchainVersion") { + description = "Defines the Java toolchain version to use for compiling code" + } + group("buildCache") { + string("username") { + description = "Username to authenticate with the remote build cache" + fromEnvironment() + } + string("password") { + description = "Password to authenticate with the remote build cache" + fromEnvironment() + } + string("url") { + description = "URL to the remote build cache" + fromEnvironment() + } + } + group("documentation") { + description = "Parameters controlling how the documentation is built" + bool("replaceCurrentDocs") { + description = "The documentation that is being deployed will replace what's currently deployed as 'current'" + defaultValue = false + } + } + group("enterprise") { + description = "Parameters controlling Gradle Enterprise features" + group("predictiveTestSelection") { + bool("enabled") { + description = "Whether or not to use Predictive Test Selection for selecting tests to execute" + defaultValue = true + } + } + group("testDistribution") { + bool("enabled") { + description = "Whether or not to use Test Distribution for executing tests" + defaultValue = false + fromEnvironment() + } + integer("maxLocalExecutors") { + description = "How many local executors to use for executing tests" + defaultValue = 1 + } + integer("maxRemoteExecutors") { + description = "How many remote executors to request for executing tests" + } + } + } + group("testing") { + description = "Testing related parameters" + bool("enableJaCoCo") { + description = "Enables JaCoCo test coverage reporting" + defaultValue = false + } + bool("enableJFR") { + description = "Enables Java Flight Recorder functionality" + defaultValue = false + } + integer("retries") { + description = "Configures the number of times failing test are retried" + } + } +} diff --git a/gradle/plugins/build.gradle.kts b/gradle/plugins/build.gradle.kts new file mode 100644 index 000000000000..f64d71d4260c --- /dev/null +++ b/gradle/plugins/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + alias(libs.plugins.versions) +} diff --git a/gradle/plugins/common/build.gradle.kts b/gradle/plugins/common/build.gradle.kts new file mode 100644 index 000000000000..0753c6a3099e --- /dev/null +++ b/gradle/plugins/common/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + `kotlin-dsl` +} + +repositories { + gradlePluginPortal() +} + +dependencies { + implementation(projects.buildParameters) + implementation(kotlin("gradle-plugin")) + implementation(libs.gradle.bnd) + implementation(libs.gradle.commonCustomUserData) + implementation(libs.gradle.enterprise) + implementation(libs.gradle.foojayResolver) + implementation(libs.gradle.shadow) + implementation(libs.gradle.spotless) + implementation(libs.gradle.versions) +} diff --git a/gradle/plugins/common/src/main/kotlin/ConfigurationContainerExtensions.kt b/gradle/plugins/common/src/main/kotlin/ConfigurationContainerExtensions.kt new file mode 100644 index 000000000000..ebe7399e9f15 --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/ConfigurationContainerExtensions.kt @@ -0,0 +1,14 @@ + +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.ConfigurationContainer +import org.gradle.kotlin.dsl.NamedDomainObjectContainerCreatingDelegateProvider + +val ConfigurationContainer.creatingResolvable + get() = creatingResolvable {} + +fun ConfigurationContainer.creatingResolvable(configuration: Configuration.() -> Unit) = + NamedDomainObjectContainerCreatingDelegateProvider.of(this) { + isCanBeResolved = true + isCanBeConsumed = false + configuration() + } diff --git a/gradle/plugins/common/src/main/kotlin/JavaLibraryExtension.kt b/gradle/plugins/common/src/main/kotlin/JavaLibraryExtension.kt new file mode 100644 index 000000000000..834374bf06c6 --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/JavaLibraryExtension.kt @@ -0,0 +1,7 @@ +import org.gradle.api.JavaVersion + +open class JavaLibraryExtension { + var mainJavaVersion: JavaVersion = JavaVersion.VERSION_1_8 + var testJavaVersion: JavaVersion = JavaVersion.VERSION_17 + var configureRelease: Boolean = true +} diff --git a/buildSrc/src/main/kotlin/License.kt b/gradle/plugins/common/src/main/kotlin/License.kt similarity index 60% rename from buildSrc/src/main/kotlin/License.kt rename to gradle/plugins/common/src/main/kotlin/License.kt index 4f4e78eaa5b2..9d83baeaf8c1 100644 --- a/buildSrc/src/main/kotlin/License.kt +++ b/gradle/plugins/common/src/main/kotlin/License.kt @@ -1,4 +1,4 @@ -import java.io.File +import org.gradle.api.file.RegularFile import java.net.URI -data class License(val name: String, val url: URI, val headerFile: File) +data class License(val name: String, val url: URI, val headerFile: RegularFile) diff --git a/gradle/plugins/common/src/main/kotlin/ProjectExtensions.kt b/gradle/plugins/common/src/main/kotlin/ProjectExtensions.kt new file mode 100644 index 000000000000..fc8a515977e7 --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/ProjectExtensions.kt @@ -0,0 +1,19 @@ +import org.gradle.api.Project +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('-', '.') + +fun Project.requiredVersionFromLibs(name: String) = + libsVersionCatalog.findVersion(name).get().requiredVersion + +fun Project.dependencyFromLibs(name: String) = + libsVersionCatalog.findLibrary(name).get() + +fun Project.bundleFromLibs(name: String) = + libsVersionCatalog.findBundle(name).get() + +private val Project.libsVersionCatalog: VersionCatalog + get() = the().named("libs") diff --git a/gradle/plugins/common/src/main/kotlin/TaskExtensions.kt b/gradle/plugins/common/src/main/kotlin/TaskExtensions.kt new file mode 100644 index 000000000000..7ad4e7ab46cb --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/TaskExtensions.kt @@ -0,0 +1,5 @@ +import org.gradle.api.Task +import org.gradle.internal.os.OperatingSystem + +fun Task.trackOperationSystemAsInput() = + inputs.property("os", OperatingSystem.current().familyName) diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.base-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.base-conventions.gradle.kts new file mode 100644 index 000000000000..4bd5a4660201 --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.base-conventions.gradle.kts @@ -0,0 +1,6 @@ +plugins { + eclipse + idea + id("junitbuild.java-toolchain-conventions") + id("junitbuild.spotless-conventions") +} diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.build-metadata.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.build-metadata.gradle.kts new file mode 100644 index 000000000000..ad58b4898de5 --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.build-metadata.gradle.kts @@ -0,0 +1,28 @@ +import java.time.Instant +import java.time.OffsetDateTime +import java.time.ZoneOffset +import java.time.format.DateTimeFormatter + +val buildTimeAndDate = + if (System.getenv().containsKey("SOURCE_DATE_EPOCH")) { + + // SOURCE_DATE_EPOCH is a UNIX timestamp for pinning build metadata against + // in order to achieve reproducible builds + // + // More details - https://reproducible-builds.org/docs/source-date-epoch/ + val sourceDateEpoch = System.getenv("SOURCE_DATE_EPOCH").toLong() + + Instant.ofEpochSecond(sourceDateEpoch).atOffset(ZoneOffset.UTC) + + } else { + OffsetDateTime.now() + } + +val buildDate: String by extra { DateTimeFormatter.ISO_LOCAL_DATE.format(buildTimeAndDate) } +val buildTime: String by extra { DateTimeFormatter.ofPattern("HH:mm:ss.SSSZ").format(buildTimeAndDate) } +val buildRevision: String by extra { + providers.exec { + commandLine("git", "rev-parse", "--verify", "HEAD") + }.standardOutput.asText.get() +} +val builtByValue by extra { project.findProperty("builtBy") ?: project.property("defaultBuiltBy") } diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.dependency-update-check.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.dependency-update-check.gradle.kts new file mode 100644 index 000000000000..f7bac1fb8efc --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.dependency-update-check.gradle.kts @@ -0,0 +1,19 @@ +plugins { + id("com.github.ben-manes.versions") +} + +tasks.dependencyUpdates { + checkConstraints = true + resolutionStrategy { + componentSelection { + all { + val rejected = listOf("alpha", "beta", "rc", "cr", "m", "preview", "b", "ea") + .map { qualifier -> Regex("(?i).*[.-]$qualifier[.\\d-+]*") } + .any { it.matches(candidate.version) } + if (rejected) { + reject("Release candidate") + } + } + } + } +} diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.jacoco-aggregation-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.jacoco-aggregation-conventions.gradle.kts new file mode 100644 index 000000000000..91008c81b184 --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.jacoco-aggregation-conventions.gradle.kts @@ -0,0 +1,12 @@ +plugins { + id("junitbuild.jacoco-conventions") + `jacoco-report-aggregation` +} + +reporting { + reports { + create("jacocoRootReport") { + testType = TestSuiteType.UNIT_TEST + } + } +} diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.jacoco-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.jacoco-conventions.gradle.kts new file mode 100644 index 000000000000..ef29df71f8c0 --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.jacoco-conventions.gradle.kts @@ -0,0 +1,12 @@ +plugins { + jacoco + id("junitbuild.build-parameters") +} + +jacoco { + toolVersion = requiredVersionFromLibs("jacoco") +} + +tasks.withType().configureEach { + enabled = buildParameters.testing.enableJaCoCo +} diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.jacoco-java-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.jacoco-java-conventions.gradle.kts new file mode 100644 index 000000000000..804de8388562 --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.jacoco-java-conventions.gradle.kts @@ -0,0 +1,31 @@ +import org.gradle.api.attributes.LibraryElements.CLASSES +import org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE + +plugins { + java + id("junitbuild.build-parameters") + id("junitbuild.jacoco-conventions") +} + +val mavenizedProjects: List by rootProject.extra + +tasks.withType().configureEach { + configure { + isEnabled = buildParameters.testing.enableJaCoCo + } +} + +val codeCoverageClassesJar by tasks.registering(Jar::class) { + from(tasks.jar.map { zipTree(it.archiveFile) }) + archiveClassifier = "jacoco" + enabled = project in mavenizedProjects + duplicatesStrategy = DuplicatesStrategy.INCLUDE +} + +configurations.create("codeCoverageReportClasses") { + isCanBeResolved = false + isCanBeConsumed = true + isTransitive = false + attributes.attribute(LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements::class, CLASSES)) + outgoing.artifact(codeCoverageClassesJar) +} diff --git a/buildSrc/src/main/kotlin/java-library-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts similarity index 57% rename from buildSrc/src/main/kotlin/java-library-conventions.gradle.kts rename to gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts index 6ebed2f22a2e..6eb7eb308ec8 100644 --- a/buildSrc/src/main/kotlin/java-library-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts @@ -1,9 +1,15 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import junitbuild.java.ModuleCompileOptions +import junitbuild.java.ModulePathArgumentProvider +import junitbuild.java.PatchModuleArgumentProvider + plugins { `java-library` eclipse idea checkstyle - id("custom-java-home") + id("junitbuild.base-conventions") + id("junitbuild.jacoco-java-conventions") } val mavenizedProjects: List by rootProject.extra @@ -13,25 +19,13 @@ val buildTime: String by rootProject.extra val buildRevision: Any by rootProject.extra val builtByValue: String by rootProject.extra -val internal by configurations.creating { - isVisible = false - isCanBeConsumed = false - isCanBeResolved = false -} - val extension = extensions.create("javaLibrary") -val moduleSourceDir = file("src/module/$javaModuleName") -val moduleOutputDir = file("$buildDir/classes/java/module") +val moduleSourceDir = layout.projectDirectory.dir("src/module/$javaModuleName") +val combinedModuleSourceDir = layout.buildDirectory.dir("module") +val moduleOutputDir = layout.buildDirectory.dir("classes/java/module") val javaVersion = JavaVersion.current() -configurations { - compileClasspath.get().extendsFrom(internal) - runtimeClasspath.get().extendsFrom(internal) - testCompileClasspath.get().extendsFrom(internal) - testRuntimeClasspath.get().extendsFrom(internal) -} - eclipse { jdt { file { @@ -44,10 +38,14 @@ eclipse { } } +java { + modularity.inferModulePath = false +} + if (project in mavenizedProjects) { - apply(plugin = "publishing-conventions") - apply(plugin = "osgi-conventions") + apply(plugin = "junitbuild.publishing-conventions") + apply(plugin = "junitbuild.osgi-conventions") java { withJavadocJar() @@ -61,13 +59,9 @@ if (project in mavenizedProjects) { encoding = "UTF-8" locale = "en" (this as StandardJavadocDocletOptions).apply { - addBooleanOption("Xdoclint:html,syntax", true) + addBooleanOption("Xdoclint:all,-missing,-reference", true) + addBooleanOption("XD-Xlint:none", true) addBooleanOption("html5", true) - // Javadoc 13 removed support for `--no-module-directories` - // https://bugs.openjdk.java.net/browse/JDK-8215580 - if (javaVersion.isJava12 && executable == null) { - addBooleanOption("-no-module-directories", true) - } addMultilineStringsOption("tag").value = listOf( "apiNote:a:API Note:", "implNote:a:Implementation Note:" @@ -96,8 +90,6 @@ if (project in mavenizedProjects) { val javaComponent = components["java"] as AdhocComponentWithVariants javaComponent.withVariantsFromConfiguration(configurations["testFixturesApiElements"]) { skip() } javaComponent.withVariantsFromConfiguration(configurations["testFixturesRuntimeElements"]) { skip() } - configurations["testFixturesCompileClasspath"].extendsFrom(internal) - configurations["testFixturesRuntimeClasspath"].extendsFrom(internal) } configure { @@ -110,7 +102,7 @@ if (project in mavenizedProjects) { } } pom { - description.set(provider { "Module \"${project.name}\" of JUnit 5." }) + description = provider { "Module \"${project.name}\" of JUnit 5." } } } } @@ -127,12 +119,25 @@ if (project in mavenizedProjects) { } } +tasks.withType().configureEach { + isPreserveFileTimestamps = false + isReproducibleFileOrder = true + dirMode = Integer.parseInt("0755", 8) + fileMode = Integer.parseInt("0644", 8) +} + normalization { runtimeClasspath { - // Ignore the JAR manifest when checking whether runtime classpath have changed - // because it contains timestamps and the commit checksum. This is used when - // checking whether a test task is up-to-date or can be loaded from the build cache. - ignore("/META-INF/MANIFEST.MF") + metaInf { + // Ignore inconsequential JAR manifest attributes such as timestamps and the commit checksum. + // This is used when checking whether runtime classpaths, e.g. of test tasks, have changed and + // improves cacheability of such tasks. + ignoreAttribute("Built-By") + ignoreAttribute("Build-Date") + ignoreAttribute("Build-Time") + ignoreAttribute("Build-Revision") + ignoreAttribute("Created-By") + } } } @@ -140,23 +145,45 @@ val allMainClasses by tasks.registering { dependsOn(tasks.classes) } +val prepareModuleSourceDir by tasks.registering(Sync::class) { + from(moduleSourceDir) + from(sourceSets.matching { it.name.startsWith("main") }.map { it.allJava }) + into(combinedModuleSourceDir.map { it.dir(javaModuleName) }) + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} + val compileModule by tasks.registering(JavaCompile::class) { dependsOn(allMainClasses) - source = fileTree(moduleSourceDir) - destinationDir = moduleOutputDir + enabled = project in modularProjects + source = fileTree(combinedModuleSourceDir).builtBy(prepareModuleSourceDir) + destinationDirectory = moduleOutputDir sourceCompatibility = "9" targetCompatibility = "9" classpath = files() - options.release.set(9) + options.release = 9 options.compilerArgs.addAll(listOf( - // "-verbose", // Suppress warnings for automatic modules: org.apiguardian.api, org.opentest4j "-Xlint:all,-requires-automatic,-requires-transitive-automatic", + "-Werror", // Terminates compilation when warnings occur. "--module-version", "${project.version}", - "--module-source-path", files(modularProjects.map { "${it.projectDir}/src/module" }).asPath )) - options.compilerArgumentProviders.add(ModulePathArgumentProvider()) - options.compilerArgumentProviders.addAll(modularProjects.map { PatchModuleArgumentProvider(it) }) + + 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) + }) + options.compilerArgumentProviders.addAll(modularProjects.map { objects.newInstance(PatchModuleArgumentProvider::class, project, it) }) + + modularity.inferModulePath = false + + doFirst { + options.allCompilerArgs.forEach { + logger.info(it) + } + } } tasks.withType().configureEach { @@ -165,9 +192,9 @@ tasks.withType().configureEach { into("META-INF") } val suffix = archiveClassifier.getOrElse("") - if (suffix.isBlank() || suffix == "all") { // "all" is used by shadow plugin + if (suffix.isBlank() || this is ShadowJar) { dependsOn(allMainClasses, compileModule) - from("$moduleOutputDir/$javaModuleName") { + from(moduleOutputDir.map { it.dir(javaModuleName) }) { include("module-info.class") } } @@ -191,6 +218,10 @@ tasks.jar { } } +tasks.withType().configureEach { + outputs.doNotCacheIf("Shadow jar contains a Manifest with Build-Time") { true } +} + tasks.withType().configureEach { options.encoding = "UTF-8" } @@ -213,31 +244,6 @@ tasks.compileTestJava { )) } -inner class ModulePathArgumentProvider : CommandLineArgumentProvider { - @get:Input val modulePath: Provider = configurations.compileClasspath - override fun asArguments(): List = listOf("--module-path", modulePath.get().asPath) -} - -inner class PatchModuleArgumentProvider(it: Project) : CommandLineArgumentProvider { - - @get:Input val module: String = it.javaModuleName - - @get:Input val patch: Provider = provider { - if (it == project) - files(sourceSets.matching { it.name.startsWith("main") }.map { it.output }) + configurations.compileClasspath.get() - else - files(it.sourceSets["main"].java.srcDirs) - } - - override fun asArguments(): List { - val path = patch.get().filter { it.exists() }.asPath - if (path.isEmpty()) { - return emptyList() - } - return listOf("--patch-module", "$module=$path") - } -} - afterEvaluate { configurations { apiElements { @@ -253,18 +259,30 @@ afterEvaluate { } tasks { compileJava { - options.release.set(extension.mainJavaVersion.majorVersion.toInt()) + if (extension.configureRelease) { + options.release = extension.mainJavaVersion.majorVersion.toInt() + } else { + sourceCompatibility = extension.mainJavaVersion.majorVersion + targetCompatibility = extension.mainJavaVersion.majorVersion + } } compileTestJava { - options.release.set(extension.testJavaVersion.majorVersion.toInt()) + if (extension.configureRelease) { + options.release = extension.testJavaVersion.majorVersion.toInt() + } else { + sourceCompatibility = extension.testJavaVersion.majorVersion + targetCompatibility = extension.testJavaVersion.majorVersion + } } } pluginManager.withPlugin("groovy") { tasks.named("compileGroovy").configure { + // Groovy compiler does not support the --release flag. sourceCompatibility = extension.mainJavaVersion.majorVersion targetCompatibility = extension.mainJavaVersion.majorVersion } tasks.named("compileTestGroovy").configure { + // Groovy compiler does not support the --release flag. sourceCompatibility = extension.testJavaVersion.majorVersion targetCompatibility = extension.testJavaVersion.majorVersion } @@ -272,21 +290,24 @@ afterEvaluate { } checkstyle { - toolVersion = versions["checkstyle"] - configDirectory.set(rootProject.file("src/checkstyle")) + toolVersion = requiredVersionFromLibs("checkstyle") + configDirectory = rootProject.layout.projectDirectory.dir("gradle/config/checkstyle") } tasks { checkstyleMain { - configFile = rootProject.file("src/checkstyle/checkstyleMain.xml") + config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleMain.xml")) } checkstyleTest { - configFile = rootProject.file("src/checkstyle/checkstyleTest.xml") + config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleTest.xml")) } } pluginManager.withPlugin("java-test-fixtures") { - tasks.named("checkstyleTestFixtures").configure { - configFile = rootProject.file("src/checkstyle/checkstyleTest.xml") + tasks.named("checkstyleTestFixtures") { + 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 new file mode 100644 index 000000000000..d1b05c6e00ce --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.java-multi-release-sources.gradle.kts @@ -0,0 +1,45 @@ +import org.gradle.configurationcache.extensions.capitalized + +plugins { + id("junitbuild.java-library-conventions") +} + +val mavenizedProjects: List by rootProject.extra + +listOf(9, 17).forEach { javaVersion -> + val sourceSet = sourceSets.register("mainRelease${javaVersion}") { + compileClasspath += sourceSets.main.get().output + runtimeClasspath += sourceSets.main.get().output + java { + setSrcDirs(setOf("src/main/java${javaVersion}")) + } + } + + configurations.named(sourceSet.get().compileClasspathConfigurationName).configure { + extendsFrom(configurations.compileClasspath.get()) + } + + tasks { + + named("allMainClasses").configure { + dependsOn(sourceSet.get().classesTaskName) + } + + named(sourceSet.get().compileJavaTaskName).configure { + options.release = javaVersion + } + + named("checkstyle${sourceSet.name.capitalized()}").configure { + config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleMain.xml")) + } + + if (project in mavenizedProjects) { + javadoc { + source(sourceSet.get().allJava) + } + named("sourcesJar").configure { + from(sourceSet.get().allSource) + } + } + } +} diff --git a/buildSrc/src/main/kotlin/java-repackage-jars.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.java-repackage-jars.gradle.kts similarity index 66% rename from buildSrc/src/main/kotlin/java-repackage-jars.gradle.kts rename to gradle/plugins/common/src/main/kotlin/junitbuild.java-repackage-jars.gradle.kts index e36bea40b0fa..a000989b0327 100644 --- a/buildSrc/src/main/kotlin/java-repackage-jars.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.java-repackage-jars.gradle.kts @@ -1,9 +1,8 @@ -import java.util.Calendar -import java.util.GregorianCalendar import java.util.jar.JarEntry import java.util.jar.JarFile import java.util.jar.JarOutputStream import org.gradle.api.internal.file.archive.ZipCopyAction +import java.nio.file.Files // This registers a `doLast` action to rewrite the timestamps of the project's output JAR afterEvaluate { @@ -11,18 +10,29 @@ afterEvaluate { jarTask.doLast { - val newFile = createTempFile("rewrite-timestamp") - val originalOutput = jarTask.archiveFile.get().getAsFile() + val newFile = Files.createTempFile("rewrite-timestamp", null).toFile() + val originalOutput = jarTask.archiveFile.get().asFile newFile.outputStream().use { os -> val newJarStream = JarOutputStream(os) val oldJar = JarFile(originalOutput) + fun sortAlwaysFirst(name: String): Comparator = + Comparator { a, b -> + when { + a.name == name -> -1 + b.name == name -> 1 + else -> 0 + } + } + oldJar.entries() .toList() .distinctBy { it.name } - .sortedBy { it.name } + .sortedWith(sortAlwaysFirst("META-INF/") + .then(sortAlwaysFirst("META-INF/MANIFEST.MF")) + .thenBy { it.name }) .forEach { entry -> val jarEntry = JarEntry(entry.name) 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 new file mode 100644 index 000000000000..68735c0c9168 --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.java-toolchain-conventions.gradle.kts @@ -0,0 +1,45 @@ +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension + +plugins { + id("junitbuild.build-parameters") +} + +project.pluginManager.withPlugin("java") { + val defaultLanguageVersion = JavaLanguageVersion.of(17) + val javaLanguageVersion = buildParameters.javaToolchainVersion.map { JavaLanguageVersion.of(it) }.getOrElse(defaultLanguageVersion) + + val extension = the() + val javaToolchainService = the() + + extension.toolchain.languageVersion = javaLanguageVersion + + pluginManager.withPlugin("org.jetbrains.kotlin.jvm") { + configure { + jvmToolchain { + languageVersion = javaLanguageVersion + } + } + } + + tasks.withType().configureEach { + javaLauncher = javaToolchainService.launcherFor(extension.toolchain) + } + + tasks.withType().configureEach { + outputs.cacheIf { javaLanguageVersion == defaultLanguageVersion } + doFirst { + if (options.release.orNull == 8 && javaLanguageVersion.asInt() >= 20) { + options.compilerArgs.add( + "-Xlint:-options" // see https://github.com/junit-team/junit5/issues/3029 + ) + } + } + } + + tasks.withType().configureEach { + javaLauncher.set(javaToolchainService.launcherFor { + // Groovy does not yet support JDK 19, see https://issues.apache.org/jira/browse/GROOVY-10569 + languageVersion = defaultLanguageVersion + }) + } +} diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.junit4-compatibility.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.junit4-compatibility.gradle.kts new file mode 100644 index 000000000000..d0d2a89ad577 --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.junit4-compatibility.gradle.kts @@ -0,0 +1,30 @@ +plugins { + `java-library` +} + +val junit_4_12 by configurations.creatingResolvable { + extendsFrom(configurations.testRuntimeClasspath.get()) +} + +dependencies { + junit_4_12("junit:junit") { + version { + strictly("4.12") + } + } + pluginManager.withPlugin("junitbuild.osgi-conventions") { + val junit4Osgi = requiredVersionFromLibs("junit4Osgi") + "osgiVerification"("org.apache.servicemix.bundles:org.apache.servicemix.bundles.junit:${junit4Osgi}") + } +} + +tasks { + val test_4_12 by registering(Test::class) { + val test by testing.suites.existing(JvmTestSuite::class) + testClassesDirs = files(test.map { it.sources.output.classesDirs }) + classpath = files(test.map { it.sources.runtimeClasspath }) + junit_4_12 + } + check { + dependsOn(test_4_12) + } +} 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 new file mode 100644 index 000000000000..bfb33224a709 --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.kotlin-library-conventions.gradle.kts @@ -0,0 +1,26 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + id("junitbuild.java-library-conventions") + kotlin("jvm") +} + +tasks.withType().configureEach { + kotlinOptions { + apiVersion = "1.3" + languageVersion = "1.3" + allWarningsAsErrors = false + } +} + +afterEvaluate { + val extension = project.the() + tasks { + withType().configureEach { + kotlinOptions.jvmTarget = extension.mainJavaVersion.toString() + } + named("compileTestKotlin") { + kotlinOptions.jvmTarget = extension.testJavaVersion.toString() + } + } +} 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 new file mode 100644 index 000000000000..9cd51cd4b56f --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts @@ -0,0 +1,117 @@ +import aQute.bnd.gradle.BundleTaskExtension +import aQute.bnd.gradle.Resolve + +plugins { + `java-library` +} + +val importAPIGuardian = "org.apiguardian.*;resolution:=\"optional\"" + +val projectDescription = objects.property().convention(provider { project.description }) + +// This task enhances `jar` and `shadowJar` tasks with the bnd +// `BundleTaskExtension` extension which allows for generating OSGi +// metadata into the jar +tasks.withType().matching { task: Jar -> + task.name == "jar" || task.name == "shadowJar" +}.all { // configure tasks eagerly as workaround for https://github.com/bndtools/bnd/issues/5695 + extra["importAPIGuardian"] = importAPIGuardian + + extensions.create(BundleTaskExtension.NAME, this).apply { + properties.set(projectDescription.map { + mapOf("project.description" to it) + }) + // These are bnd instructions necessary for generating OSGi metadata. + // We've generalized these so that they are widely applicable limiting + // module configurations to special cases. + setBnd( + """ + # Set the Bundle-SymbolicName to the archiveBaseName. + # We don't use the archiveClassifier which Bnd will use + # in the default Bundle-SymbolicName value. + Bundle-SymbolicName: ${'$'}{task.archiveBaseName} + + # Set the Bundle-Name from the project description + Bundle-Name: ${'$'}{project.description} + + # These are the general rules for package imports. + Import-Package: \ + ${importAPIGuardian},\ + org.junit.platform.commons.logging;status=INTERNAL,\ + kotlin.*;resolution:="optional",\ + * + + # This tells bnd not to complain if a module doesn't actually import + # the kotlin and apiguardian packages, but enough modules do to make it a default. + -fixupmessages.kotlin.import: "Unused Import-Package instructions: \\[kotlin.*\\]";is:=ignore + -fixupmessages.apiguardian.import: "Unused Import-Package instructions: \\[org.apiguardian.*\\]";is:=ignore + + # This tells bnd to ignore classes it finds in `META-INF/versions/` + # because bnd doesn't yet support multi-release jars. + -fixupmessages.wrong.dir: "Classes found in the wrong directory: \\{META-INF/versions/...";is:=ignore + + # Don't scan for Class.forName package imports. + # See https://bnd.bndtools.org/instructions/noclassforname.html + -noclassforname: true + + # Don't add all the extra headers bnd normally adds. + # See https://bnd.bndtools.org/instructions/noextraheaders.html + -noextraheaders: true + + # Don't add the Private-Package header. + # See https://bnd.bndtools.org/instructions/removeheaders.html + -removeheaders: Private-Package + + # Instruct the APIGuardianAnnotations how to operate. + # See https://bnd.bndtools.org/instructions/export-apiguardian.html + -export-apiguardian: *;version=${'$'}{versionmask;===;${'$'}{version_cleanup;${'$'}{task.archiveVersion}}} + """ + ) + + // Do the actual work putting OSGi stuff in the jar. + doLast(buildAction()) + } +} + +// Bnd's Resolve task uses a properties file for its configuration. This +// task writes out the properties necessary for it to verify the OSGi +// metadata. +val osgiProperties by tasks.registering(WriteProperties::class) { + destinationFile = layout.buildDirectory.file("verifyOSGiProperties.bndrun") + property("-standalone", true) + project.extensions.getByType(JavaLibraryExtension::class.java).let { javaLibrary -> + property("-runee", "JavaSE-${javaLibrary.mainJavaVersion}") + } + property("-runrequires", "osgi.identity;filter:='(osgi.identity=${project.name})'") + property("-runsystempackages", "jdk.internal.misc,jdk.jfr,sun.misc") + // API Guardian should be optional -> instruct resolver to ignore it + // during resolution. Resolve should still pass. + property("-runblacklist", "org.apiguardian.api") +} + +val osgiVerification by configurations.creatingResolvable { + extendsFrom(configurations.runtimeClasspath.get()) +} + +// Bnd's Resolve task is what verifies that a jar can be used in OSGi and +// that its metadata is valid. If the metadata is invalid this task will +// fail. +val verifyOSGi by tasks.registering(Resolve::class) { + bndrun = osgiProperties.flatMap { it.destinationFile } + outputBndrun = layout.buildDirectory.file("resolvedOSGiProperties.bndrun") + isReportOptional = false + // By default bnd will use jars found in: + // 1. project.sourceSets.main.runtimeClasspath + // 2. project.configurations.archives.artifacts.files + // to validate the metadata. + // This adds jars defined in `osgiVerification` also so that bnd + // can use them to validate the metadata without causing those to + // end up in the dependencies of those projects. + bundles(osgiVerification) + properties.empty() + outputs.doNotCacheIf("https://github.com/bndtools/bnd/issues/5666") { true } +} + +tasks.check { + dependsOn(verifyOSGi) +} 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 new file mode 100644 index 000000000000..bab950933c2a --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.publishing-conventions.gradle.kts @@ -0,0 +1,116 @@ +plugins { + `maven-publish` + signing + id("junitbuild.base-conventions") + 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 + +when (project) { + in jupiterProjects -> { + group = property("jupiterGroup")!! + } + in platformProjects -> { + group = property("platformGroup")!! + version = property("platformVersion")!! + } + in vintageProjects -> { + group = property("vintageGroup")!! + version = property("vintageVersion")!! + } +} + +// ensure project is built successfully before publishing it +tasks.withType().configureEach { + dependsOn(provider { + val tempRepoName: String by rootProject + if (repository.name != tempRepoName) { + listOf(tasks.build) + } else { + emptyList() + } + }) +} +tasks.withType().configureEach { + dependsOn(tasks.build) +} + +signing { + useGpgCmd() + sign(publishing.publications) + isRequired = !(isSnapshot || buildParameters.ci) +} + +tasks.withType().configureEach { + val isSnapshot = project.version.toString().contains("SNAPSHOT") + onlyIf { + !isSnapshot // Gradle Module Metadata currently does not support signing snapshots + } +} + +publishing { + publications { + create("maven") { + pom { + name.set(provider { + project.description ?: "${project.group}:${project.name}" + }) + url = "https://junit.org/junit5/" + scm { + connection = "scm:git:git://github.com/junit-team/junit5.git" + developerConnection = "scm:git:git://github.com/junit-team/junit5.git" + url = "https://github.com/junit-team/junit5" + } + licenses { + license { + val license: License by rootProject.extra + name = license.name + url = license.url.toString() + } + } + developers { + developer { + id = "bechte" + name = "Stefan Bechtold" + email = "stefan.bechtold@me.com" + } + developer { + id = "jlink" + name = "Johannes Link" + email = "business@johanneslink.net" + } + developer { + id = "marcphilipp" + name = "Marc Philipp" + email = "mail@marcphilipp.de" + } + developer { + id = "mmerdes" + name = "Matthias Merdes" + email = "matthias.merdes@heidelpay.com" + } + developer { + id = "sbrannen" + name = "Sam Brannen" + email = "sam@sambrannen.com" + } + developer { + id = "sormuras" + name = "Christian Stein" + email = "sormuras@gmail.com" + } + developer { + id = "juliette-derancourt" + name = "Juliette de Rancourt" + email = "derancourt.juliette@gmail.com" + } + } + } + } + } +} diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.settings-conventions.settings.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.settings-conventions.settings.gradle.kts new file mode 100644 index 000000000000..654e5b73502a --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.settings-conventions.settings.gradle.kts @@ -0,0 +1,5 @@ +plugins { + id("com.gradle.enterprise") + id("com.gradle.common-custom-user-data-gradle-plugin") + id("org.gradle.toolchains.foojay-resolver-convention") +} diff --git a/buildSrc/src/main/kotlin/shadow-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.shadow-conventions.gradle.kts similarity index 54% rename from buildSrc/src/main/kotlin/shadow-conventions.gradle.kts rename to gradle/plugins/common/src/main/kotlin/junitbuild.shadow-conventions.gradle.kts index 0a5d8db24510..341eb6f826a3 100644 --- a/buildSrc/src/main/kotlin/shadow-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.shadow-conventions.gradle.kts @@ -1,18 +1,20 @@ +import junitbuild.java.ModuleCompileOptions + plugins { - id("java-library-conventions") + id("junitbuild.java-library-conventions") id("com.github.johnrengelman.shadow") } -val shadowed by configurations.creating { - extendsFrom(configurations["internal"]) -} +val shadowed by configurations.creatingResolvable -configurations.forEach { configuration -> - configuration.outgoing.apply { - val removed = artifacts.removeIf { it.classifier.isNullOrEmpty() } - if (removed) { - artifact(tasks.shadowJar) { - classifier = "" +configurations { + listOf(apiElements, runtimeElements).forEach { + it.configure { + outgoing { + artifacts.clear() + artifact(tasks.shadowJar) { + classifier = "" + } } } } @@ -49,15 +51,24 @@ tasks { shadowJar { configurations = listOf(shadowed) exclude("META-INF/maven/**") + excludes.remove("module-info.class") + archiveClassifier = "" } jar { dependsOn(shadowJar) enabled = false } + named("codeCoverageClassesJar") { + from(shadowJar.map { zipTree(it.archiveFile) }) + exclude("**/shadow/**") + } test { dependsOn(shadowJar) // in order to run the test against the shadowJar classpath -= sourceSets.main.get().output classpath += files(shadowJar.map { it.archiveFile }) } + named("compileModule") { + the().modulePath.from(shadowed) + } } 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 new file mode 100644 index 000000000000..e078a3a50d1c --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.spotless-conventions.gradle.kts @@ -0,0 +1,64 @@ +import com.diffplug.gradle.spotless.SpotlessApply +import com.diffplug.gradle.spotless.SpotlessCheck +import com.diffplug.spotless.LineEnding + +plugins { + id("com.diffplug.spotless") +} + +val license: License by rootProject.extra + +spotless { + + format("misc") { + target("*.gradle.kts", "buildSrc/**/*.gradle.kts", "*.gitignore") + targetExclude("buildSrc/build/**") + indentWithTabs() + trimTrailingWhitespace() + endWithNewline() + } + + 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 javaFormatterConfigFile = configDir.file("junit-eclipse-formatter-settings.xml") + + java { + licenseHeaderFile(license.headerFile, "(package|import|open|module) ") + importOrderFile(importOrderConfigFile) + eclipse().configFile(javaFormatterConfigFile) + trimTrailingWhitespace() + endWithNewline() + } + } + + pluginManager.withPlugin("org.jetbrains.kotlin.jvm") { + kotlin { + targetExclude("**/src/test/resources/**") + ktlint(requiredVersionFromLibs("ktlint")) + licenseHeaderFile(license.headerFile) + trimTrailingWhitespace() + endWithNewline() + } + } + + // https://github.com/diffplug/spotless/issues/1644 + lineEndings = LineEnding.UNIX // or any other except GIT_ATTRIBUTES +} + +tasks { + withType().configureEach { + notCompatibleWithConfigurationCache("https://github.com/diffplug/spotless/issues/644") + } + withType().configureEach { + notCompatibleWithConfigurationCache("https://github.com/diffplug/spotless/issues/644") + } +} 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 new file mode 100644 index 000000000000..fb06e7ec012c --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.temp-maven-repo.gradle.kts @@ -0,0 +1,31 @@ +import org.gradle.configurationcache.extensions.capitalized + +val tempRepoName by extra("temp") +val tempRepoDir by extra { + layout.buildDirectory.dir("repo").get().asFile +} + +val clearTempRepoDir by tasks.registering { + val dir = tempRepoDir + doFirst { + dir.deleteRecursively() + } +} + +subprojects { + pluginManager.withPlugin("maven-publish") { + configure { + repositories { + maven { + name = tempRepoName + url = uri(tempRepoDir) + } + } + } + tasks.withType().configureEach { + if (name.endsWith("To${tempRepoName.capitalized()}Repository")) { + dependsOn(clearTempRepoDir) + } + } + } +} 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 new file mode 100644 index 000000000000..086e1e6135af --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.testing-conventions.gradle.kts @@ -0,0 +1,89 @@ +import com.gradle.enterprise.gradleplugin.testretry.retry +import com.gradle.enterprise.gradleplugin.testselection.internal.PredictiveTestSelectionExtensionInternal +import org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL +import org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED +import org.gradle.internal.os.OperatingSystem + +plugins { + `java-library` + id("junitbuild.build-parameters") +} + +tasks.withType().configureEach { + useJUnitPlatform { + includeEngines("junit-jupiter") + } + include("**/*Test.class", "**/*Tests.class") + testLogging { + events = setOf(FAILED) + exceptionFormat = FULL + } + retry { + maxRetries = buildParameters.testing.retries.orElse(if (buildParameters.ci) 2 else 0) + } + distribution { + enabled.convention(buildParameters.enterprise.testDistribution.enabled && (!buildParameters.ci || System.getenv("GRADLE_ENTERPRISE_ACCESS_KEY").isNotBlank())) + maxLocalExecutors = buildParameters.enterprise.testDistribution.maxLocalExecutors + maxRemoteExecutors = buildParameters.enterprise.testDistribution.maxRemoteExecutors + if (buildParameters.ci) { + when { + OperatingSystem.current().isLinux -> requirements.add("os=linux") + OperatingSystem.current().isWindows -> requirements.add("os=windows") + OperatingSystem.current().isMacOsX -> requirements.add("os=macos") + } + } + } + predictiveSelection { + enabled = buildParameters.enterprise.predictiveTestSelection.enabled + + // Ensure PTS works when publishing Build Scans to scans.gradle.com + this as PredictiveTestSelectionExtensionInternal + server = uri("https://ge.junit.org") + } + systemProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager") + // Required until ASM officially supports the JDK 14 + systemProperty("net.bytebuddy.experimental", true) + if (buildParameters.testing.enableJFR) { + jvmArgs( + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+DebugNonSafepoints", + "-XX:StartFlightRecording=filename=${reports.junitXml.outputLocation.get()},dumponexit=true,settings=profile.jfc", + "-XX:FlightRecorderOptions=stackdepth=1024" + ) + } + + // Track OS as input so that tests are executed on all configured operating systems on CI + trackOperationSystemAsInput() + + // Avoid passing unnecessary environment variables to the JVM (from GitHub Actions) + if (buildParameters.ci) { + environment.remove("RUNNER_TEMP") + environment.remove("GITHUB_ACTION") + } + + jvmArgumentProviders += CommandLineArgumentProvider { + listOf( + "-Djunit.platform.reporting.open.xml.enabled=true", + "-Djunit.platform.reporting.output.dir=${reports.junitXml.outputLocation.get().asFile.absolutePath}" + ) + } +} + +dependencies { + testImplementation(dependencyFromLibs("assertj")) + testImplementation(dependencyFromLibs("mockito")) + testImplementation(dependencyFromLibs("testingAnnotations")) + + if (!project.name.startsWith("junit-jupiter")) { + testImplementation(project(":junit-jupiter")) + } + + testRuntimeOnly(project(":junit-platform-engine")) + testRuntimeOnly(project(":junit-platform-jfr")) + testRuntimeOnly(project(":junit-platform-reporting")) + + testRuntimeOnly(bundleFromLibs("log4j")) + testRuntimeOnly(dependencyFromLibs("openTestReporting-events")) { + because("it's required to run tests via IntelliJ which does not consumed the shadowed jar of junit-platform-reporting") + } +} diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/exec/CaptureJavaExecOutput.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/exec/CaptureJavaExecOutput.kt new file mode 100644 index 000000000000..aa1f4c391168 --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild/exec/CaptureJavaExecOutput.kt @@ -0,0 +1,50 @@ +package junitbuild.exec + +import org.gradle.api.DefaultTask +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Nested +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction +import org.gradle.process.CommandLineArgumentProvider +import org.gradle.process.ExecOperations +import java.nio.file.Files +import javax.inject.Inject + +@CacheableTask +abstract class CaptureJavaExecOutput @Inject constructor(private val execOperations: ExecOperations) : DefaultTask() { + + @get:Classpath + abstract val classpath: ConfigurableFileCollection + + @get:Input + abstract val mainClass: Property + + @get:Input + abstract val args: ListProperty + + @get:Nested + val jvmArgumentProviders = mutableListOf() + + @get:OutputFile + abstract val outputFile: RegularFileProperty + + @TaskAction + fun execute() { + val outputFile = outputFile.get().asFile.toPath() + Files.newOutputStream(outputFile).use { out -> + execOperations.javaexec { + classpath = this@CaptureJavaExecOutput.classpath + mainClass.set(this@CaptureJavaExecOutput.mainClass) + args = this@CaptureJavaExecOutput.args.get() + jvmArgumentProviders.addAll(this@CaptureJavaExecOutput.jvmArgumentProviders) + standardOutput = out + } + } + } +} diff --git a/buildSrc/src/main/kotlin/org/junit/gradle/exec/ClasspathSystemPropertyProvider.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/exec/ClasspathSystemPropertyProvider.kt similarity index 92% rename from buildSrc/src/main/kotlin/org/junit/gradle/exec/ClasspathSystemPropertyProvider.kt rename to gradle/plugins/common/src/main/kotlin/junitbuild/exec/ClasspathSystemPropertyProvider.kt index 9c6423dca171..2faeb2acaeef 100644 --- a/buildSrc/src/main/kotlin/org/junit/gradle/exec/ClasspathSystemPropertyProvider.kt +++ b/gradle/plugins/common/src/main/kotlin/junitbuild/exec/ClasspathSystemPropertyProvider.kt @@ -1,4 +1,4 @@ -package org.junit.gradle.exec +package junitbuild.exec import org.gradle.api.file.FileCollection import org.gradle.api.tasks.Classpath diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/exec/GenerateStandaloneConsoleLauncherShadowedArtifactsFile.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/exec/GenerateStandaloneConsoleLauncherShadowedArtifactsFile.kt new file mode 100644 index 000000000000..042a55e8517c --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild/exec/GenerateStandaloneConsoleLauncherShadowedArtifactsFile.kt @@ -0,0 +1,40 @@ +package junitbuild.exec + +import org.gradle.api.DefaultTask +import org.gradle.api.file.ArchiveOperations +import org.gradle.api.file.FileSystemOperations +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.file.RelativePath +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction +import javax.inject.Inject + +@CacheableTask +abstract class GenerateStandaloneConsoleLauncherShadowedArtifactsFile @Inject constructor( + private val fileSystem: FileSystemOperations, + private val archives: ArchiveOperations +) : DefaultTask() { + + @get:Classpath + abstract val inputJar: RegularFileProperty + + @get:OutputFile + abstract val outputFile: RegularFileProperty + + @TaskAction + fun execute() { + fileSystem.copy { + from(archives.zipTree(inputJar)) { + include("META-INF/shadowed-artifacts") + includeEmptyDirs = false + eachFile { + relativePath = RelativePath(true, outputFile.get().asFile.name) + } + filter { line -> "- `${line}`" } + } + into(outputFile.get().asFile.parentFile) + } + } +} diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/exec/RunConsoleLauncher.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/exec/RunConsoleLauncher.kt new file mode 100644 index 000000000000..a92f31195ad2 --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild/exec/RunConsoleLauncher.kt @@ -0,0 +1,100 @@ +package junitbuild.exec + +import org.apache.tools.ant.types.Commandline +import org.gradle.api.DefaultTask +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.options.Option +import org.gradle.jvm.toolchain.JavaLauncher +import org.gradle.jvm.toolchain.JavaToolchainService +import org.gradle.kotlin.dsl.get +import org.gradle.kotlin.dsl.the +import org.gradle.process.CommandLineArgumentProvider +import org.gradle.process.ExecOperations +import trackOperationSystemAsInput +import java.io.ByteArrayOutputStream +import java.util.* +import javax.inject.Inject + +@CacheableTask +abstract class RunConsoleLauncher @Inject constructor(private val execOperations: ExecOperations) : DefaultTask() { + + @get:Classpath + abstract val runtimeClasspath: ConfigurableFileCollection + + @get:Input + abstract val args: ListProperty + + @get:Nested + abstract val argumentProviders: ListProperty + + @get:Input + abstract val commandLineArgs: ListProperty + + @get:Nested + abstract val javaLauncher: Property + + @get:Internal + abstract val debugging: Property + + @get:Internal + abstract val hideOutput: Property + + init { + runtimeClasspath.from(project.the()["test"].runtimeClasspath) + javaLauncher.set(project.the().launcherFor(project.the().toolchain)) + + debugging.convention(false) + commandLineArgs.convention(emptyList()) + outputs.cacheIf { !debugging.get() } + outputs.upToDateWhen { !debugging.get() } + + hideOutput.convention(debugging.map { !it }) + + trackOperationSystemAsInput() + } + + @TaskAction + fun execute() { + val output = ByteArrayOutputStream() + val result = execOperations.javaexec { + executable = javaLauncher.get().executablePath.asFile.absolutePath + classpath = runtimeClasspath + mainClass.set("org.junit.platform.console.ConsoleLauncher") + args(this@RunConsoleLauncher.args.get()) + args(this@RunConsoleLauncher.commandLineArgs.get()) + argumentProviders.addAll(this@RunConsoleLauncher.argumentProviders.get()) + systemProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager") + debug = debugging.get() + if (hideOutput.get()) { + standardOutput = output + errorOutput = output + } + isIgnoreExitValue = true + } + if (result.exitValue != 0 && hideOutput.get()) { + System.out.write(output.toByteArray()) + System.out.flush() + } + result.rethrowFailure().assertNormalExitValue() + } + + @Suppress("unused") + @Option(option = "args", description = "Additional command line arguments for the console launcher") + fun setCliArgs(args: String) { + commandLineArgs.set(Commandline.translateCommandline(args).toList()) + } + + @Suppress("unused") + @Option( + option = "debug-jvm", + description = "Enable debugging. The process is started suspended and listening on port 5005." + ) + fun setDebug(enabled: Boolean) { + debugging.set(enabled) + } + +} diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/java/ExecJarAction.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/java/ExecJarAction.kt new file mode 100644 index 000000000000..8a3cf49ad70e --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild/java/ExecJarAction.kt @@ -0,0 +1,24 @@ +package junitbuild.java + +import org.gradle.api.Action +import org.gradle.api.Task +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.jvm.toolchain.JavaLauncher +import org.gradle.process.ExecOperations +import javax.inject.Inject + +abstract class ExecJarAction @Inject constructor(private val operations: ExecOperations): Action { + + abstract val javaLauncher: Property + + abstract val args: ListProperty + + override fun execute(t: Task) { + operations.exec { + executable = javaLauncher.get() + .metadata.installationPath.file("bin/jar").asFile.absolutePath + args = this@ExecJarAction.args.get() + } + } +} diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/java/ModuleCompileOptions.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/java/ModuleCompileOptions.kt new file mode 100644 index 000000000000..13c6fde1c09d --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild/java/ModuleCompileOptions.kt @@ -0,0 +1,7 @@ +package junitbuild.java + +import org.gradle.api.file.ConfigurableFileCollection + +abstract class ModuleCompileOptions { + abstract val modulePath: ConfigurableFileCollection +} diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/java/ModulePathArgumentProvider.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/java/ModulePathArgumentProvider.kt new file mode 100644 index 000000000000..9d05430ad810 --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild/java/ModulePathArgumentProvider.kt @@ -0,0 +1,40 @@ +package junitbuild.java + +import org.gradle.api.Named +import org.gradle.api.Project +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.Directory +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.* +import org.gradle.process.CommandLineArgumentProvider +import javax.inject.Inject + +abstract class ModulePathArgumentProvider @Inject constructor(project: Project, combinedModuleSourceDir: Provider, modularProjects: List) : + CommandLineArgumentProvider, Named { + + @get:CompileClasspath + abstract val modulePath: ConfigurableFileCollection + + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val moduleSourceDirs: ConfigurableFileCollection + + init { + modularProjects.forEach { + if (it == project) + moduleSourceDirs.from(combinedModuleSourceDir) + else + moduleSourceDirs.from(project.files("${it.projectDir}/src/module")) + } + } + + override fun asArguments() = listOf( + "--module-path", + modulePath.asPath, + "--module-source-path", + moduleSourceDirs.asPath + ) + + @Internal + override fun getName() = "module-path" +} diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/java/PatchModuleArgumentProvider.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/java/PatchModuleArgumentProvider.kt new file mode 100644 index 000000000000..0c7f330f8adf --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild/java/PatchModuleArgumentProvider.kt @@ -0,0 +1,45 @@ +package junitbuild.java + +import javaModuleName +import org.gradle.api.Named +import org.gradle.api.Project +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* +import org.gradle.kotlin.dsl.get +import org.gradle.kotlin.dsl.the +import org.gradle.process.CommandLineArgumentProvider +import javax.inject.Inject + +abstract class PatchModuleArgumentProvider @Inject constructor(compiledProject: Project, patchModuleProject: Project) : + CommandLineArgumentProvider, Named { + + @get:Input + abstract val module: Property + + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val patch: ConfigurableFileCollection + + init { + module.convention(patchModuleProject.javaModuleName) + patch.from(compiledProject.provider { + if (patchModuleProject == compiledProject) + compiledProject.files(compiledProject.the().matching { it.name.startsWith("main") } + .map { it.output }) + else + patchModuleProject.files(patchModuleProject.the()["main"].java.srcDirs) + }) + } + + override fun asArguments(): List { + val path = patch.filter { it.exists() }.asPath + if (path.isEmpty()) { + return emptyList() + } + return listOf("--patch-module", "${module.get()}=$path") + } + + @Internal + override fun getName() = "patch-module(${module.get()})" +} diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/java/WriteArtifactsFile.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/java/WriteArtifactsFile.kt new file mode 100644 index 000000000000..684c20364597 --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild/java/WriteArtifactsFile.kt @@ -0,0 +1,36 @@ +package junitbuild.java + +import org.gradle.api.DefaultTask +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.ModuleVersionIdentifier +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Provider +import org.gradle.api.provider.SetProperty +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction + +abstract class WriteArtifactsFile : DefaultTask() { + + @get:OutputFile + abstract val outputFile: RegularFileProperty + + @get:Input + abstract val moduleVersions: SetProperty + + fun from(configuration: Provider) { + moduleVersions.addAll(configuration.map { + it.resolvedConfiguration.resolvedArtifacts.map { it.moduleVersion.id } + }) + } + + @TaskAction + fun writeFile() { + outputFile.get().asFile.printWriter().use { out -> + moduleVersions.get() + .map { "${it.group}:${it.name}:${it.version}" } + .sorted() + .forEach(out::println) + } + } +} diff --git a/buildSrc/src/main/kotlin/org/junit/gradle/javadoc/ModuleSpecificJavadocFileOption.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/javadoc/ModuleSpecificJavadocFileOption.kt similarity index 96% rename from buildSrc/src/main/kotlin/org/junit/gradle/javadoc/ModuleSpecificJavadocFileOption.kt rename to gradle/plugins/common/src/main/kotlin/junitbuild/javadoc/ModuleSpecificJavadocFileOption.kt index e9391b22d1b8..f4c6f124b65e 100644 --- a/buildSrc/src/main/kotlin/org/junit/gradle/javadoc/ModuleSpecificJavadocFileOption.kt +++ b/gradle/plugins/common/src/main/kotlin/junitbuild/javadoc/ModuleSpecificJavadocFileOption.kt @@ -1,4 +1,4 @@ -package org.junit.gradle.javadoc +package junitbuild.javadoc import org.gradle.external.javadoc.JavadocOptionFileOption import org.gradle.external.javadoc.internal.JavadocOptionFileWriterContext diff --git a/gradle/plugins/settings.gradle.kts b/gradle/plugins/settings.gradle.kts new file mode 100644 index 000000000000..0b948c46d94f --- /dev/null +++ b/gradle/plugins/settings.gradle.kts @@ -0,0 +1,20 @@ +val expectedJavaVersion = JavaVersion.VERSION_17 +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")) + } + } +} + +rootProject.name = "plugins" + +include("build-parameters") +include("common") + +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") diff --git a/src/checkBuildReproducibility.sh b/gradle/scripts/checkBuildReproducibility.sh similarity index 74% rename from src/checkBuildReproducibility.sh rename to gradle/scripts/checkBuildReproducibility.sh index a9fe7071ecdd..b07275624da1 100755 --- a/src/checkBuildReproducibility.sh +++ b/gradle/scripts/checkBuildReproducibility.sh @@ -7,7 +7,7 @@ export SOURCE_DATE_EPOCH=$(date +%s) function calculate_checksums() { OUTPUT=$1 - ./gradlew --no-build-cache clean assemble --parallel + ./gradlew --no-build-cache clean assemble --parallel -Porg.gradle.java.installations.auto-download=false -Dscan.tag.Reproducibility find . -name '*.jar' \ | grep '/build/libs/' \ diff --git a/src/publishDocumentationSnapshotOnlyIfNecessary.sh b/gradle/scripts/publishDocumentationSnapshotOnlyIfNecessary.sh similarity index 85% rename from src/publishDocumentationSnapshotOnlyIfNecessary.sh rename to gradle/scripts/publishDocumentationSnapshotOnlyIfNecessary.sh index 14372c100b91..ecd0482a2368 100755 --- a/src/publishDocumentationSnapshotOnlyIfNecessary.sh +++ b/gradle/scripts/publishDocumentationSnapshotOnlyIfNecessary.sh @@ -22,11 +22,14 @@ md5sum $(find junit-platform-commons -wholename '**/src/main/*.java') >> "${curr md5sum $(find junit-platform-console -wholename '**/src/main/*.java') >> "${current}" # skip module junit-platform-console-standalone because it doesn't contain relevant documentation md5sum $(find junit-platform-engine -wholename '**/src/main/*.java') >> "${current}" -# TODO md5sum $(find junit-platform-jfr -wholename '**/src/main/*.java') >> "${current}" +md5sum $(find junit-platform-jfr -wholename '**/src/main/*.java') >> "${current}" md5sum $(find junit-platform-launcher -wholename '**/src/main/*.java') >> "${current}" md5sum $(find junit-platform-reporting -wholename '**/src/main/*.java') >> "${current}" md5sum $(find junit-platform-runner -wholename '**/src/main/*.java') >> "${current}" +md5sum $(find junit-platform-suite -wholename '**/src/main/*.java') >> "${current}" md5sum $(find junit-platform-suite-api -wholename '**/src/main/*.java') >> "${current}" +md5sum $(find junit-platform-suite-commons -wholename '**/src/main/*.java') >> "${current}" +md5sum $(find junit-platform-suite-engine -wholename '**/src/main/*.java') >> "${current}" md5sum $(find junit-platform-testkit -wholename '**/src/main/*.java') >> "${current}" md5sum $(find junit-vintage-engine -wholename '**/src/main/*.java') >> "${current}" # skip module platform-tests because it doesn't contain relevant documentation @@ -55,5 +58,5 @@ else echo "Creating and publishing documentation..." echo cp --force "${current}" "${published}" - ./gradlew --scan gitPublishPush + ./gradlew gitPublishPush -Porg.gradle.java.installations.auto-download=false -Dscan.tag.Documentation fi diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023ec..033e24c4cdf4 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 4a1a99959e8b..c2447881d454 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,8 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=7873ed5287f47ca03549ab8dcb6dc877ac7f0e3d7b1eb12685161d10080910ac -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip +distributionSha256Sum=03ec176d388f2aa99defcadc3ac6adf8dd2bce5145a129659537c0874dea5ad1 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0c811f..fcb6fca147c0 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,98 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +118,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,88 +129,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 107acd32c4e6..93e3f59f135d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/junit-bom/junit-bom.gradle.kts b/junit-bom/junit-bom.gradle.kts index 6d50be082030..5e9107a5ac1f 100644 --- a/junit-bom/junit-bom.gradle.kts +++ b/junit-bom/junit-bom.gradle.kts @@ -1,7 +1,6 @@ plugins { `java-platform` - `publishing-conventions` - `custom-java-home` + id("junitbuild.publishing-conventions") } description = "${rootProject.description} (Bill of Materials)" @@ -18,8 +17,8 @@ dependencies { publishing.publications.named("maven") { from(components["javaPlatform"]) pom { - description.set("This Bill of Materials POM can be used to ease dependency management " + - "when referencing multiple JUnit artifacts using Gradle or Maven.") + description = "This Bill of Materials POM can be used to ease dependency management " + + "when referencing multiple JUnit artifacts using Gradle or Maven." withXml { val filteredContent = asString().replace("\\s*compile".toRegex(), "") asString().clear().append(filteredContent) diff --git a/junit-jupiter-api/junit-jupiter-api.gradle.kts b/junit-jupiter-api/junit-jupiter-api.gradle.kts index f7a66e3c419b..7dd03a78f4e9 100644 --- a/junit-jupiter-api/junit-jupiter-api.gradle.kts +++ b/junit-jupiter-api/junit-jupiter-api.gradle.kts @@ -1,17 +1,33 @@ plugins { - `kotlin-library-conventions` + id("junitbuild.kotlin-library-conventions") `java-test-fixtures` } description = "JUnit Jupiter API" dependencies { - internal(platform(project(":dependencies"))) + api(platform(projects.junitBom)) + api(libs.opentest4j) + api(projects.junitPlatformCommons) - api(platform(project(":junit-bom"))) - api("org.apiguardian:apiguardian-api") - api("org.opentest4j:opentest4j") - api(project(":junit-platform-commons")) + compileOnlyApi(libs.apiguardian) - compileOnly("org.jetbrains.kotlin:kotlin-stdlib") + compileOnly(kotlin("stdlib")) + + osgiVerification(projects.junitJupiterEngine) + osgiVerification(projects.junitPlatformLauncher) +} + +tasks { + jar { + bundle { + val version = project.version + bnd(""" + Require-Capability:\ + org.junit.platform.engine;\ + filter:='(&(org.junit.platform.engine=junit-jupiter)(version>=${'$'}{version_cleanup;${version}})(!(version>=${'$'}{versionmask;+;${'$'}{version_cleanup;${version}}})))';\ + effective:=active + """) + } + } } 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 6ae092d20b64..52743ebef416 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -27,22 +27,29 @@ *

In contrast to {@link AfterEach @AfterEach} methods, {@code @AfterAll} * methods are only executed once for a given test class. * - *

Method Signatures

+ *

Method Signatures

* - *

{@code @AfterAll} methods must have a {@code void} return type, - * must not be {@code private}, and must be {@code static} by default. - * Consequently, {@code @AfterAll} methods are not + *

{@code @AfterAll} methods must have a {@code void} return type and must be + * {@code static} by default. Consequently, {@code @AfterAll} methods are not * supported in {@link Nested @Nested} test classes or as interface default * methods unless the test class is annotated with - * {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}. {@code @AfterAll} + * {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}. + * However, beginning with Java 16 {@code @AfterAll} methods may be declared as + * {@code static} in {@link Nested @Nested} test classes, and the + * {@code Lifecycle.PER_CLASS} restriction no longer applies. {@code @AfterAll} * methods may optionally declare parameters to be resolved by * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}. * - *

Inheritance and Execution Order

+ *

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

Inheritance and Execution Order

* *

{@code @AfterAll} methods are inherited from superclasses as long as - * they are not hidden or overridden. Furthermore, - * {@code @AfterAll} methods from superclasses will be executed after + * they are not hidden (default mode with {@code static} modifier), + * overridden, or superseded (i.e., replaced based on + * signature only, irrespective of Java's visibility rules). Furthermore, + * {@code @AfterAll} methods from superclasses will be executed before * {@code @AfterAll} methods in subclasses. * *

Similarly, {@code @AfterAll} methods declared in an interface are @@ -71,7 +78,7 @@ * dependencies between the {@code @BeforeAll} methods or between the * {@code @AfterAll} methods. * - *

Composition

+ *

Composition

* *

{@code @AfterAll} may be used as a meta-annotation in order to create * a custom composed annotation that inherits the semantics of 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 1c693e922b49..8dfd018fa4f9 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -26,18 +26,21 @@ * {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, * and {@code @TestTemplate} method in the current test class. * - *

Method Signatures

+ *

Method Signatures

* - *

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

{@code @AfterEach} methods must have a {@code void} return type and must + * not be {@code static}. Using {@code private} visibility is strongly + * discouraged and will be disallowed in a future release. * They may optionally declare parameters to be resolved by * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}. * - *

Inheritance and Execution Order

+ *

Inheritance and Execution Order

* - *

{@code @AfterEach} methods are inherited from superclasses as long as - * they are not overridden. Furthermore, {@code @AfterEach} methods from - * superclasses will be executed after {@code @AfterEach} methods in subclasses. + *

{@code @AfterEach} methods are inherited from superclasses as long as they + * are not overridden or superseded (i.e., replaced based on + * signature only, irrespective of Java's visibility rules). Furthermore, + * {@code @AfterEach} methods from superclasses will be executed after + * {@code @AfterEach} methods in subclasses. * *

Similarly, {@code @AfterEach} methods declared as interface default * methods are inherited as long as they are not overridden, and @@ -65,7 +68,7 @@ * no dependencies between the {@code @BeforeEach} methods or between the * {@code @AfterEach} methods. * - *

Composition

+ *

Composition

* *

{@code @AfterEach} may be used as a meta-annotation in order to create * a custom composed annotation that inherits the semantics of 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 0cf00416fde0..601e9ecf8d9c 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -62,8 +62,8 @@ static void assertAll(String heading, Stream executables) { Preconditions.notNull(executables, "executables stream must not be null"); List failures = executables // - .peek(executable -> Preconditions.notNull(executable, "individual executables must not be null"))// .map(executable -> { + Preconditions.notNull(executable, "individual executables must not be null"); try { executable.execute(); return null; 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 67b75c0bc608..45d37399c0e1 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -10,11 +10,8 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; -import static org.junit.jupiter.api.AssertionUtils.fail; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.jupiter.api.AssertionUtils.formatIndexes; -import static org.junit.jupiter.api.AssertionUtils.formatValues; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; import static org.junit.platform.commons.util.ReflectionUtils.isArray; import java.util.ArrayDeque; @@ -406,30 +403,41 @@ private static void assertArraysNotNull(Object expected, Object actual, Deque indexes, Object messageOrSupplier) { - fail(buildPrefix(nullSafeGet(messageOrSupplier)) + "expected array was " + formatIndexes(indexes)); + assertionFailure() // + .message(messageOrSupplier) // + .reason("expected array was " + formatIndexes(indexes)) // + .buildAndThrow(); } private static void failActualArrayIsNull(Deque indexes, Object messageOrSupplier) { - fail(buildPrefix(nullSafeGet(messageOrSupplier)) + "actual array was " + formatIndexes(indexes)); + assertionFailure() // + .message(messageOrSupplier) // + .reason("actual array was " + formatIndexes(indexes)) // + .buildAndThrow(); } private static void assertArraysHaveSameLength(int expected, int actual, Deque indexes, Object messageOrSupplier) { if (expected != actual) { - String prefix = buildPrefix(nullSafeGet(messageOrSupplier)); - String message = "array lengths differ" + formatIndexes(indexes) + ", expected: <" + expected - + "> but was: <" + actual + ">"; - fail(prefix + message); + assertionFailure() // + .message(messageOrSupplier) // + .reason("array lengths differ" + formatIndexes(indexes)) // + .expected(expected) // + .actual(actual) // + .buildAndThrow(); } } private static void failArraysNotEqual(Object expected, Object actual, Deque indexes, Object messageOrSupplier) { - String prefix = buildPrefix(nullSafeGet(messageOrSupplier)); - String message = "array contents differ" + formatIndexes(indexes) + ", " + formatValues(expected, actual); - fail(prefix + message); + assertionFailure() // + .message(messageOrSupplier) // + .reason("array contents differ" + formatIndexes(indexes)) // + .expected(expected) // + .actual(actual) // + .buildAndThrow(); } private static Deque nullSafeIndexes(Deque indexes, int newIndex) { 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 20b9ba749fb5..2d424aa7ed39 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -10,8 +10,7 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.util.function.Supplier; @@ -78,9 +77,11 @@ private static T assertDoesNotThrow(ThrowingSupplier supplier, Object mes } private static AssertionFailedError createAssertionFailedError(Object messageOrSupplier, Throwable t) { - String message = buildPrefix(nullSafeGet(messageOrSupplier)) + "Unexpected exception thrown: " - + t.getClass().getName() + buildSuffix(t.getMessage()); - return new AssertionFailedError(message, t); + return assertionFailure() // + .message(messageOrSupplier) // + .reason("Unexpected exception thrown: " + t.getClass().getName() + buildSuffix(t.getMessage())) // + .cause(t) // + .build(); } private static String buildSuffix(String message) { 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 2ed37d219f6f..2a46cea309f3 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -10,8 +10,8 @@ package org.junit.jupiter.api; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.jupiter.api.AssertionUtils.doublesAreEqual; -import static org.junit.jupiter.api.AssertionUtils.failNotEqual; import static org.junit.jupiter.api.AssertionUtils.floatsAreEqual; import static org.junit.jupiter.api.AssertionUtils.objectsAreEqual; @@ -189,4 +189,11 @@ static void assertEquals(Object expected, Object actual, Supplier messag } } + private static void failNotEqual(Object expected, Object actual, Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .expected(expected) // + .actual(actual) // + .buildAndThrow(); + } } 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 01c67a0e87ea..5291a6ac6ead 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -10,9 +10,7 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; -import static org.junit.jupiter.api.AssertionUtils.fail; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.util.function.BooleanSupplier; import java.util.function.Supplier; @@ -25,8 +23,6 @@ */ class AssertFalse { - private static final String EXPECTED_FALSE = "expected: but was: "; - private AssertFalse() { /* no-op */ } @@ -37,13 +33,13 @@ static void assertFalse(boolean condition) { static void assertFalse(boolean condition, String message) { if (condition) { - fail(buildPrefix(message) + EXPECTED_FALSE, false, true); + failNotFalse(message); } } static void assertFalse(boolean condition, Supplier messageSupplier) { if (condition) { - fail(buildPrefix(nullSafeGet(messageSupplier)) + EXPECTED_FALSE, false, true); + failNotFalse(messageSupplier); } } @@ -59,4 +55,12 @@ static void assertFalse(BooleanSupplier booleanSupplier, Supplier messag assertFalse(booleanSupplier.getAsBoolean(), messageSupplier); } + private static void failNotFalse(Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .expected(false) // + .actual(true) // + .buildAndThrow(); + } + } 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 new file mode 100644 index 000000000000..2f265f3fc237 --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; + +import java.util.function.Supplier; + +/** + * {@code AssertInstanceOf} is a collection of utility methods that support + * asserting that an object is of an expected type — in other words, if it + * can be assigned to the expected type. + * + * @since 5.8 + */ +class AssertInstanceOf { + + private AssertInstanceOf() { + /* no-op */ + } + + static T assertInstanceOf(Class expectedType, Object actualValue) { + return assertInstanceOf(expectedType, actualValue, (Object) null); + } + + static T assertInstanceOf(Class expectedType, Object actualValue, String message) { + return assertInstanceOf(expectedType, actualValue, (Object) message); + } + + static T assertInstanceOf(Class expectedType, Object actualValue, Supplier messageSupplier) { + return assertInstanceOf(expectedType, actualValue, (Object) messageSupplier); + } + + private static T assertInstanceOf(Class expectedType, Object actualValue, Object messageOrSupplier) { + if (!expectedType.isInstance(actualValue)) { + assertionFailure() // + .message(messageOrSupplier) // + .reason(actualValue == null ? "Unexpected null value" : "Unexpected type") // + .expected(expectedType) // + .actual(actualValue == null ? null : actualValue.getClass()) // + .buildAndThrow(); + } + return expectedType.cast(actualValue); + } + +} 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 98df2ba54493..b837c449bca2 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -10,15 +10,14 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; -import static org.junit.jupiter.api.AssertionUtils.fail; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.jupiter.api.AssertionUtils.formatIndexes; -import static org.junit.jupiter.api.AssertionUtils.formatValues; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; import java.util.ArrayDeque; import java.util.Deque; import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; @@ -49,6 +48,11 @@ static void assertIterableEquals(Iterable expected, Iterable actual, Suppl private static void assertIterableEquals(Iterable expected, Iterable actual, Deque indexes, Object messageOrSupplier) { + assertIterableEquals(expected, actual, indexes, messageOrSupplier, new LinkedHashMap<>()); + } + + private static void assertIterableEquals(Iterable expected, Iterable actual, Deque indexes, + Object messageOrSupplier, Map investigatedElements) { if (expected == actual) { return; @@ -60,28 +64,61 @@ private static void assertIterableEquals(Iterable expected, Iterable actua int processed = 0; while (expectedIterator.hasNext() && actualIterator.hasNext()) { - processed++; Object expectedElement = expectedIterator.next(); Object actualElement = actualIterator.next(); - if (Objects.equals(expectedElement, actualElement)) { - continue; - } + indexes.addLast(processed); + + assertIterableElementsEqual(expectedElement, actualElement, indexes, messageOrSupplier, + investigatedElements); - indexes.addLast(processed - 1); - assertIterableElementsEqual(expectedElement, actualElement, indexes, messageOrSupplier); indexes.removeLast(); + processed++; } assertIteratorsAreEmpty(expectedIterator, actualIterator, processed, indexes, messageOrSupplier); } private static void assertIterableElementsEqual(Object expected, Object actual, Deque indexes, - Object messageOrSupplier) { + Object messageOrSupplier, Map investigatedElements) { + + // If both are equal, we don't need to check recursively. + if (Objects.equals(expected, actual)) { + return; + } + + // If both are iterables, we need to check whether they contain the same elements. if (expected instanceof Iterable && actual instanceof Iterable) { - assertIterableEquals((Iterable) expected, (Iterable) actual, indexes, messageOrSupplier); + + Pair pair = new Pair(expected, actual); + + // Before comparing their elements, we check whether we have already checked this pair. + Status status = investigatedElements.get(pair); + + // If we've already determined that both contain the same elements, we don't need to check them again. + if (status == Status.CONTAIN_SAME_ELEMENTS) { + return; + } + + // If the pair is already under investigation, we fail in order to avoid infinite recursion. + if (status == Status.UNDER_INVESTIGATION) { + indexes.removeLast(); + failIterablesNotEqual(expected, actual, indexes, messageOrSupplier); + } + + // Otherwise, we put the pair under investigation and recurse. + investigatedElements.put(pair, Status.UNDER_INVESTIGATION); + + assertIterableEquals((Iterable) expected, (Iterable) actual, indexes, messageOrSupplier, + investigatedElements); + + // If we reach this point, we've checked that the two iterables contain the same elements so we store this information + // in case we come across the same pair again. + investigatedElements.put(pair, Status.CONTAIN_SAME_ELEMENTS); } - else if (!Objects.equals(expected, actual)) { + + // Otherwise, they are neither equal nor iterables, so we fail. + else { assertIterablesNotNull(expected, actual, indexes, messageOrSupplier); failIterablesNotEqual(expected, actual, indexes, messageOrSupplier); } @@ -99,11 +136,17 @@ private static void assertIterablesNotNull(Object expected, Object actual, Deque } private static void failExpectedIterableIsNull(Deque indexes, Object messageOrSupplier) { - fail(buildPrefix(nullSafeGet(messageOrSupplier)) + "expected iterable was " + formatIndexes(indexes)); + assertionFailure() // + .message(messageOrSupplier) // + .reason("expected iterable was " + formatIndexes(indexes)) // + .buildAndThrow(); } private static void failActualIterableIsNull(Deque indexes, Object messageOrSupplier) { - fail(buildPrefix(nullSafeGet(messageOrSupplier)) + "actual iterable was " + formatIndexes(indexes)); + assertionFailure() // + .message(messageOrSupplier) // + .reason("actual iterable was " + formatIndexes(indexes)) // + .buildAndThrow(); } private static void assertIteratorsAreEmpty(Iterator expected, Iterator actual, int processed, @@ -116,19 +159,54 @@ private static void assertIteratorsAreEmpty(Iterator expected, Iterator ac AtomicInteger actualCount = new AtomicInteger(processed); actual.forEachRemaining(e -> actualCount.incrementAndGet()); - String prefix = buildPrefix(nullSafeGet(messageOrSupplier)); - String message = "iterable lengths differ" + formatIndexes(indexes) + ", expected: <" + expectedCount.get() - + "> but was: <" + actualCount.get() + ">"; - fail(prefix + message); + assertionFailure() // + .message(messageOrSupplier) // + .reason("iterable lengths differ" + formatIndexes(indexes)) // + .expected(expectedCount.get()) // + .actual(actualCount.get()) // + .buildAndThrow(); } } private static void failIterablesNotEqual(Object expected, Object actual, Deque indexes, Object messageOrSupplier) { - String prefix = buildPrefix(nullSafeGet(messageOrSupplier)); - String message = "iterable contents differ" + formatIndexes(indexes) + ", " + formatValues(expected, actual); - fail(prefix + message); + assertionFailure() // + .message(messageOrSupplier) // + .reason("iterable contents differ" + formatIndexes(indexes)) // + .expected(expected) // + .actual(actual) // + .buildAndThrow(); + } + + private final static class Pair { + private final Object left; + private final Object right; + + public Pair(Object left, Object right) { + this.left = left; + this.right = right; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Pair that = (Pair) o; + return Objects.equals(this.left, that.left) // + && Objects.equals(this.right, that.right); + } + + @Override + public int hashCode() { + return Objects.hash(left, right); + } + } + + private enum Status { + UNDER_INVESTIGATION, CONTAIN_SAME_ELEMENTS } } 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 2e65d69995a3..03e79081bd9e 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -12,8 +12,7 @@ import static java.lang.String.format; import static java.lang.String.join; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.platform.commons.util.Preconditions.condition; import static org.junit.platform.commons.util.Preconditions.notNull; @@ -127,7 +126,7 @@ void assertLinesMatchWithFastForward() { } String actualLine = actualDeque.peek(); - // trivial case: take the fast path when they simply match + // trivial case: take the fast path when they match if (matches(expectedLine, actualLine)) { actualDeque.pop(); continue; // main @@ -136,10 +135,10 @@ void assertLinesMatchWithFastForward() { // fast-forward marker found in expected line: fast-forward actual line... if (isFastForwardLine(expectedLine)) { int fastForwardLimit = parseFastForwardLimit(expectedLine); + int actualRemaining = actualDeque.size(); // trivial case: fast-forward marker was in last expected line if (expectedDeque.isEmpty()) { - int actualRemaining = actualDeque.size(); // no limit given or perfect match? we're done. if (fastForwardLimit == Integer.MAX_VALUE || fastForwardLimit == actualRemaining) { return; @@ -150,6 +149,10 @@ void assertLinesMatchWithFastForward() { // fast-forward limit was given: use it if (fastForwardLimit != Integer.MAX_VALUE) { + if (actualRemaining < fastForwardLimit) { + fail("fast-forward(%d) error: not enough actual lines remaining (%s)", fastForwardLimit, + actualRemaining); + } // fast-forward now: actualDeque.pop(fastForwardLimit) for (int i = 0; i < fastForwardLimit; i++) { actualDeque.pop(); @@ -191,8 +194,13 @@ String snippet(String line) { void fail(String format, Object... args) { String newLine = System.lineSeparator(); - String message = buildPrefix(nullSafeGet(messageOrSupplier)) + format(format, args); - AssertionUtils.fail(message, join(newLine, expectedLines), join(newLine, actualLines)); + assertionFailure() // + .message(messageOrSupplier) // + .reason(format(format, args)) // + .expected(join(newLine, expectedLines)) // + .actual(join(newLine, actualLines)) // + .includeValuesInMessage(false) // + .buildAndThrow(); } } @@ -202,7 +210,8 @@ static boolean isFastForwardLine(String line) { } static int parseFastForwardLimit(String fastForwardLine) { - String text = fastForwardLine.trim().substring(2, fastForwardLine.length() - 2).trim(); + fastForwardLine = fastForwardLine.trim(); + String text = fastForwardLine.substring(2, fastForwardLine.length() - 2).trim(); try { int limit = Integer.parseInt(text); condition(limit > 0, () -> format("fast-forward(%d) limit must be greater than zero", limit)); 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 abc3295e1974..5a37b059a0fc 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -10,11 +10,9 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.jupiter.api.AssertionUtils.doublesAreEqual; -import static org.junit.jupiter.api.AssertionUtils.fail; import static org.junit.jupiter.api.AssertionUtils.floatsAreEqual; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; import static org.junit.jupiter.api.AssertionUtils.objectsAreEqual; import java.util.function.Supplier; @@ -52,7 +50,7 @@ static void assertNotEquals(byte unexpected, byte actual, String message) { */ static void assertNotEquals(byte unexpected, byte actual, Supplier messageSupplier) { if (unexpected == actual) { - failEqual(actual, nullSafeGet(messageSupplier)); + failEqual(actual, messageSupplier); } } @@ -77,7 +75,7 @@ static void assertNotEquals(short unexpected, short actual, String message) { */ static void assertNotEquals(short unexpected, short actual, Supplier messageSupplier) { if (unexpected == actual) { - failEqual(actual, nullSafeGet(messageSupplier)); + failEqual(actual, messageSupplier); } } @@ -102,7 +100,7 @@ static void assertNotEquals(int unexpected, int actual, String message) { */ static void assertNotEquals(int unexpected, int actual, Supplier messageSupplier) { if (unexpected == actual) { - failEqual(actual, nullSafeGet(messageSupplier)); + failEqual(actual, messageSupplier); } } @@ -127,7 +125,7 @@ static void assertNotEquals(long unexpected, long actual, String message) { */ static void assertNotEquals(long unexpected, long actual, Supplier messageSupplier) { if (unexpected == actual) { - failEqual(actual, nullSafeGet(messageSupplier)); + failEqual(actual, messageSupplier); } } @@ -152,7 +150,7 @@ static void assertNotEquals(float unexpected, float actual, String message) { */ static void assertNotEquals(float unexpected, float actual, Supplier messageSupplier) { if (floatsAreEqual(unexpected, actual)) { - failEqual(actual, nullSafeGet(messageSupplier)); + failEqual(actual, messageSupplier); } } @@ -177,7 +175,7 @@ static void assertNotEquals(float unexpected, float actual, float delta, String */ static void assertNotEquals(float unexpected, float actual, float delta, Supplier messageSupplier) { if (floatsAreEqual(unexpected, actual, delta)) { - failEqual(actual, nullSafeGet(messageSupplier)); + failEqual(actual, messageSupplier); } } @@ -202,7 +200,7 @@ static void assertNotEquals(double unexpected, double actual, String message) { */ static void assertNotEquals(double unexpected, double actual, Supplier messageSupplier) { if (doublesAreEqual(unexpected, actual)) { - failEqual(actual, nullSafeGet(messageSupplier)); + failEqual(actual, messageSupplier); } } @@ -227,7 +225,7 @@ static void assertNotEquals(double unexpected, double actual, double delta, Stri */ static void assertNotEquals(double unexpected, double actual, double delta, Supplier messageSupplier) { if (doublesAreEqual(unexpected, actual, delta)) { - failEqual(actual, nullSafeGet(messageSupplier)); + failEqual(actual, messageSupplier); } } @@ -252,7 +250,7 @@ static void assertNotEquals(char unexpected, char actual, String message) { */ static void assertNotEquals(char unexpected, char actual, Supplier messageSupplier) { if (unexpected == actual) { - failEqual(actual, nullSafeGet(messageSupplier)); + failEqual(actual, messageSupplier); } } @@ -268,12 +266,15 @@ static void assertNotEquals(Object unexpected, Object actual, String message) { static void assertNotEquals(Object unexpected, Object actual, Supplier messageSupplier) { if (objectsAreEqual(unexpected, actual)) { - failEqual(actual, nullSafeGet(messageSupplier)); + failEqual(actual, messageSupplier); } } - private static void failEqual(Object actual, String message) { - fail(buildPrefix(message) + "expected: not equal but was: <" + actual + ">"); + private static void failEqual(Object actual, Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .reason("expected: not equal but was: <" + actual + ">") // + .buildAndThrow(); } } 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 9d2fb08f5478..43787ab8c27b 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -10,8 +10,7 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.util.function.Supplier; @@ -39,11 +38,14 @@ static void assertNotNull(Object actual, String message) { static void assertNotNull(Object actual, Supplier messageSupplier) { if (actual == null) { - failNull(nullSafeGet(messageSupplier)); + failNull(messageSupplier); } } - private static void failNull(String message) { - Assertions.fail(buildPrefix(message) + "expected: not "); + private static void failNull(Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .reason("expected: not ") // + .buildAndThrow(); } } 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 f596080dd42d..66f795c7a75a 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -10,9 +10,7 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; -import static org.junit.jupiter.api.AssertionUtils.fail; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.util.function.Supplier; @@ -40,12 +38,15 @@ static void assertNotSame(Object unexpected, Object actual, String message) { static void assertNotSame(Object unexpected, Object actual, Supplier messageSupplier) { if (unexpected == actual) { - failSame(actual, nullSafeGet(messageSupplier)); + failSame(actual, messageSupplier); } } - private static void failSame(Object actual, String message) { - fail(buildPrefix(message) + "expected: not same but was: <" + actual + ">"); + private static void failSame(Object actual, Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .reason("expected: not same but was: <" + actual + ">") // + .buildAndThrow(); } } 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 d730f2555b27..53ceb4ce099a 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -10,9 +10,7 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; -import static org.junit.jupiter.api.AssertionUtils.fail; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.util.function.Supplier; @@ -40,11 +38,16 @@ static void assertNull(Object actual, String message) { static void assertNull(Object actual, Supplier messageSupplier) { if (actual != null) { - failNotNull(actual, nullSafeGet(messageSupplier)); + failNotNull(actual, messageSupplier); } } - private static void failNotNull(Object actual, String message) { - fail(buildPrefix(message) + "expected: but was: <" + actual + ">", null, actual); + private static void failNotNull(Object actual, Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .expected(null) // + .actual(actual) // + .buildAndThrow(); } + } 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 06056746b5e3..feafa36231c9 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -10,9 +10,7 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.fail; -import static org.junit.jupiter.api.AssertionUtils.format; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.util.function.Supplier; @@ -40,12 +38,16 @@ static void assertSame(Object expected, Object actual, String message) { static void assertSame(Object expected, Object actual, Supplier messageSupplier) { if (expected != actual) { - failNotSame(expected, actual, nullSafeGet(messageSupplier)); + failNotSame(expected, actual, messageSupplier); } } - private static void failNotSame(Object expected, Object actual, String message) { - fail(format(expected, actual, message), expected, actual); + private static void failNotSame(Object expected, Object actual, Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .expected(expected) // + .actual(actual) // + .buildAndThrow(); } } 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 675e1b717b8e..b61570647cb5 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -10,16 +10,14 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; -import static org.junit.jupiter.api.AssertionUtils.format; +import static java.lang.String.format; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.jupiter.api.AssertionUtils.getCanonicalName; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; import java.util.function.Supplier; import org.junit.jupiter.api.function.Executable; import org.junit.platform.commons.util.UnrecoverableExceptions; -import org.opentest4j.AssertionFailedError; /** * {@code AssertThrows} is a collection of utility methods that support asserting @@ -60,15 +58,19 @@ private static T assertThrows(Class expectedType, Execu } else { UnrecoverableExceptions.rethrowIfUnrecoverable(actualException); - String message = buildPrefix(nullSafeGet(messageOrSupplier)) - + format(expectedType, actualException.getClass(), "Unexpected exception type thrown"); - throw new AssertionFailedError(message, actualException); + throw assertionFailure() // + .message(messageOrSupplier) // + .expected(expectedType) // + .actual(actualException.getClass()) // + .reason("Unexpected exception type thrown") // + .cause(actualException) // + .build(); } } - - String message = buildPrefix(nullSafeGet(messageOrSupplier)) - + String.format("Expected %s to be thrown, but nothing was thrown.", getCanonicalName(expectedType)); - throw new AssertionFailedError(message); + throw assertionFailure() // + .message(messageOrSupplier) // + .reason(format("Expected %s to be thrown, but nothing was thrown.", getCanonicalName(expectedType))) // + .build(); } } 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 new file mode 100644 index 000000000000..8a669388b709 --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrowsExactly.java @@ -0,0 +1,77 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static java.lang.String.format; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; +import static org.junit.jupiter.api.AssertionUtils.getCanonicalName; + +import java.util.function.Supplier; + +import org.junit.jupiter.api.function.Executable; +import org.junit.platform.commons.util.UnrecoverableExceptions; + +/** + * {@code AssertThrowsExactly} is a collection of utility methods that support asserting + * an exception of an exact type is thrown. + * + * @since 5.8 + */ +class AssertThrowsExactly { + + private AssertThrowsExactly() { + /* no-op */ + } + + static T assertThrowsExactly(Class expectedType, Executable executable) { + return assertThrowsExactly(expectedType, executable, (Object) null); + } + + static T assertThrowsExactly(Class expectedType, Executable executable, String message) { + return assertThrowsExactly(expectedType, executable, (Object) message); + } + + static T assertThrowsExactly(Class expectedType, Executable executable, + Supplier messageSupplier) { + + return assertThrowsExactly(expectedType, executable, (Object) messageSupplier); + } + + @SuppressWarnings("unchecked") + private static T assertThrowsExactly(Class expectedType, Executable executable, + Object messageOrSupplier) { + + try { + executable.execute(); + } + catch (Throwable actualException) { + if (expectedType.equals(actualException.getClass())) { + return (T) actualException; + } + else { + UnrecoverableExceptions.rethrowIfUnrecoverable(actualException); + throw assertionFailure() // + .message(messageOrSupplier) // + .expected(expectedType) // + .actual(actualException.getClass()) // + .reason("Unexpected exception type thrown") // + .cause(actualException) // + .build(); + } + } + + throw assertionFailure() // + .message(messageOrSupplier) // + .reason(format("Expected %s to be thrown, but nothing was thrown.", getCanonicalName(expectedType))) // + .build(); + } + +} 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 b8f6a029bea4..bfd1f66806ba 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -10,27 +10,14 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; +import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; import java.time.Duration; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.ThrowingSupplier; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.ExceptionUtils; -import org.opentest4j.AssertionFailedError; /** * {@code AssertTimeout} is a collection of utility methods that support asserting @@ -82,117 +69,18 @@ private static T assertTimeout(Duration timeout, ThrowingSupplier supplie result = supplier.get(); } catch (Throwable ex) { - ExceptionUtils.throwAsUncheckedException(ex); + throwAsUncheckedException(ex); } long timeElapsed = System.currentTimeMillis() - start; if (timeElapsed > timeoutInMillis) { - fail(buildPrefix(nullSafeGet(messageOrSupplier)) + "execution exceeded timeout of " + timeoutInMillis - + " ms by " + (timeElapsed - timeoutInMillis) + " ms"); + assertionFailure() // + .message(messageOrSupplier) // + .reason("execution exceeded timeout of " + timeoutInMillis + " ms by " + + (timeElapsed - timeoutInMillis) + " ms") // + .buildAndThrow(); } return result; } - static void assertTimeoutPreemptively(Duration timeout, Executable executable) { - assertTimeoutPreemptively(timeout, executable, (String) null); - } - - static void assertTimeoutPreemptively(Duration timeout, Executable executable, String message) { - assertTimeoutPreemptively(timeout, () -> { - executable.execute(); - return null; - }, message); - } - - static void assertTimeoutPreemptively(Duration timeout, Executable executable, Supplier messageSupplier) { - assertTimeoutPreemptively(timeout, () -> { - executable.execute(); - return null; - }, messageSupplier); - } - - static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier) { - return assertTimeoutPreemptively(timeout, supplier, (Object) null); - } - - static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, String message) { - return assertTimeoutPreemptively(timeout, supplier, (Object) message); - } - - static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, - Supplier messageSupplier) { - - return assertTimeoutPreemptively(timeout, supplier, (Object) messageSupplier); - } - - private static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, - Object messageOrSupplier) { - - AtomicReference threadReference = new AtomicReference<>(); - ExecutorService executorService = Executors.newSingleThreadExecutor(new TimeoutThreadFactory()); - - try { - Future future = executorService.submit(() -> { - try { - threadReference.set(Thread.currentThread()); - return supplier.get(); - } - catch (Throwable throwable) { - throw ExceptionUtils.throwAsUncheckedException(throwable); - } - }); - - long timeoutInMillis = timeout.toMillis(); - try { - return future.get(timeoutInMillis, TimeUnit.MILLISECONDS); - } - catch (TimeoutException ex) { - String message = buildPrefix(nullSafeGet(messageOrSupplier)) + "execution timed out after " - + timeoutInMillis + " ms"; - - Thread thread = threadReference.get(); - if (thread != null) { - ExecutionTimeoutException exception = new ExecutionTimeoutException( - "Execution timed out in thread " + thread.getName()); - exception.setStackTrace(thread.getStackTrace()); - throw new AssertionFailedError(message, exception); - } - else { - throw new AssertionFailedError(message); - } - } - catch (ExecutionException ex) { - throw ExceptionUtils.throwAsUncheckedException(ex.getCause()); - } - catch (Throwable ex) { - throw ExceptionUtils.throwAsUncheckedException(ex); - } - } - finally { - executorService.shutdownNow(); - } - } - - private static class ExecutionTimeoutException extends JUnitException { - - private static final long serialVersionUID = 1L; - - ExecutionTimeoutException(String message) { - super(message); - } - } - - /** - * The thread factory used for preemptive timeout. - * - * The factory creates threads with meaningful names, helpful for debugging purposes. - */ - private static class TimeoutThreadFactory implements ThreadFactory { - private static final AtomicInteger threadNumber = new AtomicInteger(1); - - public Thread newThread(Runnable r) { - return new Thread(r, "junit-timeout-thread-" + threadNumber.getAndIncrement()); - } - } - } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java new file mode 100644 index 000000000000..4ff96b71556d --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java @@ -0,0 +1,156 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; +import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; + +import java.time.Duration; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.api.function.ThrowingSupplier; +import org.junit.platform.commons.JUnitException; +import org.opentest4j.AssertionFailedError; + +/** + * {@code AssertTimeout} is a collection of utility methods that support asserting + * the execution of the code under test did not take longer than the timeout duration + * using a preemptive approach. + * + * @since 5.9.1 + */ +class AssertTimeoutPreemptively { + + static void assertTimeoutPreemptively(Duration timeout, Executable executable) { + assertTimeoutPreemptively(timeout, executable, (String) null); + } + + static void assertTimeoutPreemptively(Duration timeout, Executable executable, String message) { + assertTimeoutPreemptively(timeout, () -> { + executable.execute(); + return null; + }, message); + } + + static void assertTimeoutPreemptively(Duration timeout, Executable executable, Supplier messageSupplier) { + assertTimeoutPreemptively(timeout, () -> { + executable.execute(); + return null; + }, messageSupplier); + } + + static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier) { + return assertTimeoutPreemptively(timeout, supplier, null, AssertTimeoutPreemptively::createAssertionFailure); + } + + static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, String message) { + return assertTimeoutPreemptively(timeout, supplier, message == null ? null : () -> message, + AssertTimeoutPreemptively::createAssertionFailure); + } + + static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, + Supplier messageSupplier) { + return assertTimeoutPreemptively(timeout, supplier, messageSupplier, + AssertTimeoutPreemptively::createAssertionFailure); + } + + static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, + Supplier messageSupplier, Assertions.TimeoutFailureFactory failureFactory) throws E { + AtomicReference threadReference = new AtomicReference<>(); + ExecutorService executorService = Executors.newSingleThreadExecutor(new TimeoutThreadFactory()); + + try { + Future future = submitTask(supplier, threadReference, executorService); + return resolveFutureAndHandleException(future, timeout, messageSupplier, threadReference::get, + failureFactory); + } + finally { + executorService.shutdownNow(); + } + } + + private static Future submitTask(ThrowingSupplier supplier, AtomicReference threadReference, + ExecutorService executorService) { + return executorService.submit(() -> { + try { + threadReference.set(Thread.currentThread()); + return supplier.get(); + } + catch (Throwable throwable) { + throw throwAsUncheckedException(throwable); + } + }); + } + + private static T resolveFutureAndHandleException(Future future, Duration timeout, + Supplier messageSupplier, Supplier threadSupplier, + Assertions.TimeoutFailureFactory failureFactory) throws E { + try { + return future.get(timeout.toMillis(), TimeUnit.MILLISECONDS); + } + catch (TimeoutException ex) { + Thread thread = threadSupplier.get(); + ExecutionTimeoutException cause = null; + if (thread != null) { + cause = new ExecutionTimeoutException("Execution timed out in thread " + thread.getName()); + cause.setStackTrace(thread.getStackTrace()); + } + throw failureFactory.createTimeoutFailure(timeout, messageSupplier, cause); + } + catch (ExecutionException ex) { + throw throwAsUncheckedException(ex.getCause()); + } + catch (Throwable ex) { + throw throwAsUncheckedException(ex); + } + } + + private static AssertionFailedError createAssertionFailure(Duration timeout, Supplier messageSupplier, + Throwable cause) { + return assertionFailure() // + .message(messageSupplier) // + .reason("execution timed out after " + timeout.toMillis() + " ms") // + .cause(cause) // + .build(); + } + + private static class ExecutionTimeoutException extends JUnitException { + + private static final long serialVersionUID = 1L; + + ExecutionTimeoutException(String message) { + super(message); + } + } + + /** + * The thread factory used for preemptive timeout. + *

+ * The factory creates threads with meaningful names, helpful for debugging purposes. + */ + private static class TimeoutThreadFactory implements ThreadFactory { + private static final AtomicInteger threadNumber = new AtomicInteger(1); + + public Thread newThread(Runnable r) { + return new Thread(r, "junit-timeout-thread-" + threadNumber.getAndIncrement()); + } + } +} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java index 853f6051cb48..cf4f94277a93 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -10,9 +10,7 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; -import static org.junit.jupiter.api.AssertionUtils.fail; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.util.function.BooleanSupplier; import java.util.function.Supplier; @@ -25,8 +23,6 @@ */ class AssertTrue { - private static final String EXPECTED_TRUE = "expected: but was: "; - private AssertTrue() { /* no-op */ } @@ -37,13 +33,13 @@ static void assertTrue(boolean condition) { static void assertTrue(boolean condition, String message) { if (!condition) { - fail(buildPrefix(message) + EXPECTED_TRUE, true, false); + failNotTrue(message); } } static void assertTrue(boolean condition, Supplier messageSupplier) { if (!condition) { - fail(buildPrefix(nullSafeGet(messageSupplier)) + EXPECTED_TRUE, true, false); + failNotTrue(messageSupplier); } } @@ -59,4 +55,12 @@ static void assertTrue(BooleanSupplier booleanSupplier, Supplier message assertTrue(booleanSupplier.getAsBoolean(), messageSupplier); } + private static void failNotTrue(Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .expected(true) // + .actual(false) // + .buildAndThrow(); + } + } 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 new file mode 100644 index 000000000000..8d0341d3cb46 --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionFailureBuilder.java @@ -0,0 +1,205 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; +import static org.junit.jupiter.api.AssertionUtils.getCanonicalName; + +import java.util.function.Supplier; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.StringUtils; +import org.opentest4j.AssertionFailedError; + +/** + * Builder for {@link AssertionFailedError AssertionFailedErrors}. + *

+ * Using this builder ensures consistency in how failure message are formatted + * within JUnit Jupiter and for custom user-defined assertions. + * + * @since 5.9 + * @see AssertionFailedError + */ +@API(status = STABLE, since = "5.9") +public class AssertionFailureBuilder { + + private Object message; + private Throwable cause; + private boolean mismatch; + private Object expected; + private Object actual; + private String reason; + private boolean includeValuesInMessage = true; + + /** + * Create a new {@code AssertionFailureBuilder}. + */ + public static AssertionFailureBuilder assertionFailure() { + return new AssertionFailureBuilder(); + } + + private AssertionFailureBuilder() { + } + + /** + * Set the user-defined message of the assertion. + *

+ * The {@code message} may be passed as a {@link Supplier} or plain + * {@link String}. If any other type is passed, it is converted to + * {@code String} as per {@link StringUtils#nullSafeToString(Object)}. + * + * @param message the user-defined failure message; may be {@code null} + * @return this builder for method chaining + */ + public AssertionFailureBuilder message(Object message) { + this.message = message; + return this; + } + + /** + * Set the reason why the assertion failed. + * + * @param reason the failure reason; may be {@code null} + * @return this builder for method chaining + */ + public AssertionFailureBuilder reason(String reason) { + this.reason = reason; + return this; + } + + /** + * Set the cause of the assertion failure. + * + * @param cause the failure cause; may be {@code null} + * @return this builder for method chaining + */ + public AssertionFailureBuilder cause(Throwable cause) { + this.cause = cause; + return this; + } + + /** + * Set the expected value of the assertion. + * + * @param expected the expected value; may be {@code null} + * @return this builder for method chaining + */ + public AssertionFailureBuilder expected(Object expected) { + this.mismatch = true; + this.expected = expected; + return this; + } + + /** + * Set the actual value of the assertion. + * + * @param actual the actual value; may be {@code null} + * @return this builder for method chaining + */ + public AssertionFailureBuilder actual(Object actual) { + this.mismatch = true; + this.actual = actual; + return this; + } + + /** + * Set whether to include the actual and expected values in the generated + * failure message. + * + * @param includeValuesInMessage whether to include the actual and expected + * values + * @return this builder for method chaining + */ + public AssertionFailureBuilder includeValuesInMessage(boolean includeValuesInMessage) { + this.includeValuesInMessage = includeValuesInMessage; + return this; + } + + /** + * Build the {@link AssertionFailedError AssertionFailedError} and throw it. + * + * @throws AssertionFailedError always + */ + public void buildAndThrow() throws AssertionFailedError { + throw build(); + } + + /** + * Build the {@link AssertionFailedError AssertionFailedError} without + * throwing it. + * + * @return the built assertion failure + */ + public AssertionFailedError build() { + String reason = nullSafeGet(this.reason); + if (mismatch && includeValuesInMessage) { + reason = (reason == null ? "" : reason + ", ") + formatValues(expected, actual); + } + String message = nullSafeGet(this.message); + if (reason != null) { + message = buildPrefix(message) + reason; + } + return mismatch // + ? new AssertionFailedError(message, expected, actual, cause) // + : new AssertionFailedError(message, cause); + } + + private static String nullSafeGet(Object messageOrSupplier) { + if (messageOrSupplier == null) { + return null; + } + if (messageOrSupplier instanceof Supplier) { + Object message = ((Supplier) messageOrSupplier).get(); + return StringUtils.nullSafeToString(message); + } + return StringUtils.nullSafeToString(messageOrSupplier); + } + + private static String buildPrefix(String message) { + return (StringUtils.isNotBlank(message) ? message + " ==> " : ""); + } + + private static String formatValues(Object expected, Object actual) { + String expectedString = toString(expected); + String actualString = toString(actual); + if (expectedString.equals(actualString)) { + return String.format("expected: %s but was: %s", formatClassAndValue(expected, expectedString), + formatClassAndValue(actual, actualString)); + } + return String.format("expected: <%s> but was: <%s>", expectedString, actualString); + } + + private static String formatClassAndValue(Object value, String valueString) { + // If the value is null, return instead of null. + if (value == null) { + return ""; + } + String classAndHash = getClassName(value) + toHash(value); + // if it's a class, there's no need to repeat the class name contained in the valueString. + return (value instanceof Class ? "<" + classAndHash + ">" : classAndHash + "<" + valueString + ">"); + } + + private static String toString(Object obj) { + if (obj instanceof Class) { + return getCanonicalName((Class) obj); + } + return StringUtils.nullSafeToString(obj); + } + + private static String toHash(Object obj) { + return (obj == null ? "" : "@" + Integer.toHexString(System.identityHashCode(obj))); + } + + private static String getClassName(Object obj) { + return (obj == null ? "null" + : obj instanceof Class ? getCanonicalName((Class) obj) : obj.getClass().getName()); + } +} 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 0aba77997855..48a74da2ce96 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -15,7 +15,6 @@ import java.util.Deque; import java.util.function.Supplier; -import org.junit.platform.commons.util.StringUtils; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.opentest4j.AssertionFailedError; @@ -51,52 +50,10 @@ static void fail(Supplier messageSupplier) { throw new AssertionFailedError(nullSafeGet(messageSupplier)); } - static void fail(String message, Object expected, Object actual) { - throw new AssertionFailedError(message, expected, actual); - } - - /** - * Typically used for {@code assertEquals()}. - */ - static void failNotEqual(Object expected, Object actual, String message) { - fail(format(expected, actual, message), expected, actual); - } - - /** - * Typically used for {@code assertEquals()}. - */ - static void failNotEqual(Object expected, Object actual, Supplier messageSupplier) { - fail(format(expected, actual, nullSafeGet(messageSupplier)), expected, actual); - } - static String nullSafeGet(Supplier messageSupplier) { return (messageSupplier != null ? messageSupplier.get() : null); } - /** - * Alternative to {@link #nullSafeGet(Supplier)} that is used to avoid - * wrapping a String in a lambda expression. - * - * @param messageOrSupplier an object that is either a {@code String} or - * {@code Supplier} - */ - static String nullSafeGet(Object messageOrSupplier) { - if (messageOrSupplier instanceof String) { - return (String) messageOrSupplier; - } - if (messageOrSupplier instanceof Supplier) { - Object message = ((Supplier) messageOrSupplier).get(); - if (message != null) { - return message.toString(); - } - } - return null; - } - - static String buildPrefix(String message) { - return (StringUtils.isNotBlank(message) ? message + " ==> " : ""); - } - static String getCanonicalName(Class clazz) { try { String canonicalName = clazz.getCanonicalName(); @@ -108,42 +65,6 @@ static String getCanonicalName(Class clazz) { } } - static String format(Object expected, Object actual, String message) { - return buildPrefix(message) + formatValues(expected, actual); - } - - static String formatValues(Object expected, Object actual) { - String expectedString = toString(expected); - String actualString = toString(actual); - if (expectedString.equals(actualString)) { - return String.format("expected: %s but was: %s", formatClassAndValue(expected, expectedString), - formatClassAndValue(actual, actualString)); - } - return String.format("expected: <%s> but was: <%s>", expectedString, actualString); - } - - private static String formatClassAndValue(Object value, String valueString) { - String classAndHash = getClassName(value) + toHash(value); - // if it's a class, there's no need to repeat the class name contained in the valueString. - return (value instanceof Class ? "<" + classAndHash + ">" : classAndHash + "<" + valueString + ">"); - } - - private static String toString(Object obj) { - if (obj instanceof Class) { - return getCanonicalName((Class) obj); - } - return StringUtils.nullSafeToString(obj); - } - - private static String toHash(Object obj) { - return (obj == null ? "" : "@" + Integer.toHexString(System.identityHashCode(obj))); - } - - private static String getClassName(Object obj) { - return (obj == null ? "null" - : obj instanceof Class ? getCanonicalName((Class) obj) : obj.getClass().getName()); - } - static String formatIndexes(Deque indexes) { if (indexes == null || indexes.isEmpty()) { return ""; 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 59ebdf61f3b0..4be1561689dc 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -10,6 +10,7 @@ package org.junit.jupiter.api; +import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; import java.time.Duration; @@ -33,13 +34,29 @@ *

Unless otherwise noted, a failed assertion will throw an * {@link org.opentest4j.AssertionFailedError} or a subclass thereof. * - *

Kotlin Support

+ *

Object Equality

+ * + *

Assertion methods comparing two objects for equality, such as the + * {@code assertEquals(expected, actual)} and {@code assertNotEquals(unexpected, actual)} + * variants, are only intended to test equality for an (un-)expected value + * and an actual value. They are not designed for testing whether a class correctly + * implements {@link Object#equals(Object)}. For example, {@code assertEquals()} + * might immediately return {@code true} when provided the same object for the + * expected and actual values, without calling {@code equals(Object)} at all. + * Tests that aim to verify the {@code equals(Object)} implementation should instead + * be written to explicitly verify the {@link Object#equals(Object)} contract by + * using {@link #assertTrue(boolean) assertTrue()} or {@link #assertFalse(boolean) + * assertFalse()} — for example, {@code assertTrue(expected.equals(actual))}, + * {@code assertTrue(actual.equals(expected))}, {@code assertFalse(expected.equals(null))}, + * etc. + * + *

Kotlin Support

* *

Additional Kotlin assertions can be * found as top-level functions in the {@link org.junit.jupiter.api} * package. * - *

Preemptive Timeouts

+ *

Preemptive Timeouts

* *

The various {@code assertTimeoutPreemptively()} methods in this class * execute the provided {@code executable} or {@code supplier} in a different @@ -60,7 +77,7 @@ *

Similar side effects may be encountered with other frameworks that rely on * {@code ThreadLocal} storage. * - *

Extensibility

+ *

Extensibility

* *

Although it is technically possible to extend this class, extension is * strongly discouraged. The JUnit Team highly recommends that the methods @@ -2828,14 +2845,24 @@ public static void assertNotEquals(Object unexpected, Object actual, SupplierAssert that {@code expected} and {@code actual} refer to the same object. + * Assert that the {@code expected} object and the {@code actual} object + * are the same object. + *

This method should only be used to assert identity between objects. + * To assert equality between two objects or two primitive values, + * use one of the {@code assertEquals(...)} methods instead — for example, + * use {@code assertEquals(999, 999)} instead of {@code assertSame(999, 999)}. */ public static void assertSame(Object expected, Object actual) { AssertSame.assertSame(expected, actual); } /** - * Assert that {@code expected} and {@code actual} refer to the same object. + * Assert that the {@code expected} object and the {@code actual} object + * are the same object. + *

This method should only be used to assert identity between objects. + * To assert equality between two objects or two primitive values, + * use one of the {@code assertEquals(...)} methods instead — for example, + * use {@code assertEquals(999, 999)} instead of {@code assertSame(999, 999)}. *

Fails with the supplied failure {@code message}. */ public static void assertSame(Object expected, Object actual, String message) { @@ -2843,8 +2870,14 @@ public static void assertSame(Object expected, Object actual, String message) { } /** - * Assert that {@code expected} and {@code actual} refer to the same object. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + * Assert that the {@code expected} object and the {@code actual} object + * are the same object. + *

This method should only be used to assert identity between objects. + * To assert equality between two objects or two primitive values, + * use one of the {@code assertEquals(...)} methods instead — for example, + * use {@code assertEquals(999, 999)} instead of {@code assertSame(999, 999)}. + *

If necessary, the failure message will be retrieved lazily from the supplied + * {@code messageSupplier}. */ public static void assertSame(Object expected, Object actual, Supplier messageSupplier) { AssertSame.assertSame(expected, actual, messageSupplier); @@ -2853,14 +2886,22 @@ public static void assertSame(Object expected, Object actual, Supplier m // --- assertNotSame ------------------------------------------------------- /** - * Assert that {@code expected} and {@code actual} do not refer to the same object. + * Assert that the {@code unexpected} object and the {@code actual} + * object are not the same object. + *

This method should only be used to compare the identity of two + * objects. To assert that two objects or two primitive values are not + * equal, use one of the {@code assertNotEquals(...)} methods instead. */ public static void assertNotSame(Object unexpected, Object actual) { AssertNotSame.assertNotSame(unexpected, actual); } /** - * Assert that {@code expected} and {@code actual} do not refer to the same object. + * Assert that the {@code unexpected} object and the {@code actual} + * object are not the same object. + *

This method should only be used to compare the identity of two + * objects. To assert that two objects or two primitive values are not + * equal, use one of the {@code assertNotEquals(...)} methods instead. *

Fails with the supplied failure {@code message}. */ public static void assertNotSame(Object unexpected, Object actual, String message) { @@ -2868,8 +2909,13 @@ public static void assertNotSame(Object unexpected, Object actual, String messag } /** - * Assert that {@code expected} and {@code actual} do not refer to the same object. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + * Assert that the {@code unexpected} object and the {@code actual} + * object are not the same object. + *

This method should only be used to compare the identity of two + * objects. To assert that two objects or two primitive values are not + * equal, use one of the {@code assertNotEquals(...)} methods instead. + *

If necessary, the failure message will be retrieved lazily from the supplied + * {@code messageSupplier}. */ public static void assertNotSame(Object unexpected, Object actual, Supplier messageSupplier) { AssertNotSame.assertNotSame(unexpected, actual, messageSupplier); @@ -2993,6 +3039,64 @@ public static void assertAll(String heading, Stream executables) thr // --- executable --- + /** + * Assert that execution of the supplied {@code executable} throws + * an exception of exactly the {@code expectedType} and return the exception. + * + *

If no exception is thrown, or if an exception of a different type is + * thrown, this method will fail. + * + *

If you do not want to perform additional checks on the exception instance, + * ignore the return value. + * + * @since 5.8 + */ + @API(status = STABLE, since = "5.10") + public static T assertThrowsExactly(Class expectedType, Executable executable) { + return AssertThrowsExactly.assertThrowsExactly(expectedType, executable); + } + + /** + * Assert that execution of the supplied {@code executable} throws + * an exception of exactly the {@code expectedType} and return the exception. + * + *

If no exception is thrown, or if an exception of a different type is + * thrown, this method will fail. + * + *

If you do not want to perform additional checks on the exception instance, + * ignore the return value. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.8 + */ + @API(status = STABLE, since = "5.10") + public static T assertThrowsExactly(Class expectedType, Executable executable, + String message) { + return AssertThrowsExactly.assertThrowsExactly(expectedType, executable, message); + } + + /** + * Assert that execution of the supplied {@code executable} throws + * an exception of exactly the {@code expectedType} and return the exception. + * + *

If no exception is thrown, or if an exception of a different type is + * thrown, this method will fail. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + *

If you do not want to perform additional checks on the exception instance, + * ignore the return value. + * + * @since 5.8 + */ + @API(status = STABLE, since = "5.10") + public static T assertThrowsExactly(Class expectedType, Executable executable, + Supplier messageSupplier) { + return AssertThrowsExactly.assertThrowsExactly(expectedType, executable, messageSupplier); + } + /** * Assert that execution of the supplied {@code executable} throws * an exception of the {@code expectedType} and return the exception. @@ -3001,7 +3105,7 @@ public static void assertAll(String heading, Stream executables) thr * thrown, this method will fail. * *

If you do not want to perform additional checks on the exception instance, - * simply ignore the return value. + * ignore the return value. */ public static T assertThrows(Class expectedType, Executable executable) { return AssertThrows.assertThrows(expectedType, executable); @@ -3015,7 +3119,7 @@ public static T assertThrows(Class expectedType, Execut * thrown, this method will fail. * *

If you do not want to perform additional checks on the exception instance, - * simply ignore the return value. + * ignore the return value. * *

Fails with the supplied failure {@code message}. */ @@ -3034,7 +3138,7 @@ public static T assertThrows(Class expectedType, Execut * supplied {@code messageSupplier}. * *

If you do not want to perform additional checks on the exception instance, - * simply ignore the return value. + * ignore the return value. */ public static T assertThrows(Class expectedType, Executable executable, Supplier messageSupplier) { @@ -3047,7 +3151,7 @@ public static T assertThrows(Class expectedType, Execut * Assert that execution of the supplied {@code executable} does * not throw any kind of {@linkplain Throwable exception}. * - *

Usage Note

+ *

Usage Note

*

Although any exception thrown from a test method will cause the test * to fail, there are certain use cases where it can be beneficial * to explicitly assert that an exception is not thrown for a given code @@ -3064,7 +3168,7 @@ public static void assertDoesNotThrow(Executable executable) { * Assert that execution of the supplied {@code executable} does * not throw any kind of {@linkplain Throwable exception}. * - *

Usage Note

+ *

Usage Note

*

Although any exception thrown from a test method will cause the test * to fail, there are certain use cases where it can be beneficial * to explicitly assert that an exception is not thrown for a given code @@ -3083,7 +3187,7 @@ public static void assertDoesNotThrow(Executable executable, String message) { * Assert that execution of the supplied {@code executable} does * not throw any kind of {@linkplain Throwable exception}. * - *

Usage Note

+ *

Usage Note

*

Although any exception thrown from a test method will cause the test * to fail, there are certain use cases where it can be beneficial * to explicitly assert that an exception is not thrown for a given code @@ -3107,7 +3211,7 @@ public static void assertDoesNotThrow(Executable executable, Supplier me * *

If the assertion passes, the {@code supplier}'s result will be returned. * - *

Usage Note

+ *

Usage Note

*

Although any exception thrown from a test method will cause the test * to fail, there are certain use cases where it can be beneficial * to explicitly assert that an exception is not thrown for a given code @@ -3128,7 +3232,7 @@ public static T assertDoesNotThrow(ThrowingSupplier supplier) { * *

Fails with the supplied failure {@code message}. * - *

Usage Note

+ *

Usage Note

*

Although any exception thrown from a test method will cause the test * to fail, there are certain use cases where it can be beneficial * to explicitly assert that an exception is not thrown for a given code @@ -3150,7 +3254,7 @@ public static T assertDoesNotThrow(ThrowingSupplier supplier, String mess *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * - *

Usage Note

+ *

Usage Note

*

Although any exception thrown from a test method will cause the test * to fail, there are certain use cases where it can be beneficial * to explicitly assert that an exception is not thrown for a given code @@ -3320,7 +3424,7 @@ public static T assertTimeout(Duration timeout, ThrowingSupplier supplier * @see #assertTimeout(Duration, Executable) */ public static void assertTimeoutPreemptively(Duration timeout, Executable executable) { - AssertTimeout.assertTimeoutPreemptively(timeout, executable); + AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, executable); } /** @@ -3343,7 +3447,7 @@ public static void assertTimeoutPreemptively(Duration timeout, Executable execut * @see #assertTimeout(Duration, Executable, String) */ public static void assertTimeoutPreemptively(Duration timeout, Executable executable, String message) { - AssertTimeout.assertTimeoutPreemptively(timeout, executable, message); + AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, executable, message); } /** @@ -3368,7 +3472,7 @@ public static void assertTimeoutPreemptively(Duration timeout, Executable execut */ public static void assertTimeoutPreemptively(Duration timeout, Executable executable, Supplier messageSupplier) { - AssertTimeout.assertTimeoutPreemptively(timeout, executable, messageSupplier); + AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, executable, messageSupplier); } // --- supplier - preemptively --- @@ -3393,7 +3497,7 @@ public static void assertTimeoutPreemptively(Duration timeout, Executable execut * @see #assertTimeout(Duration, Executable) */ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier) { - return AssertTimeout.assertTimeoutPreemptively(timeout, supplier); + return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier); } /** @@ -3418,7 +3522,7 @@ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier * @see #assertTimeout(Duration, Executable, String) */ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, String message) { - return AssertTimeout.assertTimeoutPreemptively(timeout, supplier, message); + return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier, message); } /** @@ -3445,7 +3549,105 @@ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier */ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, Supplier messageSupplier) { - return AssertTimeout.assertTimeoutPreemptively(timeout, supplier, messageSupplier); + return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier, messageSupplier); } + /** + * Assert that execution of the supplied {@code supplier} + * completes before the given {@code timeout} is exceeded. + * + *

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

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

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

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @see #assertTimeoutPreemptively(Duration, Executable) + * @see #assertTimeoutPreemptively(Duration, Executable, String) + * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) + * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier) + * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, String) + * @see #assertTimeout(Duration, Executable, Supplier) + */ + @API(status = INTERNAL, since = "5.9.1") + public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, + Supplier messageSupplier, TimeoutFailureFactory failureFactory) throws E { + return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier, messageSupplier, failureFactory); + } + + // --- assertInstanceOf ---------------------------------------------------- + + /** + * Assert that the supplied {@code actualValue} is an instance of the + * {@code expectedType}. + * + *

Like the {@code instanceof} operator a {@code null} value is not + * considered to be of the {@code expectedType} and does not pass the assertion. + * + * @since 5.8 + */ + @API(status = STABLE, since = "5.10") + public static T assertInstanceOf(Class expectedType, Object actualValue) { + return AssertInstanceOf.assertInstanceOf(expectedType, actualValue); + } + + /** + * Assert that the supplied {@code actualValue} is an instance of the + * {@code expectedType}. + * + *

Like the {@code instanceof} operator a {@code null} value is not + * considered to be of the {@code expectedType} and does not pass the assertion. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.8 + */ + @API(status = STABLE, since = "5.10") + public static T assertInstanceOf(Class expectedType, Object actualValue, String message) { + return AssertInstanceOf.assertInstanceOf(expectedType, actualValue, message); + } + + /** + * Assert that the supplied {@code actualValue} is an instance of the + * {@code expectedType}. + * + *

Like the {@code instanceof} operator a {@code null} value is not + * considered to be of the {@code expectedType} and does not pass the assertion. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @since 5.8 + */ + @API(status = STABLE, since = "5.10") + public static T assertInstanceOf(Class expectedType, Object actualValue, Supplier messageSupplier) { + return AssertInstanceOf.assertInstanceOf(expectedType, actualValue, messageSupplier); + } + + /** + * Factory for timeout failures. + * + * @param The type of error or exception created + * @since 5.9.1 + * @see Assertions#assertTimeoutPreemptively(Duration, ThrowingSupplier, Supplier, TimeoutFailureFactory) + */ + @API(status = INTERNAL, since = "5.9.1") + public interface TimeoutFailureFactory { + + /** + * Create a failure for the given timeout, message, and cause. + * + * @return timeout failure; never {@code null} + */ + T createTimeoutFailure(Duration timeout, Supplier messageSupplier, Throwable cause); + } } 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 b3ec15998f82..d0448a89778e 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -54,7 +54,7 @@ protected Assumptions() { /* no-op */ } - // --- assumeTrue ---------------------------------------------------- + // --- assumeTrue ---------------------------------------------------------- /** * Validate the given assumption. @@ -98,7 +98,7 @@ public static void assumeTrue(BooleanSupplier assumptionSupplier, String message */ public static void assumeTrue(boolean assumption, Supplier messageSupplier) throws TestAbortedException { if (!assumption) { - throwTestAbortedException(messageSupplier.get()); + throwAssumptionFailed(messageSupplier.get()); } } @@ -112,7 +112,7 @@ public static void assumeTrue(boolean assumption, Supplier messageSuppli */ public static void assumeTrue(boolean assumption, String message) throws TestAbortedException { if (!assumption) { - throwTestAbortedException(message); + throwAssumptionFailed(message); } } @@ -130,7 +130,7 @@ public static void assumeTrue(BooleanSupplier assumptionSupplier, Supplier messageSupplier) throws TestAbortedException { if (assumption) { - throwTestAbortedException(messageSupplier.get()); + throwAssumptionFailed(messageSupplier.get()); } } @@ -188,7 +188,7 @@ public static void assumeFalse(boolean assumption, Supplier messageSuppl */ public static void assumeFalse(boolean assumption, String message) throws TestAbortedException { if (assumption) { - throwTestAbortedException(message); + throwAssumptionFailed(message); } } @@ -206,17 +206,18 @@ public static void assumeFalse(BooleanSupplier assumptionSupplier, SupplierIf the assumption is invalid, this method does nothing. - * - *

If the {@code executable} throws an exception, it will be rethrown - * as is but {@link ExceptionUtils#throwAsUncheckedException masked} - * as an unchecked exception. + *

Unlike the other assumption methods, this method will not abort the test. + * If the assumption is invalid, this method does nothing. If the assumption is + * valid and the {@code executable} throws an exception, it will be treated like + * a regular test failure. That exception will be rethrown as is + * but {@link ExceptionUtils#throwAsUncheckedException masked} as an unchecked + * exception. * * @param assumptionSupplier the supplier of the assumption to validate * @param executable the block of code to execute if the assumption is valid @@ -230,11 +231,12 @@ public static void assumingThat(BooleanSupplier assumptionSupplier, Executable e * Execute the supplied {@link Executable}, but only if the supplied * assumption is valid. * - *

If the assumption is invalid, this method does nothing. - * - *

If the {@code executable} throws an exception, it will be rethrown - * as is but {@link ExceptionUtils#throwAsUncheckedException masked} - * as an unchecked exception. + *

Unlike the other assumption methods, this method will not abort the test. + * If the assumption is invalid, this method does nothing. If the assumption is + * valid and the {@code executable} throws an exception, it will be treated like + * a regular test failure. That exception will be rethrown as is + * but {@link ExceptionUtils#throwAsUncheckedException masked} as an unchecked + * exception. * * @param assumption the assumption to validate * @param executable the block of code to execute if the assumption is valid @@ -251,9 +253,67 @@ public static void assumingThat(boolean assumption, Executable executable) { } } - private static void throwTestAbortedException(String message) { + // --- abort --------------------------------------------------------------- + + /** + * Abort the test without a message. + * + *

Although aborting with an explicit message is recommended, this may be + * useful when maintaining legacy code. + * + *

See Javadoc for {@link #abort(String)} for an explanation of this + * method's generic return type {@code V}. + * + * @throws TestAbortedException always + * @since 5.9 + */ + @API(status = STABLE, since = "5.9") + public static V abort() { + throw new TestAbortedException(); + } + + /** + * Abort the test with the given {@code message}. + * + *

The generic return type {@code V} allows this method to be used + * directly as a single-statement lambda expression, thereby avoiding the + * need to implement a code block with an explicit return value. Since this + * method throws a {@link TestAbortedException} before its return statement, + * this method never actually returns a value to its caller. The following + * example demonstrates how this may be used in practice. + * + *

{@code
+	 * Stream.of().map(entry -> abort("assumption not met"));
+	 * }
+ * + * @param message the message to be included in the {@code TestAbortedException} + * @throws TestAbortedException always + * @since 5.9 + */ + @API(status = STABLE, since = "5.9") + public static V abort(String message) { + throw new TestAbortedException(message); + } + + /** + * Abort the test with the supplied message. + * + *

See Javadoc for {@link #abort(String)} for an explanation of this + * method's generic return type {@code V}. + * + * @param messageSupplier the supplier of the message to be included in the + * {@code TestAbortedException} + * @throws TestAbortedException always + * @since 5.9 + */ + @API(status = STABLE, since = "5.9") + public static V abort(Supplier messageSupplier) { + throw new TestAbortedException(messageSupplier.get()); + } + + private static void throwAssumptionFailed(String message) { throw new TestAbortedException( - StringUtils.isNotBlank(message) ? ("Assumption failed: " + message) : "Assumption failed"); + StringUtils.isNotBlank(message) ? "Assumption failed: " + message : "Assumption failed"); } } 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 057cddd0ae30..30eba9b73746 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -27,21 +27,28 @@ *

In contrast to {@link BeforeEach @BeforeEach} methods, {@code @BeforeAll} * methods are only executed once for a given test class. * - *

Method Signatures

+ *

Method Signatures

* - *

{@code @BeforeAll} methods must have a {@code void} return type, - * must not be {@code private}, and must be {@code static} by default. - * Consequently, {@code @BeforeAll} methods are not - * supported in {@link Nested @Nested} test classes or as interface default - * methods unless the test class is annotated with - * {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}. {@code @BeforeAll} + *

{@code @BeforeAll} methods must have a {@code void} return type and must + * be {@code static} by default. Consequently, {@code @BeforeAll} methods are + * not supported in {@link Nested @Nested} test classes or as interface + * default methods unless the test class is annotated with + * {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}. + * However, beginning with Java 16 {@code @BeforeAll} methods may be declared as + * {@code static} in {@link Nested @Nested} test classes, and the + * {@code Lifecycle.PER_CLASS} restriction no longer applies. {@code @BeforeAll} * methods may optionally declare parameters to be resolved by * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}. * - *

Inheritance and Execution Order

+ *

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

Inheritance and Execution Order

* *

{@code @BeforeAll} methods are inherited from superclasses as long as - * they are not hidden or overridden. Furthermore, + * they are not hidden (default mode with {@code static} modifier), + * overridden, or superseded (i.e., replaced based on + * signature only, irrespective of Java's visibility rules). Furthermore, * {@code @BeforeAll} methods from superclasses will be executed before * {@code @BeforeAll} methods in subclasses. * @@ -71,7 +78,7 @@ * dependencies between the {@code @BeforeAll} methods or between the * {@code @AfterAll} methods. * - *

Composition

+ *

Composition

* *

{@code @BeforeAll} may be used as a meta-annotation in order to create * a custom composed annotation that inherits the semantics of 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 4ea0738f2e59..37ca2498c889 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -26,18 +26,21 @@ * {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, * and {@code @TestTemplate} method in the current test class. * - *

Method Signatures

+ *

Method Signatures

* - *

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

{@code @BeforeEach} methods must have a {@code void} return type and must + * not be {@code static}. Using {@code private} visibility is strongly + * discouraged and will be disallowed in a future release. * They may optionally declare parameters to be resolved by * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}. * - *

Inheritance and Execution Order

+ *

Inheritance and Execution Order

* - *

{@code @BeforeEach} methods are inherited from superclasses as long as - * they are not overridden. Furthermore, {@code @BeforeEach} methods from - * superclasses will be executed before {@code @BeforeEach} methods in subclasses. + *

{@code @BeforeEach} methods are inherited from superclasses as long as they + * are not overridden or superseded (i.e., replaced based on + * signature only, irrespective of Java's visibility rules). Furthermore, + * {@code @BeforeEach} methods from superclasses will be executed before + * {@code @BeforeEach} methods in subclasses. * *

Similarly, {@code @BeforeEach} methods declared as interface default * methods are inherited as long as they are not overridden, and @@ -65,7 +68,7 @@ * no dependencies between the {@code @BeforeEach} methods or between the * {@code @AfterEach} methods. * - *

Composition

+ *

Composition

* *

{@code @BeforeEach} may be used as a meta-annotation in order to create * a custom composed annotation that inherits the semantics of 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 new file mode 100644 index 000000000000..5773e0b007c5 --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassDescriptor.java @@ -0,0 +1,87 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Optional; + +import org.apiguardian.api.API; + +/** + * {@code ClassDescriptor} encapsulates functionality for a given {@link Class}. + * + * @since 5.8 + * @see ClassOrdererContext + */ +@API(status = STABLE, since = "5.10") +public interface ClassDescriptor { + + /** + * Get the class for this descriptor. + * + * @return the class; never {@code null} + */ + Class getTestClass(); + + /** + * Get the display name for this descriptor's {@link #getTestClass() class}. + * + * @return the display name for this descriptor's class; never {@code null} + * or blank + */ + String getDisplayName(); + + /** + * Determine if an annotation of {@code annotationType} is either + * present or meta-present on the {@link Class} for + * this descriptor. + * + * @param annotationType the annotation type to search for; never {@code null} + * @return {@code true} if the annotation is present or meta-present + * @see #findAnnotation(Class) + * @see #findRepeatableAnnotations(Class) + */ + boolean isAnnotated(Class annotationType); + + /** + * Find the first annotation of {@code annotationType} that is either + * present or meta-present on the {@link Class} for + * this descriptor. + * + * @param the annotation type + * @param annotationType the annotation type to search for; never {@code null} + * @return an {@code Optional} containing the annotation; never {@code null} but + * potentially empty + * @see #isAnnotated(Class) + * @see #findRepeatableAnnotations(Class) + */ + Optional findAnnotation(Class annotationType); + + /** + * Find all repeatable {@linkplain Annotation annotations} of + * {@code annotationType} that are either present or + * meta-present on the {@link Class} for this descriptor. + * + * @param the annotation type + * @param annotationType the repeatable annotation type to search for; never + * {@code null} + * @return the list of all such annotations found; neither {@code null} nor + * mutable, but potentially empty + * @see #isAnnotated(Class) + * @see #findAnnotation(Class) + * @see java.lang.annotation.Repeatable + */ + List findRepeatableAnnotations(Class annotationType); + +} 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 new file mode 100644 index 000000000000..2bc852b412a5 --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrderer.java @@ -0,0 +1,270 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static java.util.Comparator.comparingInt; +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.Collections; +import java.util.Comparator; +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; + +/** + * {@code ClassOrderer} defines the API for ordering top-level test classes and + * {@link Nested @Nested} test classes. + * + *

In this context, the term "test class" refers to any class containing methods + * annotated with {@code @Test}, {@code @RepeatedTest}, {@code @ParameterizedTest}, + * {@code @TestFactory}, or {@code @TestTemplate}. + * + *

Top-level test classes will be ordered relative to each other; whereas, + * {@code @Nested} test classes will be ordered relative to other {@code @Nested} + * test classes sharing the same {@linkplain Class#getEnclosingClass() enclosing + * class}. + * + *

A {@link ClassOrderer} can be configured globally for the entire + * test suite via the {@value #DEFAULT_ORDER_PROPERTY_NAME} configuration + * parameter (see the User Guide for details) or locally for + * {@link Nested @Nested} test classes via the {@link TestClassOrder @TestClassOrder} + * annotation. + * + *

Built-in Implementations

+ * + *

JUnit Jupiter provides the following built-in {@code ClassOrderer} + * implementations. + * + *

+ * + * @since 5.8 + * @see TestClassOrder + * @see ClassOrdererContext + * @see #orderClasses(ClassOrdererContext) + * @see MethodOrderer + */ +@API(status = STABLE, since = "5.10") +public interface ClassOrderer { + + /** + * Property name used to set the default class orderer class name: {@value} + * + *

Supported Values

+ * + *

Supported values include fully qualified class names for types that + * implement {@link org.junit.jupiter.api.ClassOrderer}. + * + *

If not specified, test classes are not ordered unless test classes are + * annotated with {@link TestClassOrder @TestClassOrder}. + * + * @since 5.8 + */ + @API(status = STABLE, since = "5.9") + String DEFAULT_ORDER_PROPERTY_NAME = "junit.jupiter.testclass.order.default"; + + /** + * Order the classes encapsulated in the supplied {@link ClassOrdererContext}. + * + *

The classes to order or sort are made indirectly available via + * {@link ClassOrdererContext#getClassDescriptors()}. Since this method + * has a {@code void} return type, the list of class descriptors must be + * modified directly. + * + *

For example, a simplified implementation of the {@link ClassOrderer.Random} + * {@code ClassOrderer} might look like the following. + * + *

+	 * 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} + */ + void orderClasses(ClassOrdererContext context); + + /** + * {@code ClassOrderer} that sorts classes alphanumerically based on their + * fully qualified names using {@link String#compareTo(String)}. + */ + class ClassName implements ClassOrderer { + + public ClassName() { + } + + /** + * Sort the classes encapsulated in the supplied + * {@link ClassOrdererContext} alphanumerically based on their fully + * qualified names. + */ + @Override + public void orderClasses(ClassOrdererContext context) { + context.getClassDescriptors().sort(comparator); + } + + private static final Comparator comparator = Comparator.comparing( + descriptor -> descriptor.getTestClass().getName()); + } + + /** + * {@code ClassOrderer} that sorts classes alphanumerically based on their + * display names using {@link String#compareTo(String)} + */ + class DisplayName implements ClassOrderer { + + public DisplayName() { + } + + /** + * Sort the classes encapsulated in the supplied + * {@link ClassOrdererContext} alphanumerically based on their display + * names. + */ + @Override + public void orderClasses(ClassOrdererContext context) { + context.getClassDescriptors().sort(comparator); + } + + private static final Comparator comparator = Comparator.comparing( + ClassDescriptor::getDisplayName); + } + + /** + * {@code ClassOrderer} that sorts classes based on the {@link Order @Order} + * annotation. + * + *

Any classes that are assigned the same order value will be sorted + * arbitrarily adjacent to each other. + * + *

Any classes not annotated with {@code @Order} will be assigned the + * {@linkplain Order#DEFAULT default order} value which will effectively cause them + * to appear at the end of the sorted list, unless certain classes are assigned + * an explicit order value greater than the default order value. Any classes + * assigned an explicit order value greater than the default order value will + * appear after non-annotated classes in the sorted list. + */ + class OrderAnnotation implements ClassOrderer { + + public OrderAnnotation() { + } + + /** + * Sort the classes encapsulated in the supplied + * {@link ClassOrdererContext} based on the {@link Order @Order} + * annotation. + */ + @Override + public void orderClasses(ClassOrdererContext context) { + context.getClassDescriptors().sort(comparingInt(OrderAnnotation::getOrder)); + } + + private static int getOrder(ClassDescriptor descriptor) { + return descriptor.findAnnotation(Order.class).map(Order::value).orElse(Order.DEFAULT); + } + } + + /** + * {@code ClassOrderer} that orders classes pseudo-randomly. + * + *

Custom Seed

+ * + *

By default, the random seed used for ordering classes is the + * value returned by {@link System#nanoTime()} during static initialization + * of this class. In order to support repeatable builds, the value of the + * default random seed is logged at {@code CONFIG} level. In addition, a + * custom seed (potentially the default seed from the previous test plan + * execution) may be specified via the {@value Random#RANDOM_SEED_PROPERTY_NAME} + * configuration parameter which can be supplied via the {@code Launcher} + * API, build tools (e.g., Gradle and Maven), a JVM system property, or the JUnit + * Platform configuration file (i.e., a file named {@code junit-platform.properties} + * in the root of the class path). Consult the User Guide for further information. + * + * @see Random#RANDOM_SEED_PROPERTY_NAME + * @see java.util.Random + */ + class Random implements ClassOrderer { + + private static final Logger logger = LoggerFactory.getLogger(Random.class); + + /** + * Default seed, which is generated during initialization of this class + * via {@link System#nanoTime()} for reproducibility of tests. + */ + private static final long DEFAULT_SEED; + + static { + DEFAULT_SEED = System.nanoTime(); + logger.config(() -> "ClassOrderer.Random default seed: " + DEFAULT_SEED); + } + + /** + * Property name used to set the random seed used by this + * {@code ClassOrderer}: {@value} + * + *

The same property is used by {@link MethodOrderer.Random} for + * consistency between the two random orderers. + * + *

Supported Values

+ * + *

Supported values include any string that can be converted to a + * {@link Long} via {@link Long#valueOf(String)}. + * + *

If not specified or if the specified value cannot be converted to + * a {@link Long}, the default random seed will be used (see the + * {@linkplain Random class-level Javadoc} for details). + * + * @see MethodOrderer.Random + */ + public static final String RANDOM_SEED_PROPERTY_NAME = MethodOrderer.Random.RANDOM_SEED_PROPERTY_NAME; + + public Random() { + } + + /** + * Order the classes encapsulated in the supplied + * {@link ClassOrdererContext} pseudo-randomly. + */ + @Override + public void orderClasses(ClassOrdererContext context) { + Collections.shuffle(context.getClassDescriptors(), + new java.util.Random(getCustomSeed(context).orElse(DEFAULT_SEED))); + } + + private Optional getCustomSeed(ClassOrdererContext context) { + return context.getConfigurationParameter(RANDOM_SEED_PROPERTY_NAME).map(configurationParameter -> { + Long seed = null; + try { + seed = Long.valueOf(configurationParameter); + logger.config( + () -> String.format("Using custom seed for configuration parameter [%s] with value [%s].", + RANDOM_SEED_PROPERTY_NAME, configurationParameter)); + } + catch (NumberFormatException ex) { + logger.warn(ex, + () -> String.format( + "Failed to convert configuration parameter [%s] with value [%s] to a long. " + + "Using default seed [%s] as fallback.", + RANDOM_SEED_PROPERTY_NAME, configurationParameter, DEFAULT_SEED)); + } + return seed; + }); + } + } + +} 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 new file mode 100644 index 000000000000..45f60c12679c --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrdererContext.java @@ -0,0 +1,56 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.List; +import java.util.Optional; + +import org.apiguardian.api.API; + +/** + * {@code ClassOrdererContext} encapsulates the context in which + * a {@link ClassOrderer} will be invoked. + * + * @since 5.8 + * @see ClassOrderer + * @see ClassDescriptor + */ +@API(status = STABLE, since = "5.10") +public interface ClassOrdererContext { + + /** + * Get the list of {@linkplain ClassDescriptor class descriptors} to + * order. + * + * @return the list of class descriptors; never {@code null} + */ + List getClassDescriptors(); + + /** + * Get the configuration parameter stored under the specified {@code key}. + * + *

If no such key is present in the {@code ConfigurationParameters} for + * the JUnit Platform, an attempt will be made to look up the value as a + * JVM system property. If no such system property exists, an attempt will + * be made to look up the value in the JUnit Platform properties file. + * + * @param key the key to look up; never {@code null} or blank + * @return an {@code Optional} containing the value; never {@code null} + * but potentially empty + * + * @see System#getProperty(String) + * @see org.junit.platform.engine.ConfigurationParameters + */ + Optional getConfigurationParameter(String key); + +} 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 6b784a958494..9a7717053491 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -48,6 +48,8 @@ * @see org.junit.jupiter.api.condition.DisabledForJreRange * @see org.junit.jupiter.api.condition.EnabledOnOs * @see org.junit.jupiter.api.condition.DisabledOnOs + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.extension.ExecutionCondition */ @Target({ ElementType.TYPE, ElementType.METHOD }) 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 9f6e96937ba7..e99ee34c75fc 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which 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 237d9b5e1002..bb0e1bf9f70a 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -25,9 +25,20 @@ * {@code @DisplayNameGeneration} is used to declare a custom display name * generator for the annotated test class. * + *

This annotation is inherited from superclasses and implemented + * interfaces. It is also inherited from {@linkplain Class#getEnclosingClass() + * enclosing classes} for {@link Nested @Nested} test classes. + * + *

As an alternative to {@code @DisplayNameGeneration}, a global + * {@link DisplayNameGenerator} can be configured for the entire test suite via + * the {@value DisplayNameGenerator#DEFAULT_GENERATOR_PROPERTY_NAME} configuration parameter. See + * the User Guide for details. Note, however, that a {@code @DisplayNameGeneration} + * declaration always overrides a global {@code DisplayNameGenerator}. + * * @since 5.4 * @see DisplayName * @see DisplayNameGenerator + * @see IndicativeSentencesGeneration */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) 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 42bb9821057c..22c6ec62551d 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -10,12 +10,13 @@ package org.junit.jupiter.api; -import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; -import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; +import static org.junit.platform.commons.support.ModifierSupport.isStatic; +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; import java.lang.reflect.Method; import java.util.Optional; +import java.util.function.Predicate; import org.apiguardian.api.API; import org.junit.platform.commons.util.ClassUtils; @@ -31,6 +32,20 @@ * *

Concrete implementations must have a default constructor. * + *

A {@link DisplayNameGenerator} can be configured globally for the + * entire test suite via the {@value #DEFAULT_GENERATOR_PROPERTY_NAME} + * configuration parameter (see the User Guide for details) or locally + * for a test class via the {@link DisplayNameGeneration @DisplayNameGeneration} + * annotation. + * + *

Built-in Implementations

+ *
    + *
  • {@link Standard}
  • + *
  • {@link Simple}
  • + *
  • {@link ReplaceUnderscores}
  • + *
  • {@link IndicativeSentences}
  • + *
+ * * @since 5.4 * @see DisplayName @DisplayName * @see DisplayNameGeneration @DisplayNameGeneration @@ -38,32 +53,55 @@ @API(status = STABLE, since = "5.7") public interface DisplayNameGenerator { + /** + * Property name used to set the default display name generator class name: + * {@value} + * + *

Supported Values

+ * + *

Supported values include fully qualified class names for types that + * implement {@link DisplayNameGenerator}. + * + *

If not specified, the default is + * {@link DisplayNameGenerator.Standard}. + * + * @since 5.5 + */ + @API(status = STABLE, since = "5.9") + String DEFAULT_GENERATOR_PROPERTY_NAME = "junit.jupiter.displayname.generator.default"; + /** * 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. + * * @param testClass the class to generate a name for; never {@code null} - * @return the display name for the class; never {@code null} or blank + * @return the display name for the class; never blank */ String generateDisplayNameForClass(Class testClass); /** * 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. + * * @param nestedClass the class to generate a name for; never {@code null} - * @return the display name for the nested class; never {@code null} or blank + * @return the display name for the nested class; never blank */ String generateDisplayNameForNestedClass(Class nestedClass); /** * Generate a display name for the given method. * + *

If it 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 * example, when a test method is inherited from a superclass. * * @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 {@code null} or blank + * @return the display name for the test; never blank */ String generateDisplayNameForMethod(Class testClass, Method testMethod); @@ -92,6 +130,9 @@ class Standard implements DisplayNameGenerator { static final DisplayNameGenerator INSTANCE = new Standard(); + public Standard() { + } + @Override public String generateDisplayNameForClass(Class testClass) { String name = testClass.getName(); @@ -122,6 +163,9 @@ class Simple extends Standard { static final DisplayNameGenerator INSTANCE = new Simple(); + public Simple() { + } + @Override public String generateDisplayNameForMethod(Class testClass, Method testMethod) { String displayName = testMethod.getName(); @@ -148,6 +192,9 @@ class ReplaceUnderscores extends Simple { static final DisplayNameGenerator INSTANCE = new ReplaceUnderscores(); + public ReplaceUnderscores() { + } + @Override public String generateDisplayNameForClass(Class testClass) { return replaceUnderscores(super.generateDisplayNameForClass(testClass)); @@ -172,22 +219,26 @@ private static String replaceUnderscores(String name) { /** * {@code DisplayNameGenerator} that generates complete sentences. * - *

This implements the functionality of {@link DisplayNameGenerator} - * by generating complete sentences display names, these - * sentences are divided with a separator, and the generator and separator - * can be customisable by using the {@link IndicativeSentencesGeneration} - * interface as annotation. + *

This generator generates display names that build up complete sentences + * by concatenating the names of the test and the enclosing classes. The + * sentence fragments are concatenated using a separator. The separator and + * the display name generator for individual sentence fragments can be configured + * via the {@link IndicativeSentencesGeneration @IndicativeSentencesGeneration} + * annotation. * * @since 5.7 */ - @API(status = EXPERIMENTAL, since = "5.7") + @API(status = STABLE, since = "5.10") class IndicativeSentences implements DisplayNameGenerator { static final DisplayNameGenerator INSTANCE = new IndicativeSentences(); + public IndicativeSentences() { + } + @Override public String generateDisplayNameForClass(Class testClass) { - return getGeneratorForIndicativeSentence(testClass).generateDisplayNameForClass(testClass); + return getGeneratorFor(testClass).generateDisplayNameForClass(testClass); } @Override @@ -197,94 +248,109 @@ public String generateDisplayNameForNestedClass(Class nestedClass) { @Override public String generateDisplayNameForMethod(Class testClass, Method testMethod) { - return getSentenceBeginning(testClass) + getSentenceSeparator(testClass) - + getGeneratorForIndicativeSentence(testClass).generateDisplayNameForMethod(testClass, testMethod); + return getSentenceBeginning(testClass) + getFragmentSeparator(testClass) + + getGeneratorFor(testClass).generateDisplayNameForMethod(testClass, testMethod); } private String getSentenceBeginning(Class testClass) { - Class enclosingParent = testClass.getEnclosingClass(); - Optional displayName = findAnnotation(testClass, DisplayName.class); - Optional displayNameGeneration = findAnnotation(testClass, - DisplayNameGeneration.class); - - if (enclosingParent == null || displayNameGeneration.isPresent()) { - return displayName.map(DisplayName::value).orElseGet(() -> generateDisplayNameForClass(testClass)); + Class enclosingClass = testClass.getEnclosingClass(); + boolean topLevelTestClass = (enclosingClass == null || isStatic(testClass)); + Optional displayName = findAnnotation(testClass, DisplayName.class)// + .map(DisplayName::value).map(String::trim); + + if (topLevelTestClass) { + if (displayName.isPresent()) { + return displayName.get(); + } + Class generatorClass = findDisplayNameGeneration(testClass)// + .map(DisplayNameGeneration::value)// + .filter(not(IndicativeSentences.class))// + .orElse(null); + if (generatorClass != null) { + return getDisplayNameGenerator(generatorClass).generateDisplayNameForClass(testClass); + } + return generateDisplayNameForClass(testClass); } - String separator = getSentenceSeparator(testClass); - String sentenceBeginning = getSentenceBeginning(enclosingParent); - return displayName.map(name -> sentenceBeginning + separator + name.value()) // - .orElseGet(() -> sentenceBeginning + separator - + getGeneratorForIndicativeSentence(testClass).generateDisplayNameForNestedClass( - testClass)); + + // Only build prefix based on the enclosing class if the enclosing + // class is also configured to use the IndicativeSentences generator. + boolean buildPrefix = findDisplayNameGeneration(enclosingClass)// + .map(DisplayNameGeneration::value)// + .filter(IndicativeSentences.class::equals)// + .isPresent(); + + String prefix = (buildPrefix ? getSentenceBeginning(enclosingClass) + getFragmentSeparator(testClass) : ""); + + return prefix + displayName.orElseGet( + () -> getGeneratorFor(testClass).generateDisplayNameForNestedClass(testClass)); } /** - * Gets the separator for {@link IndicativeSentencesGeneration} when extracting the - * annotation from {@code IndicativeSentencesGeneration}, if it doesn't find it, - * then search for the parent classes, if no separator is found use @code{", "} by default. + * Get the sentence fragment separator. + * + *

If {@link IndicativeSentencesGeneration @IndicativeSentencesGeneration} + * is present (searching enclosing classes if not found locally), the + * configured {@link IndicativeSentencesGeneration#separator() separator} + * will be used. Otherwise, {@link IndicativeSentencesGeneration#DEFAULT_SEPARATOR} + * will be used. * - * @param testClass Class to get Indicative sentence annotation separator either custom or default - * @return the indicative sentence separator + * @param testClass the test class to search on for {@code @IndicativeSentencesGeneration} + * @return the sentence fragment separator */ - private String getSentenceSeparator(Class testClass) { - Optional indicativeSentencesGeneration = getIndicativeSentencesGeneration( - testClass); - if (indicativeSentencesGeneration.isPresent()) { - if (indicativeSentencesGeneration.get().separator().equals("")) { - return IndicativeSentencesGeneration.DEFAULT_SEPARATOR; - } - return indicativeSentencesGeneration.get().separator(); - } - - return IndicativeSentencesGeneration.DEFAULT_SEPARATOR; + private static String getFragmentSeparator(Class testClass) { + return findIndicativeSentencesGeneration(testClass)// + .map(IndicativeSentencesGeneration::separator)// + .orElse(IndicativeSentencesGeneration.DEFAULT_SEPARATOR); } /** - * Gets the generator for {@link IndicativeSentencesGeneration} when extracting the - * annotation from {@code IndicativeSentencesGeneration}, if it doesn't find it, - * then search for the parent classes, if no generator value is found use - * {@link Standard} by default. + * Get the display name generator to use for the supplied test class. * - * @param testClass Class to get Indicative sentence generator either custom or default - * @return the {@code DisplayNameGenerator} instance to use in indicative sentences generator + *

If {@link IndicativeSentencesGeneration @IndicativeSentencesGeneration} + * is present (searching enclosing classes if not found locally), the + * configured {@link IndicativeSentencesGeneration#generator() generator} + * will be used. Otherwise, {@link IndicativeSentencesGeneration#DEFAULT_GENERATOR} + * will be used. + * + * @param testClass the test class to search on for {@code @IndicativeSentencesGeneration} + * @return the {@code DisplayNameGenerator} instance to use */ - private DisplayNameGenerator getGeneratorForIndicativeSentence(Class testClass) { - Optional indicativeSentencesGeneration = getIndicativeSentencesGeneration( - testClass); - if (indicativeSentencesGeneration.isPresent()) { - DisplayNameGenerator displayNameGenerator = getDisplayNameGenerator( - indicativeSentencesGeneration.get().generator()); - if (displayNameGenerator.getClass() == IndicativeSentences.class) { - return getDisplayNameGenerator(IndicativeSentencesGeneration.DEFAULT_GENERATOR); - } - return displayNameGenerator; - } - - return getDisplayNameGenerator(IndicativeSentencesGeneration.DEFAULT_GENERATOR); + private static DisplayNameGenerator getGeneratorFor(Class testClass) { + return findIndicativeSentencesGeneration(testClass)// + .map(IndicativeSentencesGeneration::generator)// + .filter(not(IndicativeSentences.class))// + .map(DisplayNameGenerator::getDisplayNameGenerator)// + .orElseGet(() -> getDisplayNameGenerator(IndicativeSentencesGeneration.DEFAULT_GENERATOR)); } /** - * Finds the {@code IndicativeSentencesGeneration} annotation that is present, - * meta-present or if it doesn't find it, then search for the enclosing - * parent classes, if no annotation is found returns empty. + * 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. * - * @param testClass the test class to find the {@code IndicativeSentencesGeneration} - * annotation - * @return the optional annotation retrieved from the test class. + * @param testClass the test class on which to find the annotation; never {@code null} + * @return an {@code Optional} containing the annotation, potentially empty if not found */ - private Optional getIndicativeSentencesGeneration(Class testClass) { - Optional indicativeSentencesGeneration = findAnnotation(testClass, - IndicativeSentencesGeneration.class); + private static Optional findDisplayNameGeneration(Class testClass) { + return findAnnotation(testClass, DisplayNameGeneration.class, true); + } - if (indicativeSentencesGeneration.isPresent()) { - return indicativeSentencesGeneration; - } - if (testClass.getEnclosingClass() != null) { - return getIndicativeSentencesGeneration(testClass.getEnclosingClass()); - } + /** + * 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. + * + * @param testClass the test class on which to find the annotation; 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); + } - return Optional.empty(); + private static Predicate> not(Class clazz) { + return ((Predicate>) clazz::equals).negate(); } + } /** 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 09ba38d6edeb..dcc48186c5ec 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which 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 3dab77739a5b..c1a151779f7f 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which 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 b4b1ef0c350c..935951663d48 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -155,6 +155,77 @@ public static Stream stream(Stream inputStream, .map(input -> dynamicTest(displayNameGenerator.apply(input), () -> testExecutor.accept(input))); } + /** + * Generate a stream of dynamic tests based on the given generator and test + * executor. + * + *

Use this method when the set of dynamic tests is nondeterministic in + * nature or when the input comes from an existing {@link Iterator}. See + * {@link #stream(Stream, ThrowingConsumer)} as an alternative. + * + *

The given {@code inputGenerator} is responsible for generating + * input values and display names. A {@link DynamicTest} will be added to + * the resulting stream for each dynamically generated input value, + * using the given {@code testExecutor}. + * + * @param inputGenerator an {@code Iterator} with {@code Named} values + * that serves as a dynamic input generator; never {@code null} + * @param testExecutor a consumer that executes a test based on an input + * value; never {@code null} + * @param the type of input generated by the {@code inputGenerator} + * and used by the {@code testExecutor} + * @return a stream of dynamic tests based on the given generator and + * executor; never {@code null} + * @since 5.8 + * + * @see #dynamicTest(String, Executable) + * @see #stream(Stream, ThrowingConsumer) + * @see Named + */ + @API(status = MAINTAINED, since = "5.8") + public static Stream stream(Iterator> inputGenerator, + ThrowingConsumer testExecutor) { + Preconditions.notNull(inputGenerator, "inputGenerator must not be null"); + + return stream(StreamSupport.stream(spliteratorUnknownSize(inputGenerator, ORDERED), false), testExecutor); + } + + /** + * Generate a stream of dynamic tests based on the given input stream and + * test executor. + * + *

Use this method when the set of dynamic tests is nondeterministic in + * nature or when the input comes from an existing {@link Stream}. See + * {@link #stream(Iterator, ThrowingConsumer)} as an alternative. + * + *

The given {@code inputStream} is responsible for supplying input values + * and display names. A {@link DynamicTest} will be added to the resulting stream for + * each dynamically supplied input value, using the given {@code testExecutor}. + * + * @param inputStream a {@code Stream} that supplies dynamic {@code Named} + * input values; never {@code null} + * @param testExecutor a consumer that executes a test based on an input + * value; never {@code null} + * @param the type of input supplied by the {@code inputStream} + * and used by the {@code displayNameGenerator} and {@code testExecutor} + * @return a stream of dynamic tests based on the given generator and + * executor; never {@code null} + * @since 5.8 + * + * @see #dynamicTest(String, Executable) + * @see #stream(Iterator, ThrowingConsumer) + * @see Named + */ + @API(status = MAINTAINED, since = "5.8") + public static Stream stream(Stream> inputStream, + ThrowingConsumer testExecutor) { + Preconditions.notNull(inputStream, "inputStream must not be null"); + Preconditions.notNull(testExecutor, "testExecutor must not be null"); + + return inputStream // + .map(input -> dynamicTest(input.getName(), () -> testExecutor.accept(input.getPayload()))); + } + private final Executable executable; private DynamicTest(String displayName, URI testSourceUri, Executable executable) { 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 3a5372eeb482..97ab817eb4b3 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -10,7 +10,7 @@ package org.junit.jupiter.api; -import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; @@ -20,35 +20,52 @@ import java.lang.annotation.Target; import org.apiguardian.api.API; +import org.junit.jupiter.api.DisplayNameGenerator.IndicativeSentences; /** - * {@code @IndicativeSentencesGeneration} is used to declare a custom parameters - * by {@code IndicativeSentences}, if this notation has some not declared - * parameters, it will use the default values instead. + * {@code @IndicativeSentencesGeneration} is used to register the + * {@link IndicativeSentences} display name generator and configure it. + * + *

The {@link #separator} for sentence fragments and the display name + * {@link #generator} for sentence fragments are configurable. If this annotation + * is declared without any attributes — for example, + * {@code @IndicativeSentencesGeneration} or {@code @IndicativeSentencesGeneration()} + * — the default configuration will be used. + * + *

This annotation is inherited from superclasses and implemented + * interfaces. It is also inherited from {@linkplain Class#getEnclosingClass() + * enclosing classes} for {@link Nested @Nested} test classes. * * @since 5.7 * @see DisplayName * @see DisplayNameGenerator * @see DisplayNameGenerator.IndicativeSentences + * @see DisplayNameGeneration */ -@DisplayNameGeneration(DisplayNameGenerator.IndicativeSentences.class) +@DisplayNameGeneration(IndicativeSentences.class) @Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited -@API(status = EXPERIMENTAL, since = "5.7") +@API(status = STABLE, since = "5.10") public @interface IndicativeSentencesGeneration { String DEFAULT_SEPARATOR = ", "; + Class DEFAULT_GENERATOR = DisplayNameGenerator.Standard.class; /** - * Custom separator for indicative sentences generator. + * Custom separator for sentence fragments. + * + *

Defaults to {@value #DEFAULT_SEPARATOR}. */ - String separator() default ""; + String separator() default DEFAULT_SEPARATOR; /** - * Custom display name generator. + * Custom display name generator to use for sentence fragments. + * + *

Defaults to {@link DisplayNameGenerator.Standard}. */ Class generator() default DisplayNameGenerator.Standard.class; + } 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 ce0db501e3d6..49f62ab2760b 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -10,7 +10,6 @@ package org.junit.jupiter.api; -import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Annotation; @@ -43,7 +42,7 @@ public interface MethodDescriptor { * or blank * @since 5.7 */ - @API(status = EXPERIMENTAL, since = "5.7") + @API(status = STABLE, since = "5.10") String getDisplayName(); /** 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 9e57b0b28dcd..c8269f4a705f 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -12,7 +12,6 @@ import static java.util.Comparator.comparingInt; 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; @@ -34,7 +33,12 @@ * {@code @Test}, {@code @RepeatedTest}, {@code @ParameterizedTest}, * {@code @TestFactory}, or {@code @TestTemplate}. * - *

Built-in Implementations

+ *

A {@link MethodOrderer} can be configured globally for the entire + * test suite via the {@value #DEFAULT_ORDER_PROPERTY_NAME} configuration + * parameter (see the User Guide for details) or locally for a test + * class via the {@link TestMethodOrder @TestMethodOrder} annotation. + * + *

Built-in Implementations

* *

JUnit Jupiter provides the following built-in {@code MethodOrderer} * implementations. @@ -49,10 +53,27 @@ * @see TestMethodOrder * @see MethodOrdererContext * @see #orderMethods(MethodOrdererContext) + * @see ClassOrderer */ @API(status = STABLE, since = "5.7") public interface MethodOrderer { + /** + * Property name used to set the default method orderer class name: {@value} + * + *

Supported Values

+ * + *

Supported values include fully qualified class names for types that + * implement {@link org.junit.jupiter.api.MethodOrderer}. + * + *

If not specified, test methods will be ordered using an algorithm that + * is deterministic but intentionally non-obvious. + * + * @since 5.7 + */ + @API(status = STABLE, since = "5.9") + String DEFAULT_ORDER_PROPERTY_NAME = "junit.jupiter.testmethod.order.default"; + /** * Order the methods encapsulated in the supplied {@link MethodOrdererContext}. * @@ -71,7 +92,7 @@ public interface MethodOrderer { * * * @param context the {@code MethodOrdererContext} containing the - * {@link MethodDescriptor method descriptors} to order; never {@code null} + * {@linkplain MethodDescriptor method descriptors} to order; never {@code null} * @see #getDefaultExecutionMode() */ void orderMethods(MethodOrdererContext context); @@ -112,18 +133,20 @@ default Optional getDefaultExecutionMode() { * *

If two methods have the same name, {@code String} representations of * their formal parameter lists will be used as a fallback for comparing the - * methods.

- *

This class has been deprecated in favor of - * {@link MethodOrderer.MethodName} and will be removed in 6.0

+ * methods. * * @since 5.4 - * @deprecated Please use {@link MethodOrderer.MethodName} instead. + * @deprecated as of JUnit Jupiter 5.7 in favor of {@link MethodOrderer.MethodName}; + * to be removed in 6.0 */ @API(status = DEPRECATED, since = "5.7") @Deprecated class Alphanumeric extends MethodName { + public Alphanumeric() { + } } + /** * {@code MethodOrderer} that sorts methods alphanumerically based on their * names using {@link String#compareTo(String)}. @@ -131,11 +154,15 @@ class Alphanumeric extends MethodName { *

If two methods have the same name, {@code String} representations of * their formal parameter lists will be used as a fallback for comparing the * methods. + * * @since 5.7 */ - @API(status = EXPERIMENTAL, since = "5.7") + @API(status = STABLE, since = "5.10") class MethodName implements MethodOrderer { + public MethodName() { + } + /** * Sort the methods encapsulated in the supplied * {@link MethodOrdererContext} alphanumerically based on their names @@ -161,9 +188,12 @@ private static String parameterList(Method method) { * * @since 5.7 */ - @API(status = EXPERIMENTAL, since = "5.7") + @API(status = STABLE, since = "5.10") class DisplayName implements MethodOrderer { + public DisplayName() { + } + /** * Sort the methods encapsulated in the supplied * {@link MethodOrdererContext} alphanumerically based on their display @@ -186,15 +216,17 @@ public void orderMethods(MethodOrdererContext context) { * arbitrarily adjacent to each other. * *

Any methods not annotated with {@code @Order} will be assigned the - * {@link org.junit.jupiter.api.Order#DEFAULT default order} value which will - * effectively cause them to appear at the end of the sorted list, unless - * certain methods are assigned an explicit order value greater than the default - * order value. Any methods assigned an explicit order value greater than the - * default order value will appear after non-annotated methods in the sorted - * list. + * {@linkplain Order#DEFAULT default order} value which will effectively cause them + * to appear at the end of the sorted list, unless certain methods are assigned + * an explicit order value greater than the default order value. Any methods + * assigned an explicit order value greater than the default order value will + * appear after non-annotated methods in the sorted list. */ class OrderAnnotation implements MethodOrderer { + public OrderAnnotation() { + } + /** * Sort the methods encapsulated in the supplied * {@link MethodOrdererContext} based on the {@link Order @Order} @@ -213,19 +245,18 @@ private static int getOrder(MethodDescriptor descriptor) { /** * {@code MethodOrderer} that orders methods pseudo-randomly. * - *

Custom Seed

+ *

Custom Seed

* *

By default, the random seed used for ordering methods is the * value returned by {@link System#nanoTime()} during static initialization * of this class. In order to support repeatable builds, the value of the - * default random seed is logged at {@code INFO} level. In addition, a + * default random seed is logged at {@code CONFIG} level. In addition, a * custom seed (potentially the default seed from the previous test plan - * execution) may be specified via the {@link Random#RANDOM_SEED_PROPERTY_NAME - * junit.jupiter.execution.order.random.seed} configuration parameter - * which can be supplied via the {@code Launcher} API, build tools (e.g., - * Gradle and Maven), a JVM system property, or the JUnit Platform configuration - * file (i.e., a file named {@code junit-platform.properties} in the root of - * the class path). Consult the User Guide for further information. + * execution) may be specified via the {@value ClassOrderer.Random#RANDOM_SEED_PROPERTY_NAME} + * configuration parameter which can be supplied via the {@code Launcher} + * API, build tools (e.g., Gradle and Maven), a JVM system property, or the JUnit + * Platform configuration file (i.e., a file named {@code junit-platform.properties} + * in the root of the class path). Consult the User Guide for further information. * * @see Random#RANDOM_SEED_PROPERTY_NAME * @see java.util.Random @@ -242,14 +273,17 @@ class Random implements MethodOrderer { static { DEFAULT_SEED = System.nanoTime(); - logger.info(() -> "MethodOrderer.Random default seed: " + DEFAULT_SEED); + logger.config(() -> "MethodOrderer.Random default seed: " + DEFAULT_SEED); } /** * Property name used to set the random seed used by this * {@code MethodOrderer}: {@value} * - *

Supported Values

+ *

The same property is used by {@link ClassOrderer.Random} for + * consistency between the two random orderers. + * + *

Supported Values

* *

Supported values include any string that can be converted to a * {@link Long} via {@link Long#valueOf(String)}. @@ -257,9 +291,14 @@ class Random implements MethodOrderer { *

If not specified or if the specified value cannot be converted to * a {@link Long}, the default random seed will be used (see the * {@linkplain Random class-level Javadoc} for details). + * + * @see ClassOrderer.Random */ public static final String RANDOM_SEED_PROPERTY_NAME = "junit.jupiter.execution.order.random.seed"; + public Random() { + } + /** * Order the methods encapsulated in the supplied * {@link MethodOrdererContext} pseudo-randomly. 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 db9290c92408..cb585b709416 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which 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 new file mode 100644 index 000000000000..6dee03e4a1ea --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java @@ -0,0 +1,94 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; + +/** + * {@code Named} is a container that associates a name with a given payload. + * + * @param the type of the payload + * + * @since 5.8 + */ +@API(status = STABLE, since = "5.8") +public interface Named { + + /** + * Factory method for creating an instance of {@code Named} based on a + * {@code name} and a {@code payload}. + * + * @param name the name associated with the payload; never {@code null} or + * blank + * @param payload the object that serves as the payload; may be {@code null} + * depending on the use case + * @param the type of the payload + * @return an instance of {@code Named}; never {@code null} + * @see #named(String, java.lang.Object) + */ + static Named of(String name, T payload) { + Preconditions.notBlank(name, "name must not be null or blank"); + + return new Named() { + @Override + public String getName() { + return name; + } + + @Override + public T getPayload() { + return payload; + } + + @Override + public String toString() { + return name; + } + }; + } + + /** + * Factory method for creating an instance of {@code Named} based on a + * {@code name} and a {@code payload}. + * + *

This method is an alias for {@link Named#of} and is + * intended to be used when statically imported — for example, via: + * {@code import static org.junit.jupiter.api.Named.named;} + * + * @param name the name associated with the payload; never {@code null} or + * blank + * @param payload the object that serves as the payload; may be {@code null} + * depending on the use case + * @param the type of the payload + * @return an instance of {@code Named}; never {@code null} + */ + static Named named(String name, T payload) { + return of(name, payload); + } + + /** + * Get the name of the payload. + * + * @return the name of the payload; never {@code null} or blank + */ + String getName(); + + /** + * Get the payload. + * + * @return the payload; may be {@code null} depending on the use case + */ + T getPayload(); + +} 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 e7f2f4c0dbb0..1627010af1a0 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-2020 the original author or authors. + * Copyright 2015-2023 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which @@ -28,7 +28,10 @@ * enclosing class}. The enclosing class may be a top-level test class or * another {@code @Nested} test class, and nesting can be arbitrarily deep. * - *

Test Instance Lifecycle

+ *

{@code @Nested} test classes may be ordered via + * {@link TestClassOrder @TestClassOrder} or a global {@link ClassOrderer}. + * + *

Test Instance Lifecycle

* *