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/.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/actions/main-build/action.yml b/.github/actions/main-build/action.yml
new file mode 100644
index 000000000000..4b51887efcd8
--- /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@v2
+ 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..cdd702d0a0ab
--- /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@v2
+ 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
+ -PenablePredictiveTestSelection=${{ 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..8dc70985295b
--- /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@v2
+ 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..29a1155627e6
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,17 @@
+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: "/"
+ allow:
+ - dependency-name: "com.gradle*"
+ registries:
+ - gradle-plugin-portal
+ schedule:
+ interval: "weekly"
+ open-pull-requests-limit: 10
diff --git a/.github/stale.yml b/.github/stale.yml
new file mode 100644
index 000000000000..2ef85c0b65a5
--- /dev/null
+++ b/.github/stale.yml
@@ -0,0 +1,69 @@
+# Configuration for probot-stale - https://github.com/probot/stale
+# Configuration options apply to both Issues and Pull Requests.
+# We configure those individually to match our workflow (see `pulls:` and `issues:`)
+
+pulls:
+ # Number of days of inactivity before a Pull Request becomes stale
+ daysUntilStale: 60
+
+ # Number of days of inactivity before a Pull Request with the stale label is closed.
+ # Set to false to disable. If disabled, Pull Request still need to be closed manually, but will remain marked as stale.
+ daysUntilClose: 21
+
+ # Comment to post when marking as stale. Set to `false` to disable
+ markComment: >
+ This pull request has been automatically marked as stale because it has not had recent activity.
+ Given the limited bandwidth of the team, it will be closed if no further activity occurs.
+ If you intend to work on this pull request, please reopen the PR.
+ Thank you for your contributions.
+ # Comment to post when closing a stale Pull Request.
+ closeComment: >
+ This pull request has been automatically closed due to inactivity.
+ If you are still interested in contributing this, please ensure that
+ it is rebased against the latest branch (usually `main`), all review
+ comments have been addressed and the build is passing.
+issues:
+ daysUntilStale: 365
+
+ # Number of days of inactivity before an Issue with the stale label is closed.
+ # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
+ daysUntilClose: 21
+
+ # Comment to post when marking as stale. Set to `false` to disable
+ markComment: >
+ This issue has been automatically marked as stale because it has not had recent activity.
+ Given the limited bandwidth of the team, it will be automatically closed if no further
+ activity occurs.
+ Thank you for your contribution.
+ # Comment to post when closing a stale Issue.
+ closeComment: >
+ This issue has been automatically closed due to inactivity. If you have a good use case for this
+ feature, please feel free to reopen the issue.
+
+# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
+#onlyLabels: []
+
+# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
+exemptLabels: []
+
+# Set to true to ignore issues in a project (defaults to false)
+exemptProjects: false
+
+# Set to true to ignore issues in a milestone (defaults to false)
+exemptMilestones: false
+
+# Set to true to ignore issues with an assignee (defaults to false)
+exemptAssignees: false
+
+# Label to use when marking as stale
+staleLabel: "status: stale"
+
+# Comment to post when removing the stale label.
+# unmarkComment: >
+# Your comment here.
+
+# Limit the number of actions per hour, from 1-30. Default is 30
+limitPerRun: 30
+
+# Limit to only `issues` or `pulls`
+# only: issues
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 000000000000..3d2321519793
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,38 @@
+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
+ strategy:
+ fail-fast: false
+ matrix:
+ language: ['java', 'javascript']
+ steps:
+ - name: Check out repository
+ uses: actions/checkout@v2
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v1
+ with:
+ languages: ${{ matrix.language }}
+ - 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@v1
diff --git a/.github/workflows/cross-version.yml b/.github/workflows/cross-version.yml
index 05e5a3d5c08e..fcea6360c17a 100644
--- a/.github/workflows/cross-version.yml
+++ b/.github/workflows/cross-version.yml
@@ -10,40 +10,43 @@ on:
- '*'
env:
+ ORG_GRADLE_PROJECT_enableTestDistribution: true
ORG_GRADLE_PROJECT_junitBuildCacheUsername: ${{ secrets.BUILD_CACHE_USERNAME }}
ORG_GRADLE_PROJECT_junitBuildCachePassword: ${{ secrets.BUILD_CACHE_PASSWORD }}
+ GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }}
jobs:
openjdk:
strategy:
matrix:
- jdk: [14, 15, 16]
+ jdk: [18, 19, 20]
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@v2
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@v2
+ with:
+ name: Test Distribution trace files
+ path: '**/build/test-results/*/trace.json'
diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml
index 405a2b306592..ce98fe801382 100644
--- a/.github/workflows/gradle-wrapper-validation.yml
+++ b/.github/workflows/gradle-wrapper-validation.yml
@@ -6,5 +6,9 @@ jobs:
name: "Validation"
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - uses: gradle/wrapper-validation-action@v1
+ - name: Check out repository
+ uses: actions/checkout@v2
+ 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..3dadaf6b4ad6
--- /dev/null
+++ b/.github/workflows/issue-labels.yml
@@ -0,0 +1,14 @@
+name: Label new issues
+on:
+ issues:
+ types: ['opened']
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: Renato66/auto-label@69b3cbe79438b2079aed0a49474275d3e572cae5 # or v2
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ labels-synonyms: '{"3rd-party: Eclipse":["Eclipse"],"3rd-party: Gradle":["Gradle"],"3rd-party: IntelliJ IDEA":["IDEA","IntelliJ"],"3rd-party: Maven Surefire":["Failsafe","Maven","Surefire"],"3rd-party: Pioneer":["pioneer"],"component: Groovy":["Groovy"],"component: Test Kit":["Test Kit","TestKit"]}'
+ labels-not-allowed: '["dependencies","status: blocked","status: declined","status: duplicate","status: in progress","status: invalid","status: stale","status: superseded","status: team discussion","status: waiting-for-feedback","status: waiting-for-interest","status: works-as-designed","up-for-grabs"]'
+ default-labels: '["status: new"]'
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 2a4b5da0d7e0..f7a7ef835937 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -10,153 +10,60 @@ on:
- '*'
env:
+ ORG_GRADLE_PROJECT_enableTestDistribution: true
ORG_GRADLE_PROJECT_junitBuildCacheUsername: ${{ secrets.BUILD_CACHE_USERNAME }}
ORG_GRADLE_PROJECT_junitBuildCachePassword: ${{ 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@v2
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: |
+ -PenableJaCoCo
+ build
+ jacocoRootReport
+ prepareDocsForUploadToGhPages
+ - name: Upload to Codecov.io
+ uses: codecov/codecov-action@v2
+
+ 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@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
- 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
+ - name: Check out repository
+ uses: actions/checkout@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
- 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,26 +71,17 @@ 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
+ - name: Check out repository
+ uses: actions/checkout@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
- 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
@@ -191,24 +89,19 @@ jobs:
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@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
+ 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: ./src/publishDocumentationSnapshotOnlyIfNecessary.sh
diff --git a/.github/workflows/reproducible-build.yml b/.github/workflows/reproducible-build.yml
index e48b0c5ff1b2..3a9d1fe3267f 100644
--- a/.github/workflows/reproducible-build.yml
+++ b/.github/workflows/reproducible-build.yml
@@ -9,30 +9,22 @@ on:
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@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
+ 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: |
diff --git a/.gitignore b/.gitignore
index edd18cb0c5e2..e5f0af67bde0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,5 +29,6 @@ gradle-app.setting
*.graphml
coverage.db*
.metadata
+/.sdkmanrc
checksums*
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index f77f7d2313e4..fb8322da259f 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -1,5 +1,8 @@
+
+
+
+
\ 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..e2a44b38b935 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -87,6 +87,11 @@ 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.
@@ -127,8 +132,8 @@ See [`ExtensionContext`](junit-jupiter-api/src/main/java/org/junit/jupiter/api/e
### 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
diff --git a/README.md b/README.md
index 8b6a8761c39d..ecf74ef5e4d5 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ This repository is the home of the next generation of JUnit, _JUnit 5_.
## Latest Releases
-- General Availability (GA): [JUnit 5.7.0](https://github.com/junit-team/junit5/releases/tag/r5.7.0) (September 13, 2020)
+- General Availability (GA): [JUnit 5.9.1](https://github.com/junit-team/junit5/releases/tag/r5.9.1) (September 20, 2022)
- 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
@@ -46,17 +46,24 @@ A code coverage report can also be generated locally via the [Gradle Wrapper] by
executing `gradlew -PenableJaCoCo 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.
+[](https://ge.junit.org/scans)
+
+JUnit 5 utilizes [Gradle Enterprise](https://gradle.com/) for _Build Scans_, _Build Cache_, and _Test Distribution_.
+
+The latest Build Scans are available on [ge.junit.org](https://ge.junit.org/). Currently,
+only core team members can publish Build Scans and use Test Distribution on that server.
+You can, however, publish a Build Scan to [scans.gradle.com](https://scans.gradle.com/) by
+using the `--scan` parameter explicitly.
+
+The remote Build Cache is enabled by default for everyone so that local builds can reuse
+task outputs from previous CI builds.
## Building from Source
-You need [JDK 11] to build JUnit 5.
+You need [JDK 17] to build JUnit 5. [Gradle toolchains] are used to detect and
+potentially download additional JDKs for compilation and test execution.
All modules can be _built_ with the [Gradle Wrapper] using the following command.
@@ -78,60 +85,23 @@ consumption in other projects via the following command.
## 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
+Consult the [Dependency Metadata] section of the [User Guide] for a list of all artifacts
+of the JUnit Platform, JUnit Jupiter, and JUnit Vintage.
-- **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`)
-
-### 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://adoptium.net/archive.html?variant=openjdk17&jvmVariant=hotspot
[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..0e51f7ff7479
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,12 @@
+# Security Policy
+
+## Supported Versions
+
+| Version | Supported |
+| ------- | ------------------ |
+| 5.7.x | :white_check_mark: |
+| < 5.7 | :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..cce12d0ae060 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,262 +1,92 @@
-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")
+ id("io.github.gradle-nexus.publish-plugin")
+ `base-conventions`
+ `build-metadata`
+ `dependency-update-check`
+ `jacoco-conventions`
+ `temp-maven-repo`
}
-apply(from = "gradle/build-scan-user-data.gradle")
-
-buildScan {
- if (project.hasProperty("javaHome")) {
- value("Custom Java home", project.property("javaHome") as String)
- }
-}
-
-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")) {
+description = "JUnit 5"
- val sourceDateEpoch = System.getenv("SOURCE_DATE_EPOCH").toLong()
-
- Instant.ofEpochSecond(sourceDateEpoch).atOffset(ZoneOffset.UTC)
-
- } else {
- OffsetDateTime.now()
- }
-}
-
-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 = file("src/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")
-
- if (enableJaCoCo) {
- apply(plugin = "jacoco")
- configure {
- toolVersion = versions["jacoco"]
- }
- }
+val modularProjects by extra(mavenizedProjects - listOf(projects.junitPlatformConsoleStandalone.dependencyProject))
+nexusPublishing {
+ packageGroup.set("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("buildSrc/build/generated-sources/**")
}
-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()
+val jacocoTestProjects = listOf(
+ projects.junitJupiterEngine,
+ projects.junitJupiterMigrationsupport,
+ projects.junitJupiterParams,
+ projects.junitVintageEngine,
+ projects.platformTests
+).map { it.dependencyProject }
+val jacocoClassesDir by extra(file("$buildDir/jacoco/classes"))
+
+val jacocoRootReport by tasks.registering(JacocoReport::class) {
+ modularProjects.forEach {
+ dependsOn(it.tasks.named("extractJar"))
+ it.pluginManager.withPlugin("java") {
+ sourceDirectories.from(it.the()["main"].allSource.srcDirs)
}
}
-
- 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")
- }
- }
- }
- }
- }
+ classDirectories.from(files(jacocoClassesDir))
+ reports {
+ html.required.set(true)
+ xml.required.set(true)
+ csv.required.set(false)
}
+}
- 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
- }
- }
+afterEvaluate {
+ jacocoRootReport {
+ jacocoTestProjects.forEach {
+ executionData(it.tasks.withType().map { task -> task.the().destinationFile })
+ dependsOn(it.tasks.withType())
}
}
}
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index fd74ea278a13..9fd96fb5833b 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -1,16 +1,32 @@
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
plugins {
`kotlin-dsl`
+ id("com.github.ben-manes.versions") version "0.39.0"
}
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")
+ implementation("biz.aQute.bnd:biz.aQute.bnd.gradle:6.3.1")
+ implementation("com.diffplug.spotless:spotless-plugin-gradle:6.0.0")
+ implementation("com.github.ben-manes:gradle-versions-plugin:0.39.0")
+ implementation("gradle.plugin.com.github.johnrengelman:shadow:7.1.2")
+ implementation("org.gradle:test-retry-gradle-plugin:1.4.1")
+ compileOnly("com.gradle:gradle-enterprise-gradle-plugin:3.11.1") // keep in sync with root settings.gradle.kts
+}
+
+tasks {
+ withType().configureEach {
+ options.release.set(8)
+ }
+ withType().configureEach {
+ kotlinOptions {
+ jvmTarget = "1.8"
+ allWarningsAsErrors = true
+ }
+ }
}
diff --git a/buildSrc/src/main/kotlin/JavaLibraryExtension.kt b/buildSrc/src/main/kotlin/JavaLibraryExtension.kt
index 7c020478d6c3..834374bf06c6 100644
--- a/buildSrc/src/main/kotlin/JavaLibraryExtension.kt
+++ b/buildSrc/src/main/kotlin/JavaLibraryExtension.kt
@@ -1,7 +1,7 @@
import org.gradle.api.JavaVersion
-@Suppress("UnstableApiUsage")
open class JavaLibraryExtension {
- var mainJavaVersion: JavaVersion = Versions.jvmTarget
- var testJavaVersion: JavaVersion = JavaVersion.VERSION_11
+ var mainJavaVersion: JavaVersion = JavaVersion.VERSION_1_8
+ var testJavaVersion: JavaVersion = JavaVersion.VERSION_17
+ var configureRelease: Boolean = true
}
diff --git a/buildSrc/src/main/kotlin/ProjectExtensions.kt b/buildSrc/src/main/kotlin/ProjectExtensions.kt
index d6d2f4215049..fc8a515977e7 100644
--- a/buildSrc/src/main/kotlin/ProjectExtensions.kt
+++ b/buildSrc/src/main/kotlin/ProjectExtensions.kt
@@ -1,12 +1,19 @@
import org.gradle.api.Project
-import org.gradle.kotlin.dsl.extra
-import org.gradle.kotlin.dsl.provideDelegate
+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('-', '.')
-val Project.versions: Versions
- get() {
- var versions: Versions? by rootProject.extra
- return versions ?: Versions(rootProject).also { versions = it }
- }
+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/buildSrc/src/main/kotlin/TaskExtensions.kt b/buildSrc/src/main/kotlin/TaskExtensions.kt
new file mode 100644
index 000000000000..7ad4e7ab46cb
--- /dev/null
+++ b/buildSrc/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/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/base-conventions.gradle.kts b/buildSrc/src/main/kotlin/base-conventions.gradle.kts
new file mode 100644
index 000000000000..0c4c4c5f7a29
--- /dev/null
+++ b/buildSrc/src/main/kotlin/base-conventions.gradle.kts
@@ -0,0 +1,6 @@
+plugins {
+ eclipse
+ idea
+ id("java-toolchain-conventions")
+ id("spotless-conventions")
+}
diff --git a/buildSrc/src/main/kotlin/build-metadata.gradle.kts b/buildSrc/src/main/kotlin/build-metadata.gradle.kts
new file mode 100644
index 000000000000..5d189fba246e
--- /dev/null
+++ b/buildSrc/src/main/kotlin/build-metadata.gradle.kts
@@ -0,0 +1,30 @@
+import org.codehaus.groovy.runtime.ProcessGroovyMethods
+import java.time.Instant
+import java.time.OffsetDateTime
+import java.time.ZoneOffset
+import java.time.format.DateTimeFormatter
+
+val buildTimeAndDate: OffsetDateTime 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()
+ }
+}
+
+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 {
+ ProcessGroovyMethods.getText(ProcessGroovyMethods.execute("git rev-parse --verify HEAD"))
+}
+val builtByValue by extra { project.findProperty("builtBy") ?: project.property("defaultBuiltBy") }
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/dependency-update-check.gradle.kts b/buildSrc/src/main/kotlin/dependency-update-check.gradle.kts
new file mode 100644
index 000000000000..a62a03a12169
--- /dev/null
+++ b/buildSrc/src/main/kotlin/dependency-update-check.gradle.kts
@@ -0,0 +1,21 @@
+import org.gradle.kotlin.dsl.resolutionStrategy
+
+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/buildSrc/src/main/kotlin/jacoco-conventions.gradle.kts b/buildSrc/src/main/kotlin/jacoco-conventions.gradle.kts
new file mode 100644
index 000000000000..7e0f5a1410f6
--- /dev/null
+++ b/buildSrc/src/main/kotlin/jacoco-conventions.gradle.kts
@@ -0,0 +1,60 @@
+import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
+
+plugins {
+ jacoco
+}
+
+val enableJaCoCo = project.hasProperty("enableJaCoCo")
+
+jacoco {
+ toolVersion = requiredVersionFromLibs("jacoco")
+}
+
+tasks {
+ withType().configureEach {
+ configure {
+ isEnabled = enableJaCoCo
+ }
+ }
+ withType().configureEach {
+ enabled = enableJaCoCo
+ }
+}
+
+pluginManager.withPlugin("java") {
+
+ val jacocoClassesDir: File by rootProject.extra
+
+ val jar by tasks.existing(Jar::class)
+
+ val extractJar by tasks.registering(Copy::class) {
+ from(zipTree(jar.map { it.archiveFile }))
+ into(jacocoClassesDir)
+ include("**/*.class")
+ // don't version-specific classes of MR JARs
+ exclude("META-INF/versions/**")
+ includeEmptyDirs = false
+ onlyIf { jar.get().enabled }
+ }
+
+ jar {
+ finalizedBy(extractJar)
+ }
+
+ tasks.named("jacocoTestReport") {
+ enabled = false
+ }
+
+ pluginManager.withPlugin("com.github.johnrengelman.shadow") {
+
+ val shadowJar by tasks.existing(ShadowJar::class) {
+ finalizedBy(extractJar)
+ }
+ extractJar {
+ from(zipTree(shadowJar.map { it.archiveFile }))
+ // don't report coverage for shadowed classes
+ exclude("**/shadow/**")
+ onlyIf { shadowJar.get().enabled }
+ }
+ }
+}
diff --git a/buildSrc/src/main/kotlin/java-library-conventions.gradle.kts b/buildSrc/src/main/kotlin/java-library-conventions.gradle.kts
index 6ebed2f22a2e..66e07ffbe00d 100644
--- a/buildSrc/src/main/kotlin/java-library-conventions.gradle.kts
+++ b/buildSrc/src/main/kotlin/java-library-conventions.gradle.kts
@@ -1,9 +1,13 @@
+import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
+import org.gradle.api.tasks.PathSensitivity.RELATIVE
+
plugins {
`java-library`
eclipse
idea
checkstyle
- id("custom-java-home")
+ id("base-conventions")
+ id("jacoco-conventions")
}
val mavenizedProjects: List by rootProject.extra
@@ -13,25 +17,12 @@ 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 javaVersion = JavaVersion.current()
-configurations {
- compileClasspath.get().extendsFrom(internal)
- runtimeClasspath.get().extendsFrom(internal)
- testCompileClasspath.get().extendsFrom(internal)
- testRuntimeClasspath.get().extendsFrom(internal)
-}
-
eclipse {
jdt {
file {
@@ -44,6 +35,10 @@ eclipse {
}
}
+java {
+ modularity.inferModulePath.set(false)
+}
+
if (project in mavenizedProjects) {
apply(plugin = "publishing-conventions")
@@ -61,13 +56,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 +87,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 {
@@ -127,12 +116,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")
+ }
}
}
@@ -143,20 +145,20 @@ val allMainClasses by tasks.registering {
val compileModule by tasks.registering(JavaCompile::class) {
dependsOn(allMainClasses)
source = fileTree(moduleSourceDir)
- destinationDir = moduleOutputDir
+ destinationDirectory.set(moduleOutputDir)
sourceCompatibility = "9"
targetCompatibility = "9"
classpath = files()
options.release.set(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) })
+ modularity.inferModulePath.set(false)
}
tasks.withType().configureEach {
@@ -165,7 +167,7 @@ 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") {
include("module-info.class")
@@ -213,18 +215,29 @@ 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 ModulePathArgumentProvider : CommandLineArgumentProvider, Named {
+ @get:CompileClasspath
+ val modulePath: Provider = configurations.compileClasspath
+ override fun asArguments() = listOf(
+ "--module-path",
+ modulePath.get().asPath,
+ "--module-source-path",
+ files(modularProjects.map { "${it.projectDir}/src/module" }).asPath
+ )
+ @Internal
+ override fun getName() = "module-path"
}
-inner class PatchModuleArgumentProvider(it: Project) : CommandLineArgumentProvider {
+inner class PatchModuleArgumentProvider(it: Project) : CommandLineArgumentProvider, Named {
- @get:Input val module: String = it.javaModuleName
+ @get:Input
+ val module: String = it.javaModuleName
- @get:Input val patch: Provider = provider {
+ @get:InputFiles
+ @get:PathSensitive(RELATIVE)
+ val patch: Provider = provider {
if (it == project)
- files(sourceSets.matching { it.name.startsWith("main") }.map { it.output }) + configurations.compileClasspath.get()
+ files(sourceSets.matching { it.name.startsWith("main") }.map { it.output })
else
files(it.sourceSets["main"].java.srcDirs)
}
@@ -236,6 +249,9 @@ inner class PatchModuleArgumentProvider(it: Project) : CommandLineArgumentProvid
}
return listOf("--patch-module", "$module=$path")
}
+
+ @Internal
+ override fun getName() = "patch-module($module)"
}
afterEvaluate {
@@ -253,18 +269,30 @@ afterEvaluate {
}
tasks {
compileJava {
- options.release.set(extension.mainJavaVersion.majorVersion.toInt())
+ if (extension.configureRelease) {
+ options.release.set(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.set(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,7 +300,7 @@ afterEvaluate {
}
checkstyle {
- toolVersion = versions["checkstyle"]
+ toolVersion = requiredVersionFromLibs("checkstyle")
configDirectory.set(rootProject.file("src/checkstyle"))
}
@@ -286,7 +314,10 @@ tasks {
}
pluginManager.withPlugin("java-test-fixtures") {
- tasks.named("checkstyleTestFixtures").configure {
+ tasks.named("checkstyleTestFixtures") {
configFile = rootProject.file("src/checkstyle/checkstyleTest.xml")
}
+ tasks.named("compileTestFixturesJava") {
+ options.release.set(extension.testJavaVersion.majorVersion.toInt())
+ }
}
diff --git a/buildSrc/src/main/kotlin/java-multi-release-sources.gradle.kts b/buildSrc/src/main/kotlin/java-multi-release-sources.gradle.kts
index 1843acb7c395..9b3155ab764c 100644
--- a/buildSrc/src/main/kotlin/java-multi-release-sources.gradle.kts
+++ b/buildSrc/src/main/kotlin/java-multi-release-sources.gradle.kts
@@ -4,38 +4,40 @@ plugins {
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"))
+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(mainRelease9.get().compileClasspathConfigurationName).configure {
- extendsFrom(configurations.compileClasspath.get())
-}
-
-tasks {
- named("allMainClasses").configure {
- dependsOn(mainRelease9.get().classesTaskName)
+ configurations.named(sourceSet.get().compileClasspathConfigurationName).configure {
+ extendsFrom(configurations.compileClasspath.get())
}
- named(mainRelease9.get().compileJavaTaskName).configure {
- options.release.set(9)
- }
+ tasks {
- named("checkstyle${mainRelease9.name.capitalize()}").configure {
- configFile = rootProject.file("src/checkstyle/checkstyleMain.xml")
- }
+ named("allMainClasses").configure {
+ dependsOn(sourceSet.get().classesTaskName)
+ }
- if (project in mavenizedProjects) {
- javadoc {
- source(mainRelease9.get().allJava)
+ named(sourceSet.get().compileJavaTaskName).configure {
+ options.release.set(javaVersion)
}
- named("sourcesJar").configure {
- from(mainRelease9.get().allSource)
+
+ named("checkstyle${sourceSet.name.capitalize()}").configure {
+ configFile = rootProject.file("src/checkstyle/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/buildSrc/src/main/kotlin/java-repackage-jars.gradle.kts
index e36bea40b0fa..a000989b0327 100644
--- a/buildSrc/src/main/kotlin/java-repackage-jars.gradle.kts
+++ b/buildSrc/src/main/kotlin/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/buildSrc/src/main/kotlin/java-toolchain-conventions.gradle.kts b/buildSrc/src/main/kotlin/java-toolchain-conventions.gradle.kts
new file mode 100644
index 000000000000..c85e77e260f2
--- /dev/null
+++ b/buildSrc/src/main/kotlin/java-toolchain-conventions.gradle.kts
@@ -0,0 +1,28 @@
+val javaToolchainVersion: String? by project
+val defaultLanguageVersion = JavaLanguageVersion.of(17)
+val javaLanguageVersion = javaToolchainVersion?.let { JavaLanguageVersion.of(it) } ?: defaultLanguageVersion
+
+project.pluginManager.withPlugin("java") {
+ val extension = the()
+ val javaToolchainService = the()
+ extension.toolchain.languageVersion.set(javaLanguageVersion)
+ tasks.withType().configureEach {
+ javaLauncher.set(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.set(defaultLanguageVersion)
+ })
+ }
+}
diff --git a/buildSrc/src/main/kotlin/junit4-compatibility.gradle.kts b/buildSrc/src/main/kotlin/junit4-compatibility.gradle.kts
index deb9fdced557..89ca169b699e 100644
--- a/buildSrc/src/main/kotlin/junit4-compatibility.gradle.kts
+++ b/buildSrc/src/main/kotlin/junit4-compatibility.gradle.kts
@@ -13,7 +13,8 @@ dependencies {
}
}
pluginManager.withPlugin("osgi-conventions") {
- "osgiVerification"("org.apache.servicemix.bundles:org.apache.servicemix.bundles.junit:4.13_1")
+ val junit4Osgi = requiredVersionFromLibs("junit4Osgi")
+ "osgiVerification"("org.apache.servicemix.bundles:org.apache.servicemix.bundles.junit:${junit4Osgi}")
}
}
diff --git a/buildSrc/src/main/kotlin/kotlin-library-conventions.gradle.kts b/buildSrc/src/main/kotlin/kotlin-library-conventions.gradle.kts
index 3342cd189501..08402295ff04 100644
--- a/buildSrc/src/main/kotlin/kotlin-library-conventions.gradle.kts
+++ b/buildSrc/src/main/kotlin/kotlin-library-conventions.gradle.kts
@@ -7,8 +7,21 @@ plugins {
tasks.withType().configureEach {
kotlinOptions {
- jvmTarget = Versions.jvmTarget.toString()
apiVersion = "1.3"
languageVersion = "1.3"
+ allWarningsAsErrors = false
+ }
+}
+
+afterEvaluate {
+ val extension = project.the()
+ tasks {
+ withType().configureEach {
+ kotlinOptions.jvmTarget = extension.mainJavaVersion.toString()
+ }
+ named("compileTestKotlin") {
+ // The Kotlin compiler does not yet support JDK 17 and later (see https://kotlinlang.org/docs/compiler-reference.html#jvm-target-version)
+ kotlinOptions.jvmTarget = minOf(JavaVersion.VERSION_16, extension.testJavaVersion).toString()
+ }
}
}
diff --git a/buildSrc/src/main/kotlin/osgi-conventions.gradle.kts b/buildSrc/src/main/kotlin/osgi-conventions.gradle.kts
index 8b8f1705dc7f..2ad2fb578893 100644
--- a/buildSrc/src/main/kotlin/osgi-conventions.gradle.kts
+++ b/buildSrc/src/main/kotlin/osgi-conventions.gradle.kts
@@ -1,33 +1,45 @@
-import aQute.bnd.gradle.BundleTaskConvention
-import aQute.bnd.gradle.FileSetRepositoryConvention
+import aQute.bnd.gradle.BundleTaskExtension
import aQute.bnd.gradle.Resolve
plugins {
`java-library`
}
+val importAPIGuardian = "org.apiguardian.*;resolution:=\"optional\""
+
// This task enhances `jar` and `shadowJar` tasks with the bnd
-// `BundleTaskConvention` convention which allows for generating OSGi
+// `BundleTaskExtension` extension 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)
+ val bte = extensions.create(BundleTaskExtension.NAME, this)
+
+ extra["importAPIGuardian"] = importAPIGuardian
// 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("""
+ bte.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: \
- !org.apiguardian.api,\
+ ${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 packages, but enough modules do to make it a default.
+ # 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.
@@ -47,31 +59,27 @@ tasks.withType().matching {
# Instruct the APIGuardianAnnotations how to operate.
# See https://bnd.bndtools.org/instructions/export-apiguardian.html
- -export-apiguardian: *;version=${project.version}
+ -export-apiguardian: *;version=${'$'}{versionmask;===;${'$'}{version_cleanup;${'$'}{task.archiveVersion}}}
""")
- // Add the convention to the jar task
- convention.plugins["bundle"] = btc
-
- doLast {
- // Do the actual work putting OSGi stuff in the jar.
- btc.buildBundle()
- }
+ // Do the actual work putting OSGi stuff in the jar.
+ doLast(bte.buildAction())
}
-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
+ setOutputFile(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,sun.misc")
+ 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.creating {
@@ -82,31 +90,19 @@ val osgiVerification by configurations.creating {
// 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)
+ bndrun.fileProvider(osgiProperties.map{ it.outputFile })
+ outputBndrun.set(layout.buildDirectory.file("resolvedOSGiProperties.bndrun"))
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)
- }
+ // 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
index 6f58ce7453af..c4e28d5bb3a9 100644
--- a/buildSrc/src/main/kotlin/publishing-conventions.gradle.kts
+++ b/buildSrc/src/main/kotlin/publishing-conventions.gradle.kts
@@ -1,26 +1,49 @@
-import java.time.Duration
-
plugins {
`maven-publish`
signing
- id("de.marcphilipp.nexus-publish")
+ id("base-conventions")
}
val isSnapshot = project.version.toString().contains("SNAPSHOT")
val isContinuousIntegrationEnvironment = System.getenv("CI")?.toBoolean() ?: false
-val isJitPackEnvironment = System.getenv("JITPACK")?.toBoolean() ?: false
+
+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(tasks.build)
+ 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 || isContinuousIntegrationEnvironment || isJitPackEnvironment)
+ isRequired = !(isSnapshot || isContinuousIntegrationEnvironment)
}
tasks.withType().configureEach {
@@ -29,15 +52,6 @@ tasks.withType().configureEach {
}
}
-nexusPublishing {
- connectTimeout.set(Duration.ofMinutes(2))
- clientTimeout.set(Duration.ofMinutes(2))
- packageGroup.set("org.junit")
- repositories {
- sonatype()
- }
-}
-
publishing {
publications {
create("maven") {
diff --git a/buildSrc/src/main/kotlin/shadow-conventions.gradle.kts b/buildSrc/src/main/kotlin/shadow-conventions.gradle.kts
index 0a5d8db24510..44c5bacec51d 100644
--- a/buildSrc/src/main/kotlin/shadow-conventions.gradle.kts
+++ b/buildSrc/src/main/kotlin/shadow-conventions.gradle.kts
@@ -3,9 +3,7 @@ plugins {
id("com.github.johnrengelman.shadow")
}
-val shadowed by configurations.creating {
- extendsFrom(configurations["internal"])
-}
+val shadowed by configurations.creating
configurations.forEach { configuration ->
configuration.outgoing.apply {
@@ -49,6 +47,8 @@ tasks {
shadowJar {
configurations = listOf(shadowed)
exclude("META-INF/maven/**")
+ excludes.remove("module-info.class")
+ archiveClassifier.set("")
}
jar {
dependsOn(shadowJar)
diff --git a/buildSrc/src/main/kotlin/spotless-conventions.gradle.kts b/buildSrc/src/main/kotlin/spotless-conventions.gradle.kts
new file mode 100644
index 000000000000..9f82998c135c
--- /dev/null
+++ b/buildSrc/src/main/kotlin/spotless-conventions.gradle.kts
@@ -0,0 +1,49 @@
+import org.gradle.kotlin.dsl.extra
+import org.gradle.kotlin.dsl.provideDelegate
+
+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")
+ trimTrailingWhitespace()
+ endWithNewline()
+ }
+
+ pluginManager.withPlugin("java") {
+
+ val importOrderConfigFile = rootProject.file("src/eclipse/junit-eclipse.importorder")
+ val javaFormatterConfigFile = rootProject.file("src/eclipse/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()
+ }
+ }
+}
diff --git a/buildSrc/src/main/kotlin/temp-maven-repo.gradle.kts b/buildSrc/src/main/kotlin/temp-maven-repo.gradle.kts
new file mode 100644
index 000000000000..d180a3a85628
--- /dev/null
+++ b/buildSrc/src/main/kotlin/temp-maven-repo.gradle.kts
@@ -0,0 +1,30 @@
+import org.gradle.api.publish.PublishingExtension
+import org.gradle.api.publish.maven.tasks.PublishToMavenRepository
+import org.gradle.kotlin.dsl.*
+
+val tempRepoName by extra("temp")
+val tempRepoDir by extra(file("$buildDir/repo"))
+
+val clearTempRepoDir by tasks.registering {
+ doFirst {
+ tempRepoDir.deleteRecursively()
+ }
+}
+
+subprojects {
+ pluginManager.withPlugin("maven-publish") {
+ configure() {
+ repositories {
+ maven {
+ name = tempRepoName
+ url = uri(tempRepoDir)
+ }
+ }
+ }
+ tasks.withType().configureEach {
+ if (name.endsWith("To${tempRepoName.capitalize()}Repository")) {
+ dependsOn(clearTempRepoDir)
+ }
+ }
+ }
+}
diff --git a/buildSrc/src/main/kotlin/testing-conventions.gradle.kts b/buildSrc/src/main/kotlin/testing-conventions.gradle.kts
index 57a97e90247d..a1ee801a05b7 100644
--- a/buildSrc/src/main/kotlin/testing-conventions.gradle.kts
+++ b/buildSrc/src/main/kotlin/testing-conventions.gradle.kts
@@ -1,5 +1,6 @@
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 {
id("org.gradle.test-retry")
@@ -14,30 +15,69 @@ tasks.withType().configureEach {
events = setOf(FAILED)
exceptionFormat = FULL
}
+ val isCiServer = System.getenv("CI") != null
retry {
- maxRetries.set(2)
+ maxRetries.set(providers.gradleProperty("retries").map(String::toInt).orElse(if (isCiServer) 2 else 0))
+ }
+ distribution {
+ enabled.convention(providers.gradleProperty("enableTestDistribution")
+ .map(String::toBoolean)
+ .map { enabled -> enabled && (!isCiServer || System.getenv("GRADLE_ENTERPRISE_ACCESS_KEY").isNotBlank()) }
+ .orElse(false))
+ maxLocalExecutors.set(providers.gradleProperty("testDistribution.maxLocalExecutors").map(String::toInt).orElse(1))
+ maxRemoteExecutors.set(providers.gradleProperty("testDistribution.maxRemoteExecutors").map(String::toInt))
+ if (isCiServer) {
+ when {
+ OperatingSystem.current().isLinux -> requirements.add("os=linux")
+ OperatingSystem.current().isWindows -> requirements.add("os=windows")
+ OperatingSystem.current().isMacOsX -> requirements.add("os=macos")
+ }
+ }
+ }
+ predictiveSelection {
+ enabled.set(providers.gradleProperty("enablePredictiveTestSelection").map(String::toBoolean).orElse(true))
}
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 (project.hasProperty("enableJFR")) {
+ jvmArgs(
+ "-XX:+UnlockDiagnosticVMOptions",
+ "-XX:+DebugNonSafepoints",
+ "-XX:StartFlightRecording=filename=${reports.junitXml.outputLocation.get()},dumponexit=true,settings=profile.jfc",
+ "-XX:FlightRecorderOptions=stackdepth=1024"
+ )
+ }
-dependencies {
- "testImplementation"("org.assertj:assertj-core")
- "testImplementation"("org.mockito:mockito-junit-jupiter") {
- exclude(module = "junit-jupiter-engine")
+ // 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 (isCiServer) {
+ environment.remove("RUNNER_TEMP")
+ environment.remove("GITHUB_ACTION")
}
- if (project.name != "junit-jupiter-engine") {
- "testImplementation"(project(":junit-jupiter-api"))
- "testImplementation"(project(":junit-jupiter-params"))
+ 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"))
- "testRuntimeOnly"(project(":junit-jupiter-engine"))
+ if (!project.name.startsWith("junit-jupiter")) {
+ "testImplementation"(project(":junit-jupiter"))
}
"testImplementation"(testFixtures(project(":junit-jupiter-api")))
- "testRuntimeOnly"(project(":junit-platform-launcher"))
+ "testRuntimeOnly"(project(":junit-platform-engine"))
+ "testRuntimeOnly"(project(":junit-platform-jfr"))
+ "testRuntimeOnly"(project(":junit-platform-reporting"))
- "testRuntimeOnly"("org.apache.logging.log4j:log4j-core")
- "testRuntimeOnly"("org.apache.logging.log4j:log4j-jul")
+ "testRuntimeOnly"(bundleFromLibs("log4j"))
}
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..721a35e1b06d 100644
--- a/documentation/documentation.gradle.kts
+++ b/documentation/documentation.gradle.kts
@@ -1,4 +1,5 @@
import org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask
+import org.gradle.api.tasks.PathSensitivity.RELATIVE
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.junit.gradle.exec.ClasspathSystemPropertyProvider
import org.junit.gradle.javadoc.ModuleSpecificJavadocFileOption
@@ -22,14 +23,11 @@ javaLibrary {
testJavaVersion = JavaVersion.VERSION_1_8
}
-val apiReport by configurations.creating {
- extendsFrom(configurations.internal.get())
-}
+val apiReport by configurations.creating
+val standaloneConsoleLauncher by configurations.creating
dependencies {
- internal(platform(project(":dependencies")))
-
- implementation(project(":junit-jupiter-api")) {
+ implementation(projects.junitJupiterApi) {
because("Jupiter API is used in src/main/java")
}
@@ -37,26 +35,34 @@ 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(project(":junit-vintage-engine"))
- testRuntimeOnly("org.apache.logging.log4j:log4j-core")
- testRuntimeOnly("org.apache.logging.log4j:log4j-jul")
+ testImplementation(projects.junitJupiter)
+ testImplementation(projects.junitJupiterMigrationsupport)
+ testImplementation(projects.junitPlatformConsole)
+ testImplementation(projects.junitPlatformRunner)
+ testImplementation(projects.junitPlatformSuite)
+ testImplementation(projects.junitPlatformTestkit)
+ testImplementation(kotlin("stdlib"))
+
+ testImplementation(projects.junitVintageEngine)
+ testRuntimeOnly(libs.bundles.log4j)
+ testRuntimeOnly(libs.apiguardian) {
+ because("it's required to generate API tables")
+ }
+ testRuntimeOnly(libs.openTestReporting.events) {
+ because("it's required to run tests via IntelliJ which does not consumed the shadowed jar of junit-platform-reporting")
+ }
- testImplementation("io.github.classgraph:classgraph") {
+ testImplementation(libs.classgraph) {
because("ApiReportGenerator needs it")
}
+
+ standaloneConsoleLauncher(projects.junitPlatformConsoleStandalone)
}
asciidoctorj {
modules {
diagram.use()
- pdf.version(versions["asciidoctor-pdf"])
+ pdf.version(libs.versions.asciidoctor.pdf)
}
}
@@ -67,12 +73,13 @@ val docsDir = file("$buildDir/ghpages-docs")
val replaceCurrentDocs = project.hasProperty("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")
+ sign.set(false)
contents {
from(docsDir)
@@ -88,15 +95,17 @@ 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 generatedAsciiDocPath = layout.buildDirectory.dir("generated/asciidoc")
+val consoleLauncherOptionsFile = generatedAsciiDocPath.map { it.file("console-launcher-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 = file("$buildDir/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,14 +114,58 @@ require(externalModulesWithoutModularJavadoc.values.all { it.endsWith("/") }) {
tasks {
- val consoleLauncherTest by registering(JavaExec::class) {
- dependsOn(testClasses)
+ val consoleLauncherTest by registering {
+ val runtimeClasspath = sourceSets["test"].runtimeClasspath
+ inputs.files(runtimeClasspath).withNormalizer(ClasspathNormalizer::class)
val reportsDir = file("$buildDir/test-results")
outputs.dir(reportsDir)
- outputs.cacheIf { true }
+
+ val debugging = providers.gradleProperty("consoleLauncherTestDebug")
+ .map { it != "false" }
+ .orElse(false)
+ outputs.cacheIf { !debugging.get() }
+ outputs.upToDateWhen { !debugging.get() }
+
+ // Track OS as input so that tests are executed on all configured operating systems on CI
+ trackOperationSystemAsInput()
+ doFirst {
+ val output = ByteArrayOutputStream()
+ val result = javaexec {
+ debug = project.findProperty("debug") == "true"
+ classpath = runtimeClasspath
+ mainClass.set("org.junit.platform.console.ConsoleLauncher")
+ args("--scan-classpath")
+ args("--config", "enableHttpServer=true")
+ args("--include-classname", ".*Tests")
+ args("--include-classname", ".*Demo")
+ args("--exclude-tag", "exclude")
+ args("--reports-dir", reportsDir)
+ args("--config=junit.platform.reporting.output.dir=${reportsDir}")
+ args("--config=junit.platform.reporting.open.xml.enabled=true")
+ systemProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager")
+ debug = debugging.get()
+ if (!debugging.get()) {
+ standardOutput = output
+ errorOutput = output
+ }
+ isIgnoreExitValue = true
+ }
+ if (result.exitValue != 0 && !debugging.get()) {
+ System.out.write(output.toByteArray())
+ System.out.flush()
+ }
+ result.rethrowFailure().assertNormalExitValue()
+ }
+ }
+
+ register("consoleLauncher") {
+ val reportsDir = file("$buildDir/console-launcher")
+ outputs.dir(reportsDir)
+ outputs.upToDateWhen { false }
classpath = sourceSets["test"].runtimeClasspath
- main = "org.junit.platform.console.ConsoleLauncher"
+ mainClass.set("org.junit.platform.console.ConsoleLauncher")
args("--scan-classpath")
+ args("--config", "enableHttpServer=true")
args("--include-classname", ".*Tests")
args("--include-classname", ".*Demo")
args("--exclude-tag", "exclude")
@@ -127,14 +180,14 @@ tasks {
val generateConsoleLauncherOptions by registering(JavaExec::class) {
classpath = sourceSets["test"].runtimeClasspath
- main = "org.junit.platform.console.ConsoleLauncher"
+ mainClass.set("org.junit.platform.console.ConsoleLauncher")
args("--help", "--disable-banner")
redirectOutput(consoleLauncherOptionsFile)
}
val generateExperimentalApisTable by registering(JavaExec::class) {
classpath = sourceSets["test"].runtimeClasspath
- main = "org.junit.api.tools.ApiReportGenerator"
+ mainClass.set("org.junit.api.tools.ApiReportGenerator")
jvmArgumentProviders += ClasspathSystemPropertyProvider("api.classpath", apiReport)
args("EXPERIMENTAL")
redirectOutput(experimentalApisTableFile)
@@ -142,15 +195,31 @@ tasks {
val generateDeprecatedApisTable by registering(JavaExec::class) {
classpath = sourceSets["test"].runtimeClasspath
- main = "org.junit.api.tools.ApiReportGenerator"
+ mainClass.set("org.junit.api.tools.ApiReportGenerator")
jvmArgumentProviders += ClasspathSystemPropertyProvider("api.classpath", apiReport)
args("DEPRECATED")
redirectOutput(deprecatedApisTableFile)
}
+ val generateStandaloneConsoleLauncherShadowedArtifactsFile by registering(Copy::class) {
+ from(zipTree(standaloneConsoleLauncher.elements.map { it.single().asFile })) {
+ include("META-INF/shadowed-artifacts")
+ includeEmptyDirs = false
+ eachFile {
+ relativePath = RelativePath(true, standaloneConsoleLauncherShadowedArtifactsFile.get().asFile.name)
+ }
+ filter { line -> "- `${line}`" }
+ }
+ into(standaloneConsoleLauncherShadowedArtifactsFile.map { it.asFile.parentFile })
+ }
+
withType().configureEach {
- dependsOn(generateConsoleLauncherOptions, generateExperimentalApisTable, generateDeprecatedApisTable)
- inputs.files(consoleLauncherOptionsFile, experimentalApisTableFile, deprecatedApisTableFile)
+ inputs.files(
+ generateConsoleLauncherOptions,
+ generateExperimentalApisTable,
+ generateDeprecatedApisTable,
+ generateStandaloneConsoleLauncherShadowedArtifactsFile
+ )
resources {
from(sourceDir) {
@@ -159,21 +228,25 @@ tasks {
}
}
+ // Temporary workaround for https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/599
+ inputs.dir(sourceDir).withPropertyName("sourceDir").withPathSensitivity(RELATIVE)
+
attributes(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,
+ "standaloneConsoleLauncherShadowedArtifactsFile" to standaloneConsoleLauncherShadowedArtifactsFile,
"outdir" to outputDir.absolutePath,
"source-highlighter" to "rouge",
"tabsize" to "4",
@@ -181,7 +254,8 @@ tasks {
"icons" to "font",
"sectanchors" to true,
"idprefix" to "",
- "idseparator" to "-"
+ "idseparator" to "-",
+ "jdk-javadoc-base-url" to jdkJavadocBaseUrl
))
sourceSets["test"].apply {
@@ -196,6 +270,14 @@ tasks {
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"
+ )
+ }
}
asciidoctor {
@@ -261,15 +343,15 @@ 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")
}
@@ -293,6 +375,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"
)))
}
@@ -368,31 +451,48 @@ 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)
}
+
+ gitPublishCommit {
+ dependsOn(configureGitAuthor)
+ }
}
-fun JavaExec.redirectOutput(outputFile: File) {
+fun JavaExec.redirectOutput(outputFile: Provider) {
outputs.file(outputFile)
val byteStream = ByteArrayOutputStream()
standardOutput = byteStream
doLast {
- Files.createDirectories(outputFile.parentFile.toPath())
- Files.write(outputFile.toPath(), byteStream.toByteArray())
+ outputFile.get().asFile.apply {
+ Files.createDirectories(parentFile.toPath())
+ Files.write(toPath(), byteStream.toByteArray())
+ }
}
}
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..025ab6fc86d4 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,27 @@ 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]
+: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 +64,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 +100,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,23 +112,26 @@ 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]
@@ -163,6 +182,9 @@ endif::[]
: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..e4be51b9779a 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.8 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.9.1.adoc[]
-include::{basedir}/release-notes-5.6.2.adoc[]
+include::{basedir}/release-notes-5.9.0.adoc[]
-include::{basedir}/release-notes-5.6.1.adoc[]
+include::{basedir}/release-notes-5.8.2.adoc[]
-include::{basedir}/release-notes-5.6.0.adoc[]
+include::{basedir}/release-notes-5.8.1.adoc[]
+
+include::{basedir}/release-notes-5.8.0.adoc[]
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.8.0.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.0.adoc
new file mode 100644
index 000000000000..fc0763c6e355
--- /dev/null
+++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.0.adoc
@@ -0,0 +1,21 @@
+[[release-notes-5.8.0]]
+== 5.8.0
+
+*Date of Release:* September 12, 2021
+
+*Scope:*
+
+* Declarative test suites via `@Suite` classes
+* `LauncherSession` and accompanying listener
+* New `UniqueIdTrackingListener`
+* More fine-grained Java Flight Recorder events
+* Java Flight Recorder support on Java 8 Update 262 or higher
+* Test class ordering
+* `@TempDir` can be used to create multiple temporary directories
+* Extension registration via `@ExtendWith` on fields and parameters
+* Auto-close support for arguments in `@ParameterizedTest` methods
+* Memory and performance optimizations
+* Numerous bug fixes and minor improvements
+
+For complete details consult the
+https://junit.org/junit5/docs/5.8.0/release-notes/index.html[5.8.0 Release Notes] online.
diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.1.adoc
new file mode 100644
index 000000000000..8a54b4f541d9
--- /dev/null
+++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.1.adoc
@@ -0,0 +1,62 @@
+[[release-notes-5.8.1]]
+== 5.8.1
+
+*Date of Release:* September 22, 2021
+
+*Scope:*
+
+* Support for _text blocks_ in `@CsvSource`
+* Java 18 support in the `JRE` enum
+* Access to the `ExecutionMode` in the `ExtensionContext`
+* Minor bug fixes and enhancements since 5.8.0
+
+For a complete list of all _closed_ issues and pull requests for this release, consult the
+link:{junit5-repo}+/milestone/59?closed=1+[5.8.1] milestone page in the JUnit repository on
+GitHub.
+
+
+[[release-notes-5.8.1-junit-platform]]
+=== JUnit Platform
+
+==== Deprecations and Breaking Changes
+
+* `@UseTechnicalNames` has been deprecated in favor of the new `@Suite` support which does
+ not require the use of technical names. See the warning in
+ <<../user-guide/index.adoc#running-tests-junit-platform-runner, Using JUnit 4 to run the
+ JUnit Platform>> for details.
+
+==== New Features and Improvements
+
+* `ReflectionSupport.findNestedClasses(..)` is now thread-safe with regard to cycle
+ detection.
+
+
+[[release-notes-5.8.1-junit-jupiter]]
+=== JUnit Jupiter
+
+==== Bug Fixes
+
+* `assertLinesMatch()` in `Assertions` no longer fails with a `NoSuchElementException` if
+ a limited fast-forward followed by at least one more expected line exceeds the remaining
+ actual lines.
+* `assertLinesMatch()` in `Assertions` now handles fast-forwards with leading and trailing
+ spaces correctly and no longer throws an `IndexOutOfBoundsException`.
+
+==== New Features and Improvements
+
+* `JAVA_18` has been added to the `JRE` enum for use with JRE-based execution conditions.
+* CSV content in `@CsvSource` can now be supplied as a _text block_ instead of an array of
+ strings. See the
+ <<../user-guide/index.adoc#writing-tests-parameterized-tests-sources-CsvSource, User
+ Guide>> for details and an example.
+* The `ExecutionMode` for the current test or container is now accessible via the
+ `ExtensionContext`.
+
+
+[[release-notes-5.8.1-junit-vintage]]
+=== JUnit Vintage
+
+==== Bug Fixes
+
+* Relaxed version constraint in published Gradle Module Metadata to allow downgrading the
+ `junit:junit` dependency from 4.13.2.
diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.2.adoc
new file mode 100644
index 000000000000..831cc74ae286
--- /dev/null
+++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.2.adoc
@@ -0,0 +1,45 @@
+[[release-notes-5.8.2]]
+== 5.8.2
+
+*Date of Release:* November 28, 2021
+
+*Scope:*
+
+* Text blocks in `@CsvSource` are treated like CSV files
+* CSV headers in display names for `@CsvSource` and `@CsvFileSource`
+* Custom quote character support in `@CsvSource` and `@CsvFileSource`
+
+For a complete list of all _closed_ issues and pull requests for this release, consult the
+link:{junit5-repo}+/milestone/60?closed=1+[5.8.2] milestone page in the JUnit repository on
+GitHub.
+
+
+[[release-notes-5.8.2-junit-platform]]
+=== JUnit Platform
+
+No changes.
+
+
+[[release-notes-5.8.2-junit-jupiter]]
+=== JUnit Jupiter
+
+==== New Features and Improvements
+
+* Text blocks in `@CsvSource` are now treated like complete CSV files, including support
+ for comments beginning with a `+++#+++` symbol as well as support for new lines within
+ quoted strings. See the
+ <<../user-guide/index.adoc#writing-tests-parameterized-tests-sources-CsvSource, User
+ Guide>> for details and examples.
+* CSV headers can now be used in display names in parameterized tests. See
+ <<../user-guide/index.adoc#writing-tests-parameterized-tests-sources-CsvSource,
+ `@CsvSource`>> and
+ <<../user-guide/index.adoc#writing-tests-parameterized-tests-sources-CsvFileSource,
+ `@CsvFileSource`>> in the User Guide for details and examples.
+* The quote character for _quoted strings_ in `@CsvSource` and `@CsvFileSource` is now
+ configurable via a new `quoteCharacter` attribute in each annotation.
+
+
+[[release-notes-5.8.2-junit-vintage]]
+=== JUnit Vintage
+
+No changes.
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..aa292b95a634
--- /dev/null
+++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.0.adoc
@@ -0,0 +1,148 @@
+[[release-notes-5.9.0]]
+== 5.9.0
+
+*Date of Release:* July 26, 2022
+
+*Scope:*
+
+* XML reports in 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`
+
+For a complete list of all _closed_ issues and pull requests for this release, consult the
+link:{junit5-repo}+/milestone/58?closed=1+[5.9 M1],
+link:{junit5-repo}+/milestone/61?closed=1+[5.9 RC1], and
+link:{junit5-repo}+/milestone/62?closed=1+[5.9 GA] milestone pages in the JUnit repository
+on GitHub.
+
+
+[[release-notes-5.9.0-junit-platform]]
+=== JUnit Platform
+
+==== Bug Fixes
+
+* Fixed handling of global post-discovery filters that apply to `@Suite` classes.
+* Since the Turkish language has special characters such as 'ı' and 'İ', the uppercase
+ conversion in `DefaultParallelExecutionConfigurationStrategy#getStrategy` previously
+ caused all tests to finish with exit code -1. This has been fixed by using the root
+ locale instead of the default one.
+* Absolute path entries are now supported in JUnit's Platform Console Launcher on Windows.
+* Attempts to load a `Class` for an invalid class name representing an extremely large
+ multidimensional array now fail within a reasonable amount of time.
+* Fix concurrency issue in classpath scanning.
+
+==== Deprecations and Breaking Changes
+
+* `ConfigurationParameters.size()` has been deprecated in favor of the new `keySet()`
+ method.
+
+==== New Features and Improvements
+
+* Support for the https://github.com/ota4j-team/open-test-reporting[Open Test Reporting]
+ format which supports all features of the JUnit Platform such as hierarchical test
+ structures, display names, tags, etc. Please refer to the
+ <<../user-guide/index.adoc#junit-platform-reporting-open-test-reporting, User Guide>>
+ for instructions on how to enable such reports in your build.
+* `ConfigurationParameters` has a new `keySet()` method which allows you to retrieve all
+ configuration parameter keys.
+* New `IterationSelector` for selecting a subset of a test's or container's iterations.
+* `ParallelExecutionConfiguration` allows configuring the `saturate` predicate of the
+ `ForkJoinPool` used for parallel test execution.
+* JUnit OSGi bundles now contain `engine` and `launcher` requirements ensuring that at
+ resolution time a fully running set of dependencies is calculated, avoiding the need for
+ these to be manually specified.
+* JUnit Platform Standalone Console JAR now also includes the JUnit Platform Suite Engine.
+* New `failIfNoTests` attribute added to `@Suite`. This will fail the suite if no tests
+ are discovered.
+* The output color for the `ConsoleLauncher` can now be customized. The option
+ `--single-color` will apply a built-in monochrome style, while `--color-palette` will
+ accept a properties file. See the
+ <<../user-guide/index.adoc#running-tests-console-launcher-color-customization,
+ User Guide>> for details.
+* The default theme for the `ConsoleLauncher` is now determined by the charset reported by
+ the system console on Java 17 and later.
+* New `--list-engines` option added to the `ConsoleLauncher` which displays all registered
+ test engine implementations.
+* Configuring included `EngineFilters` that do not match any registered `TestEngine`
+ results in an error to avoid misconfiguration – for example, due to typos.
+* New public factory method to instantiate an `ExecutionRequest`.
+* Documentation for overriding the JUnit version used in Spring Boot applications. See the
+ <<../user-guide/index.adoc#running-tests-build-spring-boot, User Guide>> for details.
+
+
+[[release-notes-5.9.0-junit-jupiter]]
+=== JUnit Jupiter
+
+==== Bug Fixes
+
+* When cleaning up a `@TempDir`, only one retry attempt will be made to delete directories.
+* Since the Turkish language has special characters such as 'ı' and 'İ', the uppercase
+ conversion in `DefaultParallelExecutionConfigurationStrategy#getStrategy` previously
+ caused all tests to finish with exit code -1. This has been fixed by using the root
+ locale instead of the default one.
+
+==== Deprecations and Breaking Changes
+
+* `@TempDir` fields are no longer allowed to be declared as `final`.
+ - This improves diagnostics for failures resulting from a user-declared `static final`
+ `@TempDir` field by throwing an exception with an informative error message.
+* Private lifecycle methods (annotated with `@BeforeAll`, `@AfterAll`, `@BeforeEach`, or
+ `@AfterEach`) now correctly lead to an exception. Although this is a bug fix, it is
+ technically also a breaking change since there might be existing user code with
+ `private` lifecycle methods which will now start to fail.
+
+==== New Features and Improvements
+
+* `@TempDir` now includes a `cleanup` mode attribute for preventing a temporary directory
+ from being deleted after a test. The default cleanup mode can be configured via a
+ configuration parameter.
+* Support for FreeBSD and OpenBSD operating systems in `@EnabledOnOs` and `@DisabledOnOs`.
+* New `MATCH_NONE` mode for `@EnumSource` that selects only those enum constants whose
+ names match none of the supplied patterns.
+* The `@Order` annotation is now a `STABLE` API.
+* New `TestInstancePreConstructCallback` extension API that is called prior to test
+ instance construction – symmetric to the existing `TestInstancePreDestroyCallback`
+ extension API.
+* Extensions can now leverage registered `ParameterResolver` extensions when invoking
+ methods and constructors via the new `ExecutableInvoker` API available in the
+ `ExtensionContext`.
+* A subset of the invocations of parameterized or dynamic tests can now be selected via
+ the new `IterationSelector` discovery selector when launching the JUnit Platform.
+* `JAVA_19` and `JAVA_20` have been added to the `JRE` enum for use with JRE-based
+ execution conditions.
+* `@MethodSource` factory methods can now accept arguments resolved by registered
+ `ParameterResolver` extensions.
+* `AssertionFailureBuilder` allows reusing Jupiter's logic for creating failure messages
+ to assist in writing custom assertion methods.
+* Three new `abort` methods have been added to the `Assumptions` class. These are
+ analogous to the `fail` methods in the `Assertions` class, but instead of failing they
+ abort the test or container.
+* Support for enabling/disabling tests based on the system's hardware architecture via new
+ `architectures` attributes in `@EnabledOnOs` and `@DisabledOnOs`.
+* Default `@MethodSource` factory methods can now accept arguments. A _default_ factory
+ method is a method declared in the test class with the same name as the
+ `@ParameterizedTest` method that is inferred as the factory method when no explicit
+ factory method is specified in the `@MethodSource` annotation.
+* Thread mode can be set on `@Timeout` annotation. It allows to configure whether test
+ code is executed in the thread of the calling code or in a separate thread. The three
+ modes are: `INFERRED` (default) which resolves the thread mode configured via the
+ property `junit.jupiter.execution.timeout.thread.mode.default`, `SAME_THREAD` that
+ executes the test code in the same thread as the calling code, and `SEPARATE_THREAD`
+ which executes it in a separate thread.
+
+
+[[release-notes-5.9.0-junit-vintage]]
+=== JUnit Vintage
+
+==== New Features and Improvements
+
+* More accurate reporting of intermediate start/finish events, e.g. iterations of the
+ `Parameterized` runner and classes executed indirectly via the `Suite` runner, when
+ running with JUnit 4.13 or later.
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-TEMPLATE.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-TEMPLATE.adoc
index 2952e59175fe..17790483e124 100644
--- a/documentation/src/docs/asciidoc/release-notes/release-notes-TEMPLATE.adoc
+++ b/documentation/src/docs/asciidoc/release-notes/release-notes-TEMPLATE.adoc
@@ -7,9 +7,9 @@
*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/⚠️?closed=1+[⚠️] milestone page in the JUnit repository on
+GitHub.
[[release-notes-⚠️-junit-platform]]
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..3c06d5af1736
--- /dev/null
+++ b/documentation/src/docs/asciidoc/user-guide/advanced-topics/engines.adoc
@@ -0,0 +1,80 @@
+[[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.
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..18be01cd9091
--- /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 declard 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..2c8b25e7c9c2
--- /dev/null
+++ b/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc
@@ -0,0 +1,243 @@
+[[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}`.
+
+[[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-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]
+----
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..a1fee08b8303 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,40 @@ 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]
+----
+
+[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 +146,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 +189,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 +245,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 +266,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 +321,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
@@ -366,6 +416,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
@@ -742,27 +799,31 @@ callbacks implemented by `Extension2`. `Extension1` is therefore said to _wrap_
JUnit Jupiter also guarantees _wrapping_ behavior within class and interface hierarchies
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.
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..09d351fbfec6 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,11 @@ 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 <>.
+* 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 +98,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 +122,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..4a5d1bd57e6b 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]]
@@ -118,28 +131,29 @@ Platform 1.2 and discontinued in 1.3. Please switch to Gradle's standard `test`
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 +162,9 @@ 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.
+NOTE: 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 +174,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 +191,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}")
}
----
@@ -192,7 +205,7 @@ 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 {
@@ -215,11 +228,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")
}
----
@@ -246,6 +259,9 @@ 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.
+NOTE: 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,46 +275,17 @@ following.
[source,xml,indent=0]
[subs=attributes+]
----
-
-
-
- maven-surefire-plugin
- {surefire-version}
-
-
- maven-failsafe-plugin
- {surefire-version}
-
-
- org.junit.jupiter
- junit-jupiter-api
- {jupiter-version}
- test
-
-
- org.junit.jupiter
- junit-jupiter-engine
+ junit-jupiter{jupiter-version}test
-
-----
-
-Maven Surefire and Maven Failsafe can run JUnit 4 based tests alongside Jupiter tests as
-long as you configure `test` scoped dependencies on JUnit 4 and the JUnit Vintage
-`TestEngine` implementation similar to the following.
-
-[source,xml,indent=0]
-[subs=attributes+]
-----
-
@@ -312,6 +299,16 @@ long as you configure `test` scoped dependencies on JUnit 4 and the JUnit Vintag
+----
+
+Maven Surefire and Maven Failsafe can run JUnit 4 based tests alongside Jupiter tests as
+long as you configure `test` scoped dependencies on JUnit 4 and the JUnit Vintage
+`TestEngine` implementation similar to the following.
+
+[source,xml,indent=0]
+[subs=attributes+]
+----
+
@@ -329,6 +326,19 @@ long as you configure `test` scoped dependencies on JUnit 4 and the JUnit Vintag
+
+
+
+ maven-surefire-plugin
+ {surefire-version}
+
+
+ maven-failsafe-plugin
+ {surefire-version}
+
+
+
+
----
[[running-tests-build-maven-filter-test-class-names]]
@@ -337,10 +347,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 +386,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 +405,7 @@ the following configuration properties.
acceptance | !feature-aintegration, regression
-
+
@@ -436,18 +447,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 +521,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 +573,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:
-`java -jar junit-platform-console-standalone-{platform-version}.jar <<>>`
+include::{standaloneConsoleLauncherShadowedArtifactsFile}[]
-Here's an example of its output:
+You can https://docs.oracle.com/javase/tutorial/deployment/jar/run.html[run] the
+standalone `ConsoleLauncher` as shown below.
+
+[source,console,subs=attributes+]
+----
+$ java -jar junit-platform-console-standalone-{platform-version}.jar
-....
├─ JUnit Vintage
│ └─ example.JUnit4Tests
│ └─ standardJUnit4Test ✔
@@ -553,7 +609,15 @@ 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
@@ -596,10 +660,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 +713,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 +721,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 +731,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 +745,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 +802,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.
- <>
@@ -738,30 +844,61 @@ 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 +925,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 +945,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 +962,69 @@ 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
+
+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.
+* `{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 +1037,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 +1052,6 @@ 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.
diff --git a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
index 3c6a686f3038..6fb1838f63ec 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,59 @@ 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;
-[[writing-tests-tagging-and-filtering]]
-=== Tagging and Filtering
+include::{testDir}/example/ExternalCustomConditionDemo.java[tags=user_guide_external_custom_condition]
+----
-Test classes and methods can be tagged via the `@Tag` annotation. Those tags can later be
-used to filter <>.
+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 either static methods or instance methods as condition
+methods.
+
+[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 +579,16 @@ 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 +601,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 +624,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 +634,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,6 +647,73 @@ 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, run 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
@@ -598,6 +739,9 @@ 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.
@@ -642,7 +786,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 +796,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
@@ -995,9 +1157,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 +1186,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
@@ -1161,7 +1335,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 +1385,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 +1395,27 @@ 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 methods with the same name, the factory method must be
+referenced by its fully qualified method name – for example,
+`@MethodSource("example.MyTests#factoryMethodWithArguments(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 +1427,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 +1543,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 +1557,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
@@ -1548,7 +1845,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 +1866,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 +1991,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 +2045,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 +2056,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 +2067,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 +2082,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,7 +2192,8 @@ 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]]
@@ -1891,11 +2238,11 @@ junit.jupiter.execution.parallel.mode.default = concurrent
The default execution mode is applied to all nodes of the test tree with a few notable
exceptions, namely test classes that use the `Lifecycle.PER_CLASS` mode or a
-`{MethodOrderer}` (except for `{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.
All nodes of the test tree that are configured with the `CONCURRENT` execution mode will
be executed fully in parallel according to the provided
@@ -2007,6 +2354,63 @@ behind the scenes may spawn additional threads to ensure execution continues wit
sufficient parallelism. Thus, if you require such guarantees in a test class, please use
your own means of controlling concurrency.
+[[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.fixed.parallelism```
+| Desired parallelism for the ```fixed``` configuration strategy
+| a positive integer
+| no default value
+
+| ```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
@@ -2064,7 +2468,7 @@ 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 +2482,45 @@ 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/TempDirCleanupModeDemo.java[tags=user_guide]
+----
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..2baf09e52879 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-2022 the original author or authors.
*
* All rights 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..153222b66248 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-2022 the original author or authors.
*
* All rights 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..39c71a400436 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-2022 the original author or authors.
*
* All rights 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..adcae9157bcc 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-2022 the original author or authors.
*
* All rights 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..c096b71e1877 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-2022 the original author or authors.
*
* All rights 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..3420f34a1601 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-2022 the original author or authors.
*
* All rights 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..2a09131c8469 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-2022 the original author or authors.
*
* All rights 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..18f398c9577e 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-2022 the original author or authors.
*
* All rights 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/AssumptionsDemo.java b/documentation/src/test/java/example/AssumptionsDemo.java
index 75914749a7bf..0f6755c67bec 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-2022 the original author or authors.
*
* All rights 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..a85031a1117c 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-2022 the original author or 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/CustomTestEngine.java b/documentation/src/test/java/example/CustomTestEngine.java
index b12a8e862c49..e14c4821e7b1 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-2022 the original author or authors.
*
* All rights 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..a4c8168aa2d8 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-2022 the original author or authors.
*
* All rights 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..72d146f793b3 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-2022 the original author or authors.
*
* All rights 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..eda1c9347586 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-2022 the original author or authors.
*
* All rights 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..f5170e4c5bc5 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-2022 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
diff --git a/documentation/src/test/java/example/DocumentationTestSuite.java b/documentation/src/test/java/example/DocumentationTestSuite.java
index 1b2f9d575883..52b5659b29cd 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-2022 the original author or 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;
/**
- *
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
- * 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}
+ *
{@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 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 @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
+ *
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 +75,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..3e0cab029463 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-2022 the original author or 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,20 @@
* {@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}.
* 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 +67,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..6424dd14b2cf 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-2022 the original author or 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..2a2f8f6d2042 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-2022 the original author or 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..d11a3e0e6dbf 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-2022 the original author or 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..9afd4b1072c5 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-2022 the original author or 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..8364cb64615b 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-2022 the original author or 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..f1891d138c83
--- /dev/null
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2015-2022 the original author or 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..a569d2105b90 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-2022 the original author or 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..1479728830ed 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-2022 the original author or 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..1e8be26c6a5e 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-2022 the original author or 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..71be7b88a72f 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-2022 the original author or 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..753e734c2a34 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-2022 the original author or 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..4a9e92b2d4a0 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-2022 the original author or 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..c080b3e01f65 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-2022 the original author or 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..d4e8b19d3498 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-2022 the original author or 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..39a4c2169367
--- /dev/null
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrowsExactly.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2015-2022 the original author or 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..93c7c11f8a71 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-2022 the original author or 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..94534171dd01
--- /dev/null
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2015-2022 the original author or 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..3d250a46f68e 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-2022 the original author or 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..b1df82fab85c
--- /dev/null
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionFailureBuilder.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2015-2022 the original author or 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..c591c217dadb 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-2022 the original author or 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..62fefbe630fb 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-2022 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
@@ -10,6 +10,8 @@
package org.junit.jupiter.api;
+import static org.apiguardian.api.API.Status.EXPERIMENTAL;
+import static org.apiguardian.api.API.Status.INTERNAL;
import static org.apiguardian.api.API.Status.STABLE;
import java.time.Duration;
@@ -33,13 +35,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 +78,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
@@ -2993,6 +3011,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 = EXPERIMENTAL, since = "5.8")
+ 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 = EXPERIMENTAL, since = "5.8")
+ 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 = EXPERIMENTAL, since = "5.8")
+ 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 +3077,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 +3091,7 @@ public static