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. +[![Revved up by Gradle Enterprise](https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.junit.org/scans) + +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-a integration, 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; /** - *

Logging Configuration

+ *

Logging Configuration

* *

In order for our log4j2 configuration to be used in an IDE, you must * set the following system property before running any tests — for @@ -29,9 +28,9 @@ * * @since 5.0 */ -@RunWith(JUnitPlatform.class) +@Suite @SelectPackages("example") @IncludeClassNamePatterns(".+(Tests|Demo)$") @ExcludeTags("exclude") -public class DocumentationTestSuite { +class DocumentationTestSuite { } diff --git a/documentation/src/test/java/example/DynamicTestsDemo.java b/documentation/src/test/java/example/DynamicTestsDemo.java index 8aa063a57dbe..78081a93690b 100644 --- a/documentation/src/test/java/example/DynamicTestsDemo.java +++ b/documentation/src/test/java/example/DynamicTestsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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 @@ -19,6 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; import static org.junit.jupiter.api.DynamicTest.dynamicTest; +import static org.junit.jupiter.api.Named.named; import java.util.Arrays; import java.util.Collection; @@ -33,6 +34,7 @@ import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Named; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.function.ThrowingConsumer; @@ -151,6 +153,21 @@ Stream dynamicTestsFromStreamFactoryMethod() { return DynamicTest.stream(inputStream, displayNameGenerator, testExecutor); } + @TestFactory + Stream dynamicTestsFromStreamFactoryMethodWithNames() { + // Stream of palindromes to check + Stream> inputStream = Stream.of( + named("racecar is a palindrome", "racecar"), + named("radar is also a palindrome", "radar"), + named("mom also seems to be a palindrome", "mom"), + named("dad is yet another palindrome", "dad") + ); + + // Returns a stream of dynamic tests. + return DynamicTest.stream(inputStream, + text -> assertTrue(isPalindrome(text))); + } + @TestFactory Stream dynamicTestsWithContainers() { return Stream.of("A", "B", "C") diff --git a/documentation/src/test/java/example/ExampleTestCase.java b/documentation/src/test/java/example/ExampleTestCase.java index e08950b1df9b..70669434358c 100644 --- a/documentation/src/test/java/example/ExampleTestCase.java +++ b/documentation/src/test/java/example/ExampleTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/ExternalCustomConditionDemo.java b/documentation/src/test/java/example/ExternalCustomConditionDemo.java new file mode 100644 index 000000000000..afa135502243 --- /dev/null +++ b/documentation/src/test/java/example/ExternalCustomConditionDemo.java @@ -0,0 +1,34 @@ +/* + * 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 example; + +// tag::user_guide_external_custom_condition[] +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIf; + +class ExternalCustomConditionDemo { + + @Test + @EnabledIf("example.ExternalCondition#customCondition") + void enabled() { + // ... + } + +} + +class ExternalCondition { + + static boolean customCondition() { + return true; + } + +} +// end::user_guide_external_custom_condition[] diff --git a/documentation/src/test/java/example/ExternalMethodSourceDemo.java b/documentation/src/test/java/example/ExternalMethodSourceDemo.java index cda486d775de..93c35a6eb567 100644 --- a/documentation/src/test/java/example/ExternalMethodSourceDemo.java +++ b/documentation/src/test/java/example/ExternalMethodSourceDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/Fast.java b/documentation/src/test/java/example/Fast.java index 911fdb2df0d7..0908da76d652 100644 --- a/documentation/src/test/java/example/Fast.java +++ b/documentation/src/test/java/example/Fast.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/FastTest.java b/documentation/src/test/java/example/FastTest.java index f85066f7db29..9d5fcd7e4722 100644 --- a/documentation/src/test/java/example/FastTest.java +++ b/documentation/src/test/java/example/FastTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/HamcrestAssertionsDemo.java b/documentation/src/test/java/example/HamcrestAssertionsDemo.java index 5f9cd857cb81..6b74a22df88a 100644 --- a/documentation/src/test/java/example/HamcrestAssertionsDemo.java +++ b/documentation/src/test/java/example/HamcrestAssertionsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/IgnoredTestsDemo.java b/documentation/src/test/java/example/IgnoredTestsDemo.java index 6bddc9134c9f..75289a0948fc 100644 --- a/documentation/src/test/java/example/IgnoredTestsDemo.java +++ b/documentation/src/test/java/example/IgnoredTestsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/JUnit4Tests.java b/documentation/src/test/java/example/JUnit4Tests.java index 2caac8b99351..86babd497881 100644 --- a/documentation/src/test/java/example/JUnit4Tests.java +++ b/documentation/src/test/java/example/JUnit4Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/JUnitPlatformClassDemo.java b/documentation/src/test/java/example/JUnitPlatformClassDemo.java index 09acf1b0e24d..be2844623c05 100644 --- a/documentation/src/test/java/example/JUnitPlatformClassDemo.java +++ b/documentation/src/test/java/example/JUnitPlatformClassDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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 @@ -14,10 +14,12 @@ import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.Test; -import org.junit.platform.runner.JUnitPlatform; import org.junit.runner.RunWith; -@RunWith(JUnitPlatform.class) +//end::user_guide[] +@SuppressWarnings("deprecation") +//tag::user_guide[] +@RunWith(org.junit.platform.runner.JUnitPlatform.class) public class JUnitPlatformClassDemo { @Test diff --git a/documentation/src/test/java/example/JUnitPlatformSuiteDemo.java b/documentation/src/test/java/example/JUnitPlatformSuiteDemo.java index a5c698558967..7dd7296ed015 100644 --- a/documentation/src/test/java/example/JUnitPlatformSuiteDemo.java +++ b/documentation/src/test/java/example/JUnitPlatformSuiteDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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 @@ -11,15 +11,15 @@ package example; //tag::user_guide[] -import org.junit.platform.runner.JUnitPlatform; import org.junit.platform.suite.api.SelectPackages; import org.junit.platform.suite.api.SuiteDisplayName; import org.junit.runner.RunWith; -@RunWith(JUnitPlatform.class) +@RunWith(org.junit.platform.runner.JUnitPlatform.class) @SuiteDisplayName("JUnit Platform Suite Demo") @SelectPackages("example") //end::user_guide[] +@SuppressWarnings("deprecation") @org.junit.platform.suite.api.ExcludeTags("exclude") //tag::user_guide[] public class JUnitPlatformSuiteDemo { diff --git a/documentation/src/test/java/example/MethodSourceParameterResolutionDemo.java b/documentation/src/test/java/example/MethodSourceParameterResolutionDemo.java new file mode 100644 index 000000000000..36ea8701d907 --- /dev/null +++ b/documentation/src/test/java/example/MethodSourceParameterResolutionDemo.java @@ -0,0 +1,66 @@ +/* + * 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 example; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class MethodSourceParameterResolutionDemo { + + // @formatter:off + // tag::parameter_resolution_MethodSource_example[] + @RegisterExtension + static final IntegerResolver integerResolver = new IntegerResolver(); + + @ParameterizedTest + @MethodSource("factoryMethodWithArguments") + void testWithFactoryMethodWithArguments(String argument) { + assertTrue(argument.startsWith("2")); + } + + static Stream factoryMethodWithArguments(int quantity) { + return Stream.of( + arguments(quantity + " apples"), + arguments(quantity + " lemons") + ); + } + + static class IntegerResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, + ExtensionContext extensionContext) { + + return parameterContext.getParameter().getType() == int.class; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, + ExtensionContext extensionContext) { + + return 2; + } + + } + // end::parameter_resolution_MethodSource_example[] + // @formatter:on + +} diff --git a/documentation/src/test/java/example/MyFirstJUnitJupiterTests.java b/documentation/src/test/java/example/MyFirstJUnitJupiterTests.java index 6eea9d7210d6..d1ddbc5b77bc 100644 --- a/documentation/src/test/java/example/MyFirstJUnitJupiterTests.java +++ b/documentation/src/test/java/example/MyFirstJUnitJupiterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/OrderedNestedTestClassesDemo.java b/documentation/src/test/java/example/OrderedNestedTestClassesDemo.java new file mode 100644 index 000000000000..194bfcbe3828 --- /dev/null +++ b/documentation/src/test/java/example/OrderedNestedTestClassesDemo.java @@ -0,0 +1,41 @@ +/* + * 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 example; + +// tag::user_guide[] +import org.junit.jupiter.api.ClassOrderer; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestClassOrder; + +@TestClassOrder(ClassOrderer.OrderAnnotation.class) +class OrderedNestedTestClassesDemo { + + @Nested + @Order(1) + class PrimaryTests { + + @Test + void test1() { + } + } + + @Nested + @Order(2) + class SecondaryTests { + + @Test + void test2() { + } + } +} +//end::user_guide[] diff --git a/documentation/src/test/java/example/OrderedTestsDemo.java b/documentation/src/test/java/example/OrderedTestsDemo.java index d54ff5b3fc82..10399dff02be 100644 --- a/documentation/src/test/java/example/OrderedTestsDemo.java +++ b/documentation/src/test/java/example/OrderedTestsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/ParameterizedTestDemo.java b/documentation/src/test/java/example/ParameterizedTestDemo.java index b0f2b76fde99..0a35327b10e5 100644 --- a/documentation/src/test/java/example/ParameterizedTestDemo.java +++ b/documentation/src/test/java/example/ParameterizedTestDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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,10 +15,12 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Named.named; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.junit.jupiter.params.provider.EnumSource.Mode.EXCLUDE; import static org.junit.jupiter.params.provider.EnumSource.Mode.MATCH_ALL; +import java.io.File; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -210,7 +212,8 @@ static Stream stringIntAndListProvider() { @CsvSource({ "apple, 1", "banana, 2", - "'lemon, lime', 0xF1" + "'lemon, lime', 0xF1", + "strawberry, 700_000" }) void testWithCsvSource(String fruit, int rank) { assertNotNull(fruit); @@ -233,6 +236,13 @@ void testWithCsvFileSourceFromFile(String country, int reference) { assertNotNull(country); assertNotEquals(0, reference); } + + @ParameterizedTest(name = "[{index}] {arguments}") + @CsvFileSource(resources = "/two-column.csv", useHeadersInDisplayName = true) + void testWithCsvFileSourceAndHeaders(String country, int reference) { + assertNotNull(country); + assertNotEquals(0, reference); + } // end::CsvFileSource_example[] // tag::ArgumentsSource_example[] @@ -344,7 +354,7 @@ protected ToLengthArgumentConverter() { @Override protected Integer convert(String source) { - return source.length(); + return (source != null ? source.length() : 0); } } @@ -440,4 +450,21 @@ void testWithCustomAggregatorAnnotation(@CsvToPerson Person person) { void testWithCustomDisplayNames(String fruit, int rank) { } // end::custom_display_names[] + + // @formatter:off + // tag::named_arguments[] + @DisplayName("A parameterized test with named arguments") + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("namedArguments") + void testWithNamedArguments(File file) { + } + + static Stream namedArguments() { + return Stream.of( + arguments(named("An important file", new File("path1"))), + arguments(named("Another file", new File("path2"))) + ); + } + // end::named_arguments[] + // @formatter:on } diff --git a/documentation/src/test/java/example/PollingTimeoutDemo.java b/documentation/src/test/java/example/PollingTimeoutDemo.java index 2242b6b86c84..88708cf270f1 100644 --- a/documentation/src/test/java/example/PollingTimeoutDemo.java +++ b/documentation/src/test/java/example/PollingTimeoutDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/RepeatedTestsDemo.java b/documentation/src/test/java/example/RepeatedTestsDemo.java index 98d552952f4d..a414d053e036 100644 --- a/documentation/src/test/java/example/RepeatedTestsDemo.java +++ b/documentation/src/test/java/example/RepeatedTestsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/SharedResourcesDemo.java b/documentation/src/test/java/example/SharedResourcesDemo.java index 99d3727437cc..b71d3e500ed1 100644 --- a/documentation/src/test/java/example/SharedResourcesDemo.java +++ b/documentation/src/test/java/example/SharedResourcesDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/SlowTests.java b/documentation/src/test/java/example/SlowTests.java index a1fba4358f35..10e88d044160 100644 --- a/documentation/src/test/java/example/SlowTests.java +++ b/documentation/src/test/java/example/SlowTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/StandardTests.java b/documentation/src/test/java/example/StandardTests.java index 91bd871f9ffa..a2bd484c237e 100644 --- a/documentation/src/test/java/example/StandardTests.java +++ b/documentation/src/test/java/example/StandardTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/SuiteDemo.java b/documentation/src/test/java/example/SuiteDemo.java new file mode 100644 index 000000000000..182d863900cd --- /dev/null +++ b/documentation/src/test/java/example/SuiteDemo.java @@ -0,0 +1,28 @@ +/* + * 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 example; + +//tag::user_guide[] +import org.junit.platform.suite.api.IncludeClassNamePatterns; +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.api.SuiteDisplayName; + +@Suite +@SuiteDisplayName("JUnit Platform Suite Demo") +@SelectPackages("example") +@IncludeClassNamePatterns(".*Tests") +//end::user_guide[] +@org.junit.platform.suite.api.ExcludeTags("exclude") +//tag::user_guide[] +class SuiteDemo { +} +//end::user_guide[] diff --git a/documentation/src/test/java/example/TaggingDemo.java b/documentation/src/test/java/example/TaggingDemo.java index 6ab3e162badf..3ec3c925b1c1 100644 --- a/documentation/src/test/java/example/TaggingDemo.java +++ b/documentation/src/test/java/example/TaggingDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/TempDirCleanupModeDemo.java b/documentation/src/test/java/example/TempDirCleanupModeDemo.java new file mode 100644 index 000000000000..ed164c25a377 --- /dev/null +++ b/documentation/src/test/java/example/TempDirCleanupModeDemo.java @@ -0,0 +1,28 @@ +/* + * 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 example; + +import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; + +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +// tag::user_guide[] +class TempDirCleanupModeDemo { + + @Test + void fileTest(@TempDir(cleanup = ON_SUCCESS) Path tempDir) { + // perform test + } +} +// end::user_guide[] diff --git a/documentation/src/test/java/example/TempDirectoryDemo.java b/documentation/src/test/java/example/TempDirectoryDemo.java index e656b44142dc..d41b6983d85d 100644 --- a/documentation/src/test/java/example/TempDirectoryDemo.java +++ b/documentation/src/test/java/example/TempDirectoryDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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,6 +12,7 @@ import static java.util.Collections.singletonList; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import java.io.IOException; import java.nio.file.Files; @@ -35,6 +36,19 @@ void writeItemsToFile(@TempDir Path tempDir) throws IOException { } // end::user_guide_parameter_injection[] + // tag::user_guide_multiple_directories[] + @Test + void copyFileFromSourceToTarget(@TempDir Path source, @TempDir Path target) throws IOException { + Path sourceFile = source.resolve("test.txt"); + new ListWriter(sourceFile).write("a", "b", "c"); + + Path targetFile = Files.copy(sourceFile, target.resolve("test.txt")); + + assertNotEquals(sourceFile, targetFile); + assertEquals(singletonList("a,b,c"), Files.readAllLines(targetFile)); + } + // end::user_guide_multiple_directories[] + static // tag::user_guide_field_injection[] class SharedTempDirectoryDemo { diff --git a/documentation/src/test/java/example/TestInfoDemo.java b/documentation/src/test/java/example/TestInfoDemo.java index 806397b9408d..2360fb07bb47 100644 --- a/documentation/src/test/java/example/TestInfoDemo.java +++ b/documentation/src/test/java/example/TestInfoDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/TestReporterDemo.java b/documentation/src/test/java/example/TestReporterDemo.java index 5638d24f19d8..2a2f68cbe687 100644 --- a/documentation/src/test/java/example/TestReporterDemo.java +++ b/documentation/src/test/java/example/TestReporterDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/TestTemplateDemo.java b/documentation/src/test/java/example/TestTemplateDemo.java index e017af9d7db0..4378ededc314 100644 --- a/documentation/src/test/java/example/TestTemplateDemo.java +++ b/documentation/src/test/java/example/TestTemplateDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/TestingAStackDemo.java b/documentation/src/test/java/example/TestingAStackDemo.java index c9cc1e0adc06..d46ff8bf253a 100644 --- a/documentation/src/test/java/example/TestingAStackDemo.java +++ b/documentation/src/test/java/example/TestingAStackDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/TimeoutDemo.java b/documentation/src/test/java/example/TimeoutDemo.java index 5959befda41f..066bbf8d40c2 100644 --- a/documentation/src/test/java/example/TimeoutDemo.java +++ b/documentation/src/test/java/example/TimeoutDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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,6 +15,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.Timeout.ThreadMode; // tag::user_guide[] class TimeoutDemo { @@ -26,9 +27,15 @@ void setUp() { } @Test - @Timeout(value = 100, unit = TimeUnit.MILLISECONDS) - void failsIfExecutionTimeExceeds100Milliseconds() { - // fails if execution time exceeds 100 milliseconds + @Timeout(value = 500, unit = TimeUnit.MILLISECONDS) + void failsIfExecutionTimeExceeds500Milliseconds() { + // fails if execution time exceeds 500 milliseconds + } + + @Test + @Timeout(value = 500, unit = TimeUnit.MILLISECONDS, threadMode = ThreadMode.SEPARATE_THREAD) + void failsIfExecutionTimeExceeds500MillisecondsInSeparateThread() { + // fails if execution time exceeds 500 milliseconds, the test code is executed in a separate thread } } diff --git a/documentation/src/test/java/example/UsingTheLauncherDemo.java b/documentation/src/test/java/example/UsingTheLauncherDemo.java index e7e2dbe001b3..a4629c04a111 100644 --- a/documentation/src/test/java/example/UsingTheLauncherDemo.java +++ b/documentation/src/test/java/example/UsingTheLauncherDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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 @@ -19,8 +19,14 @@ import java.nio.file.Path; import java.nio.file.Paths; +import org.junit.platform.engine.FilterResult; +import org.junit.platform.engine.TestDescriptor; import org.junit.platform.launcher.Launcher; +import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.LauncherSession; +import org.junit.platform.launcher.LauncherSessionListener; +import org.junit.platform.launcher.PostDiscoveryFilter; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; import org.junit.platform.launcher.core.LauncherConfig; @@ -51,13 +57,16 @@ void discovery() { ) .build(); - Launcher launcher = LauncherFactory.create(); + try (LauncherSession session = LauncherFactory.openSession()) { + TestPlan testPlan = session.getLauncher().discover(request); - TestPlan testPlan = launcher.discover(request); + // ... discover additional test plans or execute tests + } // end::discovery[] // @formatter:on } + @org.junit.jupiter.api.Tag("exclude") @org.junit.jupiter.api.Test @SuppressWarnings("unused") void execution() { @@ -73,16 +82,22 @@ void execution() { ) .build(); - Launcher launcher = LauncherFactory.create(); - - // Register a listener of your choice SummaryGeneratingListener listener = new SummaryGeneratingListener(); - launcher.registerTestExecutionListeners(listener); - launcher.execute(request); + try (LauncherSession session = LauncherFactory.openSession()) { + Launcher launcher = session.getLauncher(); + // Register a listener of your choice + launcher.registerTestExecutionListeners(listener); + // Discover tests and build a test plan + TestPlan testPlan = launcher.discover(request); + // Execute test plan + launcher.execute(testPlan); + // Alternatively, execute the request directly + launcher.execute(request); + } TestExecutionSummary summary = listener.getSummary(); - // Do something with the TestExecutionSummary. + // Do something with the summary... // end::execution[] // @formatter:on @@ -96,19 +111,25 @@ void launcherConfig() { // tag::launcherConfig[] LauncherConfig launcherConfig = LauncherConfig.builder() .enableTestEngineAutoRegistration(false) + .enableLauncherSessionListenerAutoRegistration(false) + .enableLauncherDiscoveryListenerAutoRegistration(false) + .enablePostDiscoveryFilterAutoRegistration(false) .enableTestExecutionListenerAutoRegistration(false) .addTestEngines(new CustomTestEngine()) + .addLauncherSessionListeners(new CustomLauncherSessionListener()) + .addLauncherDiscoveryListeners(new CustomLauncherDiscoveryListener()) + .addPostDiscoveryFilters(new CustomPostDiscoveryFilter()) .addTestExecutionListeners(new LegacyXmlReportGeneratingListener(reportsDir, out)) .addTestExecutionListeners(new CustomTestExecutionListener()) .build(); - Launcher launcher = LauncherFactory.create(launcherConfig); - LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() .selectors(selectPackage("com.example.mytests")) .build(); - launcher.execute(request); + try (LauncherSession session = LauncherFactory.openSession(launcherConfig)) { + session.getLauncher().execute(request); + } // end::launcherConfig[] // @formatter:on } @@ -120,3 +141,16 @@ class MyTestClass { class CustomTestExecutionListener implements TestExecutionListener { } + +class CustomLauncherSessionListener implements LauncherSessionListener { +} + +class CustomLauncherDiscoveryListener implements LauncherDiscoveryListener { +} + +class CustomPostDiscoveryFilter implements PostDiscoveryFilter { + @Override + public FilterResult apply(TestDescriptor object) { + return FilterResult.included("includes everything"); + } +} diff --git a/documentation/src/test/java/example/callbacks/AbstractDatabaseTests.java b/documentation/src/test/java/example/callbacks/AbstractDatabaseTests.java index aec3d20ec30a..8010fd31c696 100644 --- a/documentation/src/test/java/example/callbacks/AbstractDatabaseTests.java +++ b/documentation/src/test/java/example/callbacks/AbstractDatabaseTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/callbacks/BrokenLifecycleMethodConfigDemo.java b/documentation/src/test/java/example/callbacks/BrokenLifecycleMethodConfigDemo.java index 7d3ed08f75eb..6e4880eb4537 100644 --- a/documentation/src/test/java/example/callbacks/BrokenLifecycleMethodConfigDemo.java +++ b/documentation/src/test/java/example/callbacks/BrokenLifecycleMethodConfigDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/callbacks/DatabaseTestsDemo.java b/documentation/src/test/java/example/callbacks/DatabaseTestsDemo.java index aadda9995f2c..b7eb652907b4 100644 --- a/documentation/src/test/java/example/callbacks/DatabaseTestsDemo.java +++ b/documentation/src/test/java/example/callbacks/DatabaseTestsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/callbacks/Extension1.java b/documentation/src/test/java/example/callbacks/Extension1.java index a02451794e86..b1658aa451dc 100644 --- a/documentation/src/test/java/example/callbacks/Extension1.java +++ b/documentation/src/test/java/example/callbacks/Extension1.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/callbacks/Extension2.java b/documentation/src/test/java/example/callbacks/Extension2.java index ea0cf0467eb7..b7ad2849598a 100644 --- a/documentation/src/test/java/example/callbacks/Extension2.java +++ b/documentation/src/test/java/example/callbacks/Extension2.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/callbacks/Logger.java b/documentation/src/test/java/example/callbacks/Logger.java index e3e3a76dcb65..7bfe2399bc5b 100644 --- a/documentation/src/test/java/example/callbacks/Logger.java +++ b/documentation/src/test/java/example/callbacks/Logger.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/defaultmethods/ComparableContract.java b/documentation/src/test/java/example/defaultmethods/ComparableContract.java index 0002836ebada..c5580d303d80 100644 --- a/documentation/src/test/java/example/defaultmethods/ComparableContract.java +++ b/documentation/src/test/java/example/defaultmethods/ComparableContract.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/defaultmethods/EqualsContract.java b/documentation/src/test/java/example/defaultmethods/EqualsContract.java index 47e1252d6236..c986a0b6f619 100644 --- a/documentation/src/test/java/example/defaultmethods/EqualsContract.java +++ b/documentation/src/test/java/example/defaultmethods/EqualsContract.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/defaultmethods/StringTests.java b/documentation/src/test/java/example/defaultmethods/StringTests.java index 965100debfbd..85aaac73afb1 100644 --- a/documentation/src/test/java/example/defaultmethods/StringTests.java +++ b/documentation/src/test/java/example/defaultmethods/StringTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/defaultmethods/Testable.java b/documentation/src/test/java/example/defaultmethods/Testable.java index 273a225c20ff..f1ac340faf34 100644 --- a/documentation/src/test/java/example/defaultmethods/Testable.java +++ b/documentation/src/test/java/example/defaultmethods/Testable.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/exception/IgnoreIOExceptionExtension.java b/documentation/src/test/java/example/exception/IgnoreIOExceptionExtension.java index 28505bf3e7a9..266f39292cc8 100644 --- a/documentation/src/test/java/example/exception/IgnoreIOExceptionExtension.java +++ b/documentation/src/test/java/example/exception/IgnoreIOExceptionExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/exception/IgnoreIOExceptionTests.java b/documentation/src/test/java/example/exception/IgnoreIOExceptionTests.java index f4de1484fad7..6df8ce4a72e0 100644 --- a/documentation/src/test/java/example/exception/IgnoreIOExceptionTests.java +++ b/documentation/src/test/java/example/exception/IgnoreIOExceptionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/exception/MultipleHandlersTestCase.java b/documentation/src/test/java/example/exception/MultipleHandlersTestCase.java index 48ee9ec88d7d..709231a6bcaa 100644 --- a/documentation/src/test/java/example/exception/MultipleHandlersTestCase.java +++ b/documentation/src/test/java/example/exception/MultipleHandlersTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/exception/RecordStateOnErrorExtension.java b/documentation/src/test/java/example/exception/RecordStateOnErrorExtension.java index 3f0422107036..5b2804684915 100644 --- a/documentation/src/test/java/example/exception/RecordStateOnErrorExtension.java +++ b/documentation/src/test/java/example/exception/RecordStateOnErrorExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/extensions/Random.java b/documentation/src/test/java/example/extensions/Random.java new file mode 100644 index 000000000000..9d83f3d4da94 --- /dev/null +++ b/documentation/src/test/java/example/extensions/Random.java @@ -0,0 +1,26 @@ +/* + * 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 example.extensions; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +//tag::user_guide[] +@Target({ ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(RandomNumberExtension.class) +public @interface Random { +} +//end::user_guide[] diff --git a/documentation/src/test/java/example/extensions/RandomNumberDemo.java b/documentation/src/test/java/example/extensions/RandomNumberDemo.java new file mode 100644 index 000000000000..24db76bc013f --- /dev/null +++ b/documentation/src/test/java/example/extensions/RandomNumberDemo.java @@ -0,0 +1,41 @@ +/* + * 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 example.extensions; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +@Disabled("RandomNumberExtension has not been implemented") +//tag::user_guide[] +class RandomNumberDemo { + + // use random number field in test methods and @BeforeEach + // or @AfterEach lifecycle methods + @Random + private int randomNumber1; + + RandomNumberDemo(@Random int randomNumber2) { + // use random number in constructor + } + + @BeforeEach + void beforeEach(@Random int randomNumber3) { + // use random number in @BeforeEach method + } + + @Test + void test(@Random int randomNumber4) { + // use random number in test method + } + +} +//end::user_guide[] diff --git a/documentation/src/test/java/example/extensions/RandomNumberExtension.java b/documentation/src/test/java/example/extensions/RandomNumberExtension.java new file mode 100644 index 000000000000..f3ed2c9f2631 --- /dev/null +++ b/documentation/src/test/java/example/extensions/RandomNumberExtension.java @@ -0,0 +1,39 @@ +/* + * 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 example.extensions; + +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; + +class RandomNumberExtension implements BeforeAllCallback, BeforeEachCallback, ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return false; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return null; + } + + @Override + public void beforeAll(ExtensionContext context) { + } + + @Override + public void beforeEach(ExtensionContext context) { + } + +} diff --git a/documentation/src/test/java/example/interceptor/SwingEdtInterceptor.java b/documentation/src/test/java/example/interceptor/SwingEdtInterceptor.java index 78f21e8e3b66..5970b80b9dd7 100644 --- a/documentation/src/test/java/example/interceptor/SwingEdtInterceptor.java +++ b/documentation/src/test/java/example/interceptor/SwingEdtInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/registration/DocumentationDemo.java b/documentation/src/test/java/example/registration/DocumentationDemo.java index fe542cf37c28..c5cf431bce48 100644 --- a/documentation/src/test/java/example/registration/DocumentationDemo.java +++ b/documentation/src/test/java/example/registration/DocumentationDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/registration/WebServerDemo.java b/documentation/src/test/java/example/registration/WebServerDemo.java index c99d9c0e70f6..0ade52412975 100644 --- a/documentation/src/test/java/example/registration/WebServerDemo.java +++ b/documentation/src/test/java/example/registration/WebServerDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/session/GlobalSetupTeardownListener.java b/documentation/src/test/java/example/session/GlobalSetupTeardownListener.java new file mode 100644 index 000000000000..6d3417817d45 --- /dev/null +++ b/documentation/src/test/java/example/session/GlobalSetupTeardownListener.java @@ -0,0 +1,92 @@ +/* + * 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 example.session; + +//tag::user_guide[] +import static java.net.InetAddress.getLoopbackAddress; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.InetSocketAddress; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import com.sun.net.httpserver.HttpServer; + +import org.junit.platform.launcher.LauncherSession; +import org.junit.platform.launcher.LauncherSessionListener; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestPlan; + +public class GlobalSetupTeardownListener implements LauncherSessionListener { + + private Fixture fixture; + + @Override + public void launcherSessionOpened(LauncherSession session) { + // Avoid setup for test discovery by delaying it until tests are about to be executed + session.getLauncher().registerTestExecutionListeners(new TestExecutionListener() { + @Override + public void testPlanExecutionStarted(TestPlan testPlan) { + //end::user_guide[] + if (!testPlan.getConfigurationParameters().getBoolean("enableHttpServer").orElse(false)) { + // avoid starting multiple HTTP servers unnecessarily from UsingTheLauncherDemo + return; + } + //tag::user_guide[] + if (fixture == null) { + fixture = new Fixture(); + fixture.setUp(); + } + } + }); + } + + @Override + public void launcherSessionClosed(LauncherSession session) { + if (fixture != null) { + fixture.tearDown(); + fixture = null; + } + } + + static class Fixture { + + private HttpServer server; + private ExecutorService executorService; + + void setUp() { + try { + server = HttpServer.create(new InetSocketAddress(getLoopbackAddress(), 0), 0); + } + catch (IOException e) { + throw new UncheckedIOException("Failed to start HTTP server", e); + } + server.createContext("/test", exchange -> { + exchange.sendResponseHeaders(204, -1); + exchange.close(); + }); + executorService = Executors.newCachedThreadPool(); + server.setExecutor(executorService); + server.start(); // <1> + int port = server.getAddress().getPort(); + System.setProperty("http.server.host", getLoopbackAddress().getHostAddress()); // <2> + System.setProperty("http.server.port", String.valueOf(port)); // <3> + } + + void tearDown() { + server.stop(0); // <4> + executorService.shutdownNow(); + } + } + +} +//end::user_guide[] diff --git a/documentation/src/test/java/example/session/HttpTests.java b/documentation/src/test/java/example/session/HttpTests.java new file mode 100644 index 000000000000..01477b6b0d75 --- /dev/null +++ b/documentation/src/test/java/example/session/HttpTests.java @@ -0,0 +1,36 @@ +/* + * 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 example.session; + +//tag::user_guide[] +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.net.HttpURLConnection; +import java.net.URL; + +import org.junit.jupiter.api.Test; + +class HttpTests { + + @Test + void respondsWith204() throws Exception { + String host = System.getProperty("http.server.host"); // <1> + String port = System.getProperty("http.server.port"); // <2> + URL url = new URL("http://" + host + ":" + port + "/test"); + + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + int responseCode = connection.getResponseCode(); // <3> + + assertEquals(204, responseCode); // <4> + } +} +//end::user_guide[] diff --git a/documentation/src/test/java/example/testinterface/TestInterfaceDemo.java b/documentation/src/test/java/example/testinterface/TestInterfaceDemo.java index 86b64961afb3..e37ee55e4bf2 100644 --- a/documentation/src/test/java/example/testinterface/TestInterfaceDemo.java +++ b/documentation/src/test/java/example/testinterface/TestInterfaceDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/testinterface/TestInterfaceDynamicTestsDemo.java b/documentation/src/test/java/example/testinterface/TestInterfaceDynamicTestsDemo.java index ee611239e61d..41cce3456765 100644 --- a/documentation/src/test/java/example/testinterface/TestInterfaceDynamicTestsDemo.java +++ b/documentation/src/test/java/example/testinterface/TestInterfaceDynamicTestsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/testinterface/TestLifecycleLogger.java b/documentation/src/test/java/example/testinterface/TestLifecycleLogger.java index d6dae294e16a..1d4ccbe818a1 100644 --- a/documentation/src/test/java/example/testinterface/TestLifecycleLogger.java +++ b/documentation/src/test/java/example/testinterface/TestLifecycleLogger.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/testinterface/TimeExecutionLogger.java b/documentation/src/test/java/example/testinterface/TimeExecutionLogger.java index 944e7c72ada7..1f0d28276e6e 100644 --- a/documentation/src/test/java/example/testinterface/TimeExecutionLogger.java +++ b/documentation/src/test/java/example/testinterface/TimeExecutionLogger.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/testkit/EngineTestKitAllEventsDemo.java b/documentation/src/test/java/example/testkit/EngineTestKitAllEventsDemo.java index 21f05d439caf..2a8991c45f93 100644 --- a/documentation/src/test/java/example/testkit/EngineTestKitAllEventsDemo.java +++ b/documentation/src/test/java/example/testkit/EngineTestKitAllEventsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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 @@ -41,7 +41,7 @@ class EngineTestKitAllEventsDemo { void verifyAllJupiterEvents() { Writer writer = // create a java.io.Writer for debug output // end::user_guide[] - // For the demo, we are simply swallowing the debug output. + // For the demo, we are swallowing the debug output. new StringWriter(); // tag::user_guide[] diff --git a/documentation/src/test/java/example/testkit/EngineTestKitFailedMethodDemo.java b/documentation/src/test/java/example/testkit/EngineTestKitFailedMethodDemo.java index 78d2173f2ebd..fe13c967edb0 100644 --- a/documentation/src/test/java/example/testkit/EngineTestKitFailedMethodDemo.java +++ b/documentation/src/test/java/example/testkit/EngineTestKitFailedMethodDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/testkit/EngineTestKitSkippedMethodDemo.java b/documentation/src/test/java/example/testkit/EngineTestKitSkippedMethodDemo.java index 708dfe598c38..401e76881bb7 100644 --- a/documentation/src/test/java/example/testkit/EngineTestKitSkippedMethodDemo.java +++ b/documentation/src/test/java/example/testkit/EngineTestKitSkippedMethodDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/testkit/EngineTestKitStatisticsDemo.java b/documentation/src/test/java/example/testkit/EngineTestKitStatisticsDemo.java index ef3ca601494b..0f0d92d5be0d 100644 --- a/documentation/src/test/java/example/testkit/EngineTestKitStatisticsDemo.java +++ b/documentation/src/test/java/example/testkit/EngineTestKitStatisticsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/timing/TimingExtension.java b/documentation/src/test/java/example/timing/TimingExtension.java index 6c4fcc73027e..c82321201a5e 100644 --- a/documentation/src/test/java/example/timing/TimingExtension.java +++ b/documentation/src/test/java/example/timing/TimingExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/timing/TimingExtensionTests.java b/documentation/src/test/java/example/timing/TimingExtensionTests.java index b9b11a1582d6..cf84479e7450 100644 --- a/documentation/src/test/java/example/timing/TimingExtensionTests.java +++ b/documentation/src/test/java/example/timing/TimingExtensionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/extensions/ExpectToFail.java b/documentation/src/test/java/extensions/ExpectToFail.java index c325a81de5db..fb14a7d110eb 100644 --- a/documentation/src/test/java/extensions/ExpectToFail.java +++ b/documentation/src/test/java/extensions/ExpectToFail.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/org/junit/api/tools/AbstractApiReportWriter.java b/documentation/src/test/java/org/junit/api/tools/AbstractApiReportWriter.java index ed59e28e792b..29b2909d3cd7 100644 --- a/documentation/src/test/java/org/junit/api/tools/AbstractApiReportWriter.java +++ b/documentation/src/test/java/org/junit/api/tools/AbstractApiReportWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/org/junit/api/tools/ApiReport.java b/documentation/src/test/java/org/junit/api/tools/ApiReport.java index 067d5318f8eb..3d8a6fb498fc 100644 --- a/documentation/src/test/java/org/junit/api/tools/ApiReport.java +++ b/documentation/src/test/java/org/junit/api/tools/ApiReport.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/org/junit/api/tools/ApiReportGenerator.java b/documentation/src/test/java/org/junit/api/tools/ApiReportGenerator.java index 963d0c4553cb..637513538d5f 100644 --- a/documentation/src/test/java/org/junit/api/tools/ApiReportGenerator.java +++ b/documentation/src/test/java/org/junit/api/tools/ApiReportGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/org/junit/api/tools/ApiReportWriter.java b/documentation/src/test/java/org/junit/api/tools/ApiReportWriter.java index 8fcadb342744..30c430d05fe1 100644 --- a/documentation/src/test/java/org/junit/api/tools/ApiReportWriter.java +++ b/documentation/src/test/java/org/junit/api/tools/ApiReportWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/org/junit/api/tools/AsciidocApiReportWriter.java b/documentation/src/test/java/org/junit/api/tools/AsciidocApiReportWriter.java index 98f5f8fb2a51..ce688064103e 100644 --- a/documentation/src/test/java/org/junit/api/tools/AsciidocApiReportWriter.java +++ b/documentation/src/test/java/org/junit/api/tools/AsciidocApiReportWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/org/junit/api/tools/HtmlApiReportWriter.java b/documentation/src/test/java/org/junit/api/tools/HtmlApiReportWriter.java index b260f8ff1ce3..c27ca6b9cee0 100644 --- a/documentation/src/test/java/org/junit/api/tools/HtmlApiReportWriter.java +++ b/documentation/src/test/java/org/junit/api/tools/HtmlApiReportWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/org/junit/api/tools/MarkdownApiReportWriter.java b/documentation/src/test/java/org/junit/api/tools/MarkdownApiReportWriter.java index 65f0f918b2f8..a6296d1a3c1c 100644 --- a/documentation/src/test/java/org/junit/api/tools/MarkdownApiReportWriter.java +++ b/documentation/src/test/java/org/junit/api/tools/MarkdownApiReportWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/kotlin/example/FibonacciCalculator.kt b/documentation/src/test/kotlin/example/FibonacciCalculator.kt index ae16322fc335..281cb7d492ef 100644 --- a/documentation/src/test/kotlin/example/FibonacciCalculator.kt +++ b/documentation/src/test/kotlin/example/FibonacciCalculator.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/kotlin/example/KotlinAssertionsDemo.kt b/documentation/src/test/kotlin/example/KotlinAssertionsDemo.kt index 23c901fa1672..64afa0de38cd 100644 --- a/documentation/src/test/kotlin/example/KotlinAssertionsDemo.kt +++ b/documentation/src/test/kotlin/example/KotlinAssertionsDemo.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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 @@ -13,7 +13,6 @@ package example import example.domain.Person import example.util.Calculator -import java.time.Duration import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test @@ -22,6 +21,7 @@ import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.assertTimeout import org.junit.jupiter.api.assertTimeoutPreemptively +import java.time.Duration class KotlinAssertionsDemo { @@ -48,7 +48,8 @@ class KotlinAssertionsDemo { @Test fun `grouped assertions`() { - assertAll("Person properties", + assertAll( + "Person properties", { assertEquals("Jane", person.firstName) }, { assertEquals("Doe", person.lastName) } ) @@ -56,7 +57,8 @@ class KotlinAssertionsDemo { @Test fun `grouped assertions from a stream`() { - assertAll("People with first name starting with J", + assertAll( + "People with first name starting with J", people .stream() .map { @@ -68,7 +70,8 @@ class KotlinAssertionsDemo { @Test fun `grouped assertions from a collection`() { - assertAll("People with last name of Doe", + assertAll( + "People with last name of Doe", people.map { { assertEquals("Doe", it.lastName) } } ) } diff --git a/documentation/src/test/kotlin/example/registration/KotlinWebServerDemo.kt b/documentation/src/test/kotlin/example/registration/KotlinWebServerDemo.kt index 73b36a59365e..3f23480c7798 100644 --- a/documentation/src/test/kotlin/example/registration/KotlinWebServerDemo.kt +++ b/documentation/src/test/kotlin/example/registration/KotlinWebServerDemo.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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 @@ -17,11 +17,11 @@ import org.junit.jupiter.api.extension.RegisterExtension class KotlinWebServerDemo { companion object { - @JvmField + @JvmStatic @RegisterExtension val server = WebServerExtension.builder() - .enableSecurity(false) - .build() + .enableSecurity(false) + .build() } @Test diff --git a/documentation/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener b/documentation/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener new file mode 100644 index 000000000000..f6d297627eeb --- /dev/null +++ b/documentation/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener @@ -0,0 +1 @@ +example.session.GlobalSetupTeardownListener diff --git a/documentation/src/test/resources/two-column.csv b/documentation/src/test/resources/two-column.csv index 2ef0d215adac..7ebb4c545f1b 100644 --- a/documentation/src/test/resources/two-column.csv +++ b/documentation/src/test/resources/two-column.csv @@ -1,4 +1,5 @@ -Country, reference +COUNTRY, REFERENCE Sweden, 1 Poland, 2 "United States of America", 3 +France, 700_000 diff --git a/gradle.properties b/gradle.properties index 30e3b4488249..6ada11024510 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,58 +1,31 @@ group = org.junit -version = 5.7.0 +version = 5.9.1 jupiterGroup = org.junit.jupiter platformGroup = org.junit.platform -platformVersion = 1.7.0 +platformVersion = 1.9.1 vintageGroup = org.junit.vintage -vintageVersion = 5.7.0 +vintageVersion = 5.9.1 defaultBuiltBy = JUnit Team # We need more metaspace due to apparent memory leak in Asciidoctor/JRuby -org.gradle.jvmargs=-Xmx1g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError +# The exports are needed due to https://github.com/diffplug/spotless/issues/834 +org.gradle.jvmargs=-Xmx1g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError \ + --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED org.gradle.caching=true org.gradle.parallel=true +org.gradle.java.installations.fromEnv=JDK8,JDK18,JDK19,JDK20 -# Dependencies -apiguardian.version=1.1.0 -assertj.version=3.16.1 -junit4.version=4.13 -junit4Min.version=4.12 -opentest4j.version=1.2.0 -picocli.version=4.5.0 -univocity-parsers.version=2.9.0 +# Test Distribution +gradle.internal.testdistribution.writeTraceFile=true -# Test Dependencies -archunit.version=0.14.1 -bartholdy.version=0.2.3 -classgraph.version=4.8.87 -commons-io.version=2.7 -kotlinx-coroutines-core.version=1.3.9 -groovy.version=3.0.5 -log4j.version=2.13.3 -mockito.version=3.5.0 -slf4j.version=1.7.30 -spock.version=1.3-groovy-2.5 - -# Tools -checkstyle.version=8.31 -jacoco.version=0.8.5 -jmh.version=1.25 -ktlint.version=0.35.0 -surefire.version=2.22.2 -bnd.version=5.1.2 - -# Plugins -gradle.enterprise.plugin.version=3.3.1 -versioning.plugin.version=2.14.0 -versions.plugin.version=0.29.0 -spotless.plugin.version=5.1.1 -git-publish.plugin.version=2.1.3 -kotlin.plugin.version=1.4.0 -asciidoctor-pdf.version=1.5.3 -asciidoctor.plugin.version=3.2.0 -jmh.plugin.version=0.5.0 -nohttp.plugin.version=0.0.5.RELEASE +# Omit automatic compile dependency on kotlin-stdlib +# https://kotlinlang.org/docs/gradle.html#dependency-on-the-standard-library +kotlin.stdlib.default.dependency=false diff --git a/gradle/build-scan-user-data.gradle b/gradle/build-scan-user-data.gradle deleted file mode 100644 index 8436692dc512..000000000000 --- a/gradle/build-scan-user-data.gradle +++ /dev/null @@ -1,215 +0,0 @@ -// Source: https://github.com/gradle/gradle-build-scan-snippets/blob/master/guided-trials-default-custom-user-data/default-custom-user-data.gradle - -tagOs() -tagIde() -tagCiOrLocal() -addCiMetadata() -addGitMetadata() -addTestParallelization() - -// Add here other scripts, if needed -//apply from:"${rootProject.projectDir}/<>" - -void tagOs() { - buildScan.tag System.getProperty('os.name') -} - -void tagIde() { - if (project.hasProperty('android.injected.invoked.from.ide')) { - buildScan.tag 'Android Studio' - } else if (System.getProperty('idea.version')) { - buildScan.tag 'IntelliJ IDEA' - } else if (System.getProperty('eclipse.buildId')) { - buildScan.tag 'Eclipse' - } else if (!isCi()) { - buildScan.tag 'Cmd Line' - } -} - -void tagCiOrLocal() { - buildScan.tag(isCi() ? 'CI' : 'LOCAL') -} - -void addCiMetadata() { - if (isJenkins()) { - if (System.getenv('BUILD_URL')) { - buildScan.link 'Jenkins build', System.getenv('BUILD_URL') - } - if (System.getenv('BUILD_NUMBER')) { - buildScan.value 'CI build number', System.getenv('BUILD_NUMBER') - } - if (System.getenv('NODE_NAME')) { - def agentName = System.getenv('NODE_NAME') == 'master' ? 'master-node' : System.getenv('NODE_NAME') - buildScan.tag agentName - buildScan.value 'CI node name', agentName - } - if (System.getenv('JOB_NAME')) { - def jobNameLabel = 'CI job' - def jobName = System.getenv('JOB_NAME') - buildScan.value jobNameLabel, jobName - addCustomValueSearchLink 'CI job build scans', [(jobNameLabel): jobName] - } - if (System.getenv('STAGE_NAME')) { - def stageNameLabel = 'CI stage' - def stageName = System.getenv('STAGE_NAME') - buildScan.value stageNameLabel, stageName - addCustomValueSearchLink 'CI stage build scans', [(stageNameLabel): stageName] - } - } - - if (isTeamCity()) { - def teamCityConfigurationFileProp = 'teamcity.configuration.properties.file' - if (project.hasProperty(teamCityConfigurationFileProp)) { - def properties = new Properties() - properties.load(new FileInputStream("${project.property(teamCityConfigurationFileProp)}")) - def teamCityServerUrl = properties.getProperty("teamcity.serverUrl") - if (teamCityServerUrl && project.hasProperty('build.number') && project.hasProperty('teamcity.buildType.id')) { - def teamCityBuildNumber = project.property('build.number') - def teamCityBuildTypeId = project.property('teamcity.buildType.id') - buildScan.link 'TeamCity build', "${appendIfMissing(teamCityServerUrl, '/')}viewLog.html?buildNumber=${teamCityBuildNumber}&buildTypeId=${teamCityBuildTypeId}" - } - } - if (project.hasProperty('build.number')) { - buildScan.value 'CI build number', project.property('build.number') - } - if (project.hasProperty('agent.name')) { - def agentName = project.property('agent.name') - buildScan.tag agentName - buildScan.value 'CI agent name', agentName - } - } - - if (isCircleCI()) { - if (System.getenv('CIRCLE_BUILD_URL')) { - buildScan.link 'CircleCI build', System.getenv('CIRCLE_BUILD_URL') - } - if (System.getenv('CIRCLE_BUILD_NUM')) { - buildScan.value 'CI build number', System.getenv('CIRCLE_BUILD_NUM') - } - if (System.getenv('CIRCLE_JOB')) { - def jobLabel = 'CI job' - def job = System.getenv('CIRCLE_JOB') - buildScan.value jobLabel, job - addCustomValueSearchLink 'CI job build scans', [(jobLabel): job] - } - if (System.getenv('CIRCLE_WORKFLOW_ID')) { - def workflowIdLabel = 'CI workflow' - def workflowId = System.getenv('CIRCLE_WORKFLOW_ID') - buildScan.value workflowIdLabel, workflowId - addCustomValueSearchLink 'CI workflow build scans', [(workflowIdLabel): workflowId] - } - } - - if (isBamboo()) { - if (System.getenv('bamboo_resultsUrl')) { - buildScan.link 'Bamboo build', System.getenv('bamboo_resultsUrl') - } - if (System.getenv('bamboo_buildNumber')) { - buildScan.value 'CI build number', System.getenv('bamboo_buildNumber') - } - if (System.getenv('bamboo_planName')) { - def planNameLabel = 'CI plan' - def planName = System.getenv('bamboo_planName') - buildScan.value planNameLabel, planName - addCustomValueSearchLink 'CI plan build scans', [(planNameLabel): planName] - } - if (System.getenv('bamboo_buildPlanName')) { - def jobNameLabel = 'CI job' - def jobName = System.getenv('bamboo_buildPlanName') - buildScan.value jobNameLabel, jobName - addCustomValueSearchLink 'CI job build scans', [(jobNameLabel): jobName] - } - if (System.getenv('bamboo_agentId')) { - def agentId = System.getenv('bamboo_agentId') - buildScan.tag agentId - buildScan.value 'CI agent ID', agentId - } - } - - if (isGitHubActions()) { - def repo = System.getenv('GITHUB_REPOSITORY') - def runId = System.getenv('GITHUB_RUN_ID') - if (repo && runId) { - buildScan.link 'GitHub Actions run', "https://github.com/$repo/actions/runs/$runId" - } - if (System.getenv('GITHUB_WORKFLOW')) { - def ghActionWorkflowLabel = 'GitHub workflow' - def ghActionWorkflowName = System.getenv('GITHUB_WORKFLOW') - buildScan.value ghActionWorkflowLabel, ghActionWorkflowName - addCustomValueSearchLink 'GitHub workflow build scans', [(ghActionWorkflowLabel): ghActionWorkflowName] - } - } -} - -void addGitMetadata() { - buildScan.background { - def gitCommitId = versioning.info.commit - def gitBranchName = versioning.info.branch - def gitDirty = versioning.info.dirty - - if (gitCommitId) { - def commitIdLabel = 'Git commit id' - value commitIdLabel, gitCommitId - addCustomValueSearchLink 'Git commit id build scans', [(commitIdLabel): gitCommitId] - link 'Github Source', "https://github.com/junit-team/junit5/tree/" + gitCommitId - } - if (gitBranchName) { - tag gitBranchName - value 'Git branch', gitBranchName - } - if (gitDirty) { - tag 'Dirty' - } - } -} - -void addTestParallelization() { - allprojects { p -> - p.tasks.withType(Test).configureEach { t -> doFirst { buildScan.value "${t.identityPath}#maxParallelForks", t.maxParallelForks.toString() } } - } -} - -static boolean isCi() { - isJenkins() || isTeamCity() || isCircleCI() || isBamboo() || isGitHubActions() -} - -static boolean isJenkins() { - System.getenv('JENKINS_URL') -} - -static boolean isTeamCity() { - System.getenv('TEAMCITY_VERSION') -} - -static boolean isCircleCI() { - System.getenv('CIRCLECI') -} - -static boolean isBamboo() { - System.getenv('bamboo_resultsUrl') -} - -static boolean isGitHubActions() { - System.getenv('GITHUB_ACTIONS') -} - -void addCustomValueSearchLink(String title, Map search) { - if (buildScan.server) { - buildScan.link title, customValueSearchUrl(search) - } -} - -String customValueSearchUrl(Map search) { - def query = search.collect { name, value -> - "search.names=${encodeURL(name)}&search.values=${encodeURL(value)}" - }.join('&') - "${appendIfMissing(buildScan.server, '/')}scans?$query#selection.buildScanB=%7BSCAN_ID%7D" -} - -static String encodeURL(String url) { - URLEncoder.encode(url, 'UTF-8') -} - -static String appendIfMissing(String str, String suffix) { - str.endsWith(suffix) ? str : str + suffix -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 000000000000..6bfa0b03406f --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,56 @@ +[versions] +ant = "1.10.12" +apiguardian = "1.1.2" +asciidoctor-pdf = "1.5.3" +assertj = "3.23.1" +checkstyle = "9.0" +jacoco = "0.8.7" +jmh = "1.33" +junit4 = "4.13.2" +junit4Osgi = "4.13.2_1" +junit4Min = "4.12" +ktlint = "0.43.0" +log4j = "2.17.1" +opentest4j = "1.2.0" +openTestReporting = "0.1.0-M1" +surefire = "2.22.2" +xmlunit = "2.8.4" + +[libraries] +ant = { module = "org.apache.ant:ant", version.ref = "ant" } +ant-junit = { module = "org.apache.ant:ant-junit", version.ref = "ant" } +ant-junitlauncher = { module = "org.apache.ant:ant-junitlauncher", version.ref = "ant" } +apiguardian = { module = "org.apiguardian:apiguardian-api", version.ref = "apiguardian" } +archunit = { module = "com.tngtech.archunit:archunit-junit5", version = "0.23.1" } +assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" } +bartholdy = { module = "de.sormuras:bartholdy", version = "0.2.3" } +bnd = { module = "biz.aQute.bnd:biz.aQute.bndlib", version = "6.3.1" } +classgraph = { module = "io.github.classgraph:classgraph", version = "4.8.132" } +commons-io = { module = "commons-io:commons-io", version = "2.11.0" } +groovy4 = { module = "org.apache.groovy:groovy", version = "4.0.1" } +groovy2-bom = { module = "org.codehaus.groovy:groovy-bom", version = "2.5.14" } +hamcrest = { module = "org.hamcrest:hamcrest", version = "2.2" } +jfrunit = { module = "org.moditect.jfrunit:jfrunit-core", version = "1.0.0.Alpha2" } +jmh-core = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" } +jmh-generator-annprocess = { module = "org.openjdk.jmh:jmh-generator-annprocess", version.ref = "jmh" } +joox = { module = "org.jooq:joox", version = "1.6.2" } +junit4 = { module = "junit:junit", version = { require = "[4.12,)", prefer = "4.13.2" } } +kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.4.3" } +log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } +log4j-jul = { module = "org.apache.logging.log4j:log4j-jul", version.ref = "log4j" } +maven = { module = "org.apache.maven:apache-maven", version = "3.8.5" } +mockito = { module = "org.mockito:mockito-junit-jupiter", version = "4.1.0" } +opentest4j = { module = "org.opentest4j:opentest4j", version.ref = "opentest4j" } +openTestReporting-events = { module = "org.opentest4j.reporting:open-test-reporting-events", version.ref = "openTestReporting" } +openTestReporting-tooling = { module = "org.opentest4j.reporting:open-test-reporting-tooling", version.ref = "openTestReporting" } +picocli = { module = "info.picocli:picocli", version = "4.6.2" } +slf4j-julBinding = { module = "org.slf4j:slf4j-jdk14", version = "1.7.32" } +spock1 = { module = "org.spockframework:spock-core", version = "1.3-groovy-2.5" } +univocity-parsers = { module = "com.univocity:univocity-parsers", version = "2.9.1" } +xmlunit-assertj = { module = "org.xmlunit:xmlunit-assertj3", version.ref = "xmlunit" } +xmlunit-placeholders = { module = "org.xmlunit:xmlunit-placeholders", version.ref = "xmlunit" } + +[bundles] +ant = ["ant", "ant-junit", "ant-junitlauncher"] +log4j = ["log4j-core", "log4j-jul"] +xmlunit = ["xmlunit-assertj", "xmlunit-placeholders"] diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023ec..249e5832f090 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4a1a99959e8b..b916c04dbb24 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=7873ed5287f47ca03549ab8dcb6dc877ac7f0e3d7b1eb12685161d10080910ac -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip +distributionSha256Sum=f6b8596b10cce501591e92f229816aa4046424f3b24d771751b06779d58c8ec4 +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0c811f..a69d9cb6c206 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,101 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 107acd32c4e6..f127cfd49d40 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/junit-bom/junit-bom.gradle.kts b/junit-bom/junit-bom.gradle.kts index 6d50be082030..fae23ee37eab 100644 --- a/junit-bom/junit-bom.gradle.kts +++ b/junit-bom/junit-bom.gradle.kts @@ -1,7 +1,6 @@ plugins { `java-platform` `publishing-conventions` - `custom-java-home` } description = "${rootProject.description} (Bill of Materials)" diff --git a/junit-jupiter-api/junit-jupiter-api.gradle.kts b/junit-jupiter-api/junit-jupiter-api.gradle.kts index f7a66e3c419b..d28dec7bf358 100644 --- a/junit-jupiter-api/junit-jupiter-api.gradle.kts +++ b/junit-jupiter-api/junit-jupiter-api.gradle.kts @@ -6,12 +6,27 @@ plugins { description = "JUnit Jupiter API" dependencies { - internal(platform(project(":dependencies"))) + api(platform(projects.junitBom)) + api(libs.opentest4j) + api(projects.junitPlatformCommons) - api(platform(project(":junit-bom"))) - api("org.apiguardian:apiguardian-api") - api("org.opentest4j:opentest4j") - api(project(":junit-platform-commons")) + compileOnlyApi(libs.apiguardian) - compileOnly("org.jetbrains.kotlin:kotlin-stdlib") + compileOnly(kotlin("stdlib")) + + osgiVerification(projects.junitJupiterEngine) + osgiVerification(projects.junitPlatformLauncher) +} + +tasks { + jar { + bundle { + bnd(""" + Require-Capability:\ + org.junit.platform.engine;\ + filter:='(&(org.junit.platform.engine=junit-jupiter)(version>=${'$'}{version_cleanup;${rootProject.property("version")!!}})(!(version>=${'$'}{versionmask;+;${'$'}{version_cleanup;${rootProject.property("version")!!}}})))';\ + effective:=active + """) + } + } } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java index 6ae092d20b64..d356d261c9ff 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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 @@ -27,22 +27,26 @@ *

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 T assertThrows(Class expectedType, Execut * thrown, this method will fail. * *

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

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

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

Usage Note

+ *

Usage Note

*

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

Usage Note

+ *

Usage Note

*

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

Usage Note

+ *

Usage Note

*

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

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

Usage Note

+ *

Usage Note

*

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

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

Usage Note

+ *

Usage Note

*

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

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

Usage Note

+ *

Usage Note

*

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

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

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

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

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

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

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

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

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

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @since 5.8 + */ + @API(status = EXPERIMENTAL, since = "5.8") + public static T assertInstanceOf(Class expectedType, Object actualValue, Supplier messageSupplier) { + return AssertInstanceOf.assertInstanceOf(expectedType, actualValue, messageSupplier); + } + + /** + * Factory for timeout failures. + * + * @param The type of error or exception created + * @since 5.9.1 + * @see Assertions#assertTimeoutPreemptively(Duration, ThrowingSupplier, Supplier, TimeoutFailureFactory) + */ + @API(status = INTERNAL, since = "5.9.1") + public interface TimeoutFailureFactory { + + /** + * Create a failure for the given timeout, message, and cause. + * + * @return timeout failure; never {@code null} + */ + T createTimeoutFailure(Duration timeout, Supplier messageSupplier, Throwable cause); + } } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java index b3ec15998f82..d98a44d254e2 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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 @@ -54,7 +54,7 @@ protected Assumptions() { /* no-op */ } - // --- assumeTrue ---------------------------------------------------- + // --- assumeTrue ---------------------------------------------------------- /** * Validate the given assumption. @@ -98,7 +98,7 @@ public static void assumeTrue(BooleanSupplier assumptionSupplier, String message */ public static void assumeTrue(boolean assumption, Supplier messageSupplier) throws TestAbortedException { if (!assumption) { - throwTestAbortedException(messageSupplier.get()); + throwAssumptionFailed(messageSupplier.get()); } } @@ -112,7 +112,7 @@ public static void assumeTrue(boolean assumption, Supplier messageSuppli */ public static void assumeTrue(boolean assumption, String message) throws TestAbortedException { if (!assumption) { - throwTestAbortedException(message); + throwAssumptionFailed(message); } } @@ -130,7 +130,7 @@ public static void assumeTrue(BooleanSupplier assumptionSupplier, Supplier messageSupplier) throws TestAbortedException { if (assumption) { - throwTestAbortedException(messageSupplier.get()); + throwAssumptionFailed(messageSupplier.get()); } } @@ -188,7 +188,7 @@ public static void assumeFalse(boolean assumption, Supplier messageSuppl */ public static void assumeFalse(boolean assumption, String message) throws TestAbortedException { if (assumption) { - throwTestAbortedException(message); + throwAssumptionFailed(message); } } @@ -206,17 +206,18 @@ public static void assumeFalse(BooleanSupplier assumptionSupplier, SupplierIf the assumption is invalid, this method does nothing. - * - *

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

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

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

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

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

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

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

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

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

See Javadoc for {@link #abort(String)} for an explanation of this + * method's generic return type {@code V}. + * + * @param messageSupplier the supplier of the message to be included in the + * {@code TestAbortedException} + * @throws TestAbortedException always + * @since 5.9 + */ + @API(status = STABLE, since = "5.9") + public static V abort(Supplier messageSupplier) { + throw new TestAbortedException(messageSupplier.get()); + } + + private static void throwAssumptionFailed(String message) { throw new TestAbortedException( - StringUtils.isNotBlank(message) ? ("Assumption failed: " + message) : "Assumption failed"); + StringUtils.isNotBlank(message) ? "Assumption failed: " + message : "Assumption failed"); } } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java index 057cddd0ae30..9b3b2878281c 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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 @@ -27,21 +27,25 @@ *

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

Method Signatures

+ *

Method Signatures

* - *

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

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

Inheritance and Execution Order

+ *

Inheritance and Execution Order

* *

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

Composition

+ *

Composition

* *

{@code @BeforeAll} may be used as a meta-annotation in order to create * a custom composed annotation that inherits the semantics of diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeEach.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeEach.java index 4ea0738f2e59..8d8d2a0b4173 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeEach.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeEach.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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 @BeforeEach} 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 @BeforeEach} methods are inherited from superclasses as long as - * they are not overridden. Furthermore, {@code @BeforeEach} methods from - * superclasses will be executed before {@code @BeforeEach} methods in subclasses. + *

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

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

Composition

+ *

Composition

* *

{@code @BeforeEach} may be used as a meta-annotation in order to create * a custom composed annotation that inherits the semantics of diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassDescriptor.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassDescriptor.java new file mode 100644 index 000000000000..7496c1caa876 --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassDescriptor.java @@ -0,0 +1,87 @@ +/* + * 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.EXPERIMENTAL; + +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Optional; + +import org.apiguardian.api.API; + +/** + * {@code ClassDescriptor} encapsulates functionality for a given {@link Class}. + * + * @since 5.8 + * @see ClassOrdererContext + */ +@API(status = EXPERIMENTAL, since = "5.8") +public interface ClassDescriptor { + + /** + * Get the class for this descriptor. + * + * @return the class; never {@code null} + */ + Class getTestClass(); + + /** + * Get the display name for this descriptor's {@link #getTestClass() class}. + * + * @return the display name for this descriptor's class; never {@code null} + * or blank + */ + String getDisplayName(); + + /** + * Determine if an annotation of {@code annotationType} is either + * present or meta-present on the {@link Class} for + * this descriptor. + * + * @param annotationType the annotation type to search for; never {@code null} + * @return {@code true} if the annotation is present or meta-present + * @see #findAnnotation(Class) + * @see #findRepeatableAnnotations(Class) + */ + boolean isAnnotated(Class annotationType); + + /** + * Find the first annotation of {@code annotationType} that is either + * present or meta-present on the {@link Class} for + * this descriptor. + * + * @param the annotation type + * @param annotationType the annotation type to search for; never {@code null} + * @return an {@code Optional} containing the annotation; never {@code null} but + * potentially empty + * @see #isAnnotated(Class) + * @see #findRepeatableAnnotations(Class) + */ + Optional findAnnotation(Class annotationType); + + /** + * Find all repeatable {@linkplain Annotation annotations} of + * {@code annotationType} that are either present or + * meta-present on the {@link Class} for this descriptor. + * + * @param the annotation type + * @param annotationType the repeatable annotation type to search for; never + * {@code null} + * @return the list of all such annotations found; neither {@code null} nor + * mutable, but potentially empty + * @see #isAnnotated(Class) + * @see #findAnnotation(Class) + * @see java.lang.annotation.Repeatable + */ + List findRepeatableAnnotations(Class annotationType); + +} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrderer.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrderer.java new file mode 100644 index 000000000000..0ebdbef03653 --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrderer.java @@ -0,0 +1,271 @@ +/* + * 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.util.Comparator.comparingInt; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.Collections; +import java.util.Comparator; +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; + +/** + * {@code ClassOrderer} defines the API for ordering top-level test classes and + * {@link Nested @Nested} test classes. + * + *

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

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

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

Built-in Implementations

+ * + *

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

    + *
  • {@link ClassOrderer.ClassName}
  • + *
  • {@link ClassOrderer.DisplayName}
  • + *
  • {@link ClassOrderer.OrderAnnotation}
  • + *
  • {@link ClassOrderer.Random}
  • + *
+ * + * @since 5.8 + * @see TestClassOrder + * @see ClassOrdererContext + * @see #orderClasses(ClassOrdererContext) + * @see MethodOrderer + */ +@API(status = EXPERIMENTAL, since = "5.8") +public interface ClassOrderer { + + /** + * Property name used to set the default class orderer class name: {@value} + * + *

Supported Values

+ * + *

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

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

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

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

+	 * public void orderClasses(ClassOrdererContext context) {
+	 *     Collections.shuffle(context.getClassDescriptors());
+	 * }
+	 * 
+ * + * @param context the {@code ClassOrdererContext} containing the + * {@linkplain ClassDescriptor class descriptors} to order; never {@code null} + */ + void orderClasses(ClassOrdererContext context); + + /** + * {@code ClassOrderer} that sorts classes alphanumerically based on their + * fully qualified names using {@link String#compareTo(String)}. + */ + class ClassName implements ClassOrderer { + + public ClassName() { + } + + /** + * Sort the classes encapsulated in the supplied + * {@link ClassOrdererContext} alphanumerically based on their fully + * qualified names. + */ + @Override + public void orderClasses(ClassOrdererContext context) { + context.getClassDescriptors().sort(comparator); + } + + private static final Comparator comparator = Comparator.comparing( + descriptor -> descriptor.getTestClass().getName()); + } + + /** + * {@code ClassOrderer} that sorts classes alphanumerically based on their + * display names using {@link String#compareTo(String)} + */ + class DisplayName implements ClassOrderer { + + public DisplayName() { + } + + /** + * Sort the classes encapsulated in the supplied + * {@link ClassOrdererContext} alphanumerically based on their display + * names. + */ + @Override + public void orderClasses(ClassOrdererContext context) { + context.getClassDescriptors().sort(comparator); + } + + private static final Comparator comparator = Comparator.comparing( + ClassDescriptor::getDisplayName); + } + + /** + * {@code ClassOrderer} that sorts classes based on the {@link Order @Order} + * annotation. + * + *

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

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

Custom Seed

+ * + *

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

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

Supported Values

+ * + *

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

If not specified or if the specified value cannot be converted to + * a {@link Long}, the default random seed will be used (see the + * {@linkplain Random class-level Javadoc} for details). + * + * @see MethodOrderer.Random + */ + public static final String RANDOM_SEED_PROPERTY_NAME = MethodOrderer.Random.RANDOM_SEED_PROPERTY_NAME; + + public Random() { + } + + /** + * Order the classes encapsulated in the supplied + * {@link ClassOrdererContext} pseudo-randomly. + */ + @Override + public void orderClasses(ClassOrdererContext context) { + Collections.shuffle(context.getClassDescriptors(), + new java.util.Random(getCustomSeed(context).orElse(DEFAULT_SEED))); + } + + private Optional getCustomSeed(ClassOrdererContext context) { + return context.getConfigurationParameter(RANDOM_SEED_PROPERTY_NAME).map(configurationParameter -> { + Long seed = null; + try { + seed = Long.valueOf(configurationParameter); + logger.config( + () -> String.format("Using custom seed for configuration parameter [%s] with value [%s].", + RANDOM_SEED_PROPERTY_NAME, configurationParameter)); + } + catch (NumberFormatException ex) { + logger.warn(ex, + () -> String.format( + "Failed to convert configuration parameter [%s] with value [%s] to a long. " + + "Using default seed [%s] as fallback.", + RANDOM_SEED_PROPERTY_NAME, configurationParameter, DEFAULT_SEED)); + } + return seed; + }); + } + } + +} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrdererContext.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrdererContext.java new file mode 100644 index 000000000000..cc4d169eade8 --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrdererContext.java @@ -0,0 +1,56 @@ +/* + * 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.EXPERIMENTAL; + +import java.util.List; +import java.util.Optional; + +import org.apiguardian.api.API; + +/** + * {@code ClassOrdererContext} encapsulates the context in which + * a {@link ClassOrderer} will be invoked. + * + * @since 5.8 + * @see ClassOrderer + * @see ClassDescriptor + */ +@API(status = EXPERIMENTAL, since = "5.8") +public interface ClassOrdererContext { + + /** + * Get the list of {@linkplain ClassDescriptor class descriptors} to + * order. + * + * @return the list of class descriptors; never {@code null} + */ + List getClassDescriptors(); + + /** + * Get the configuration parameter stored under the specified {@code key}. + * + *

If no such key is present in the {@code ConfigurationParameters} for + * the JUnit Platform, an attempt will be made to look up the value as a + * JVM system property. If no such system property exists, an attempt will + * be made to look up the value in the JUnit Platform properties file. + * + * @param key the key to look up; never {@code null} or blank + * @return an {@code Optional} containing the value; never {@code null} + * but potentially empty + * + * @see System#getProperty(String) + * @see org.junit.platform.engine.ConfigurationParameters + */ + Optional getConfigurationParameter(String key); + +} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java index 6b784a958494..88b6f16d4b54 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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 @@ -48,6 +48,8 @@ * @see org.junit.jupiter.api.condition.DisabledForJreRange * @see org.junit.jupiter.api.condition.EnabledOnOs * @see org.junit.jupiter.api.condition.DisabledOnOs + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.extension.ExecutionCondition */ @Target({ ElementType.TYPE, ElementType.METHOD }) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayName.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayName.java index 9f6e96937ba7..5b250299829e 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayName.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayName.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGeneration.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGeneration.java index 237d9b5e1002..04adf0b37798 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGeneration.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGeneration.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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 @@ -25,9 +25,20 @@ * {@code @DisplayNameGeneration} is used to declare a custom display name * generator for the annotated test class. * + *

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

As an alternative to {@code @DisplayNameGeneration}, a global + * {@link DisplayNameGenerator} can be configured for the entire test suite via + * the {@value DisplayNameGenerator#DEFAULT_GENERATOR_PROPERTY_NAME} configuration parameter. See + * the User Guide for details. Note, however, that a {@code @DisplayNameGeneration} + * declaration always overrides a global {@code DisplayNameGenerator}. + * * @since 5.4 * @see DisplayName * @see DisplayNameGenerator + * @see IndicativeSentencesGeneration */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java index 42bb9821057c..e23fd31e39db 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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,10 +12,12 @@ import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; -import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; +import static org.junit.platform.commons.support.ModifierSupport.isStatic; +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; import java.lang.reflect.Method; import java.util.Optional; +import java.util.function.Predicate; import org.apiguardian.api.API; import org.junit.platform.commons.util.ClassUtils; @@ -31,6 +33,20 @@ * *

Concrete implementations must have a default constructor. * + *

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

Built-in Implementations

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

Supported Values

+ * + *

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

If not specified, the default is + * {@link DisplayNameGenerator.Standard}. + * + * @since 5.5 + */ + @API(status = STABLE, since = "5.9") + String DEFAULT_GENERATOR_PROPERTY_NAME = "junit.jupiter.displayname.generator.default"; + /** * Generate a display name for the given top-level or {@code static} nested test class. * @@ -92,6 +125,9 @@ class Standard implements DisplayNameGenerator { static final DisplayNameGenerator INSTANCE = new Standard(); + public Standard() { + } + @Override public String generateDisplayNameForClass(Class testClass) { String name = testClass.getName(); @@ -122,6 +158,9 @@ class Simple extends Standard { static final DisplayNameGenerator INSTANCE = new Simple(); + public Simple() { + } + @Override public String generateDisplayNameForMethod(Class testClass, Method testMethod) { String displayName = testMethod.getName(); @@ -148,6 +187,9 @@ class ReplaceUnderscores extends Simple { static final DisplayNameGenerator INSTANCE = new ReplaceUnderscores(); + public ReplaceUnderscores() { + } + @Override public String generateDisplayNameForClass(Class testClass) { return replaceUnderscores(super.generateDisplayNameForClass(testClass)); @@ -172,11 +214,12 @@ private static String replaceUnderscores(String name) { /** * {@code DisplayNameGenerator} that generates complete sentences. * - *

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

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

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

If {@link IndicativeSentencesGeneration @IndicativeSentencesGeneration} + * is present (searching enclosing classes if not found locally), the + * configured {@link IndicativeSentencesGeneration#generator() generator} + * will be used. Otherwise, {@link IndicativeSentencesGeneration#DEFAULT_GENERATOR} + * will be used. * - * @param testClass Class to get Indicative sentence generator either custom or default - * @return the {@code DisplayNameGenerator} instance to use in indicative sentences generator + * @param testClass the test class to search on for {@code @IndicativeSentencesGeneration} + * @return the {@code DisplayNameGenerator} instance to use */ - private DisplayNameGenerator getGeneratorForIndicativeSentence(Class testClass) { - Optional indicativeSentencesGeneration = getIndicativeSentencesGeneration( - testClass); - if (indicativeSentencesGeneration.isPresent()) { - DisplayNameGenerator displayNameGenerator = getDisplayNameGenerator( - indicativeSentencesGeneration.get().generator()); - if (displayNameGenerator.getClass() == IndicativeSentences.class) { - return getDisplayNameGenerator(IndicativeSentencesGeneration.DEFAULT_GENERATOR); - } - return displayNameGenerator; - } - - return getDisplayNameGenerator(IndicativeSentencesGeneration.DEFAULT_GENERATOR); + private static DisplayNameGenerator getGeneratorFor(Class testClass) { + return findIndicativeSentencesGeneration(testClass)// + .map(IndicativeSentencesGeneration::generator)// + .filter(not(IndicativeSentences.class))// + .map(DisplayNameGenerator::getDisplayNameGenerator)// + .orElseGet(() -> getDisplayNameGenerator(IndicativeSentencesGeneration.DEFAULT_GENERATOR)); } /** - * Finds the {@code IndicativeSentencesGeneration} annotation that is present, - * meta-present or if it doesn't find it, then search for the enclosing - * parent classes, if no annotation is found returns empty. + * Find the first {@code DisplayNameGeneration} annotation that is either + * directly present, meta-present, or indirectly present + * on the supplied {@code testClass} or on an enclosing class. * - * @param testClass the test class to find the {@code IndicativeSentencesGeneration} - * annotation - * @return the optional annotation retrieved from the test class. + * @param testClass the test class on which to find the annotation; never {@code null} + * @return an {@code Optional} containing the annotation, potentially empty if not found */ - private Optional getIndicativeSentencesGeneration(Class testClass) { - Optional indicativeSentencesGeneration = findAnnotation(testClass, - IndicativeSentencesGeneration.class); + private static Optional findDisplayNameGeneration(Class testClass) { + return findAnnotation(testClass, DisplayNameGeneration.class, true); + } - if (indicativeSentencesGeneration.isPresent()) { - return indicativeSentencesGeneration; - } - if (testClass.getEnclosingClass() != null) { - return getIndicativeSentencesGeneration(testClass.getEnclosingClass()); - } + /** + * Find the first {@code IndicativeSentencesGeneration} annotation that is either + * directly present, meta-present, or indirectly present + * on the supplied {@code testClass} or on an enclosing class. + * + * @param testClass the test class on which to find the annotation; never {@code null} + * @return an {@code Optional} containing the annotation, potentially empty if not found + */ + private static Optional findIndicativeSentencesGeneration(Class testClass) { + return findAnnotation(testClass, IndicativeSentencesGeneration.class, true); + } - return Optional.empty(); + private static Predicate> not(Class clazz) { + return ((Predicate>) clazz::equals).negate(); } + } /** diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java index 09ba38d6edeb..bae75ee8304d 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java index 3dab77739a5b..819f1e2b0f52 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java index b4b1ef0c350c..62a018985741 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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 @@ -155,6 +155,77 @@ public static Stream stream(Stream inputStream, .map(input -> dynamicTest(displayNameGenerator.apply(input), () -> testExecutor.accept(input))); } + /** + * Generate a stream of dynamic tests based on the given generator and test + * executor. + * + *

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

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

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

The given {@code inputStream} is responsible for supplying input values + * and display names. A {@link DynamicTest} will be added to the resulting stream for + * each dynamically supplied input value, using the given {@code testExecutor}. + * + * @param inputStream a {@code Stream} that supplies dynamic {@code Named} + * input values; never {@code null} + * @param testExecutor a consumer that executes a test based on an input + * value; never {@code null} + * @param the type of input supplied by the {@code inputStream} + * and used by the {@code displayNameGenerator} and {@code testExecutor} + * @return a stream of dynamic tests based on the given generator and + * executor; never {@code null} + * @since 5.8 + * + * @see #dynamicTest(String, Executable) + * @see #stream(Iterator, ThrowingConsumer) + * @see Named + */ + @API(status = MAINTAINED, since = "5.8") + public static Stream stream(Stream> inputStream, + ThrowingConsumer testExecutor) { + Preconditions.notNull(inputStream, "inputStream must not be null"); + Preconditions.notNull(testExecutor, "testExecutor must not be null"); + + return inputStream // + .map(input -> dynamicTest(input.getName(), () -> testExecutor.accept(input.getPayload()))); + } + private final Executable executable; private DynamicTest(String displayName, URI testSourceUri, Executable executable) { diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/IndicativeSentencesGeneration.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/IndicativeSentencesGeneration.java index 3a5372eeb482..1f8c916d664a 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/IndicativeSentencesGeneration.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/IndicativeSentencesGeneration.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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 @@ -20,18 +20,29 @@ import java.lang.annotation.Target; import org.apiguardian.api.API; +import org.junit.jupiter.api.DisplayNameGenerator.IndicativeSentences; /** - * {@code @IndicativeSentencesGeneration} is used to declare a custom parameters - * by {@code IndicativeSentences}, if this notation has some not declared - * parameters, it will use the default values instead. + * {@code @IndicativeSentencesGeneration} is used to register the + * {@link IndicativeSentences} display name generator and configure it. + * + *

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

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

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

Defaults to {@link DisplayNameGenerator.Standard}. */ Class generator() default DisplayNameGenerator.Standard.class; + } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodDescriptor.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodDescriptor.java index ce0db501e3d6..256326781eec 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodDescriptor.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrderer.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrderer.java index 9e57b0b28dcd..89950bf4dfa4 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrderer.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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 @@ -34,7 +34,12 @@ * {@code @Test}, {@code @RepeatedTest}, {@code @ParameterizedTest}, * {@code @TestFactory}, or {@code @TestTemplate}. * - *

Built-in Implementations

+ *

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

Built-in Implementations

* *

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

Supported Values

+ * + *

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

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

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

- *

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

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

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

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

Custom Seed

+ *

Custom Seed

* *

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

Supported Values

+ *

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

Supported Values

* *

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

If not specified or if the specified value cannot be converted to * a {@link Long}, the default random seed will be used (see the * {@linkplain Random class-level Javadoc} for details). + * + * @see ClassOrderer.Random */ public static final String RANDOM_SEED_PROPERTY_NAME = "junit.jupiter.execution.order.random.seed"; + public Random() { + } + /** * Order the methods encapsulated in the supplied * {@link MethodOrdererContext} pseudo-randomly. diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrdererContext.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrdererContext.java index db9290c92408..970e0ba10620 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrdererContext.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrdererContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java new file mode 100644 index 000000000000..1b7cb774ce7d --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java @@ -0,0 +1,94 @@ +/* + * 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 org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; + +/** + * {@code Named} is a container that associates a name with a given payload. + * + * @param the type of the payload + * + * @since 5.8 + */ +@API(status = STABLE, since = "5.8") +public interface Named { + + /** + * Factory method for creating an instance of {@code Named} based on a + * {@code name} and a {@code payload}. + * + * @param name the name associated with the payload; never {@code null} or + * blank + * @param payload the object that serves as the payload; may be {@code null} + * depending on the use case + * @param the type of the payload + * @return an instance of {@code Named}; never {@code null} + * @see #named(String, java.lang.Object) + */ + static Named of(String name, T payload) { + Preconditions.notBlank(name, "name must not be null or blank"); + + return new Named() { + @Override + public String getName() { + return name; + } + + @Override + public T getPayload() { + return payload; + } + + @Override + public String toString() { + return name; + } + }; + } + + /** + * Factory method for creating an instance of {@code Named} based on a + * {@code name} and a {@code payload}. + * + *

This method is an alias for {@link Named#of} and is + * intended to be used when statically imported — for example, via: + * {@code import static org.junit.jupiter.api.Named.named;} + * + * @param name the name associated with the payload; never {@code null} or + * blank + * @param payload the object that serves as the payload; may be {@code null} + * depending on the use case + * @param the type of the payload + * @return an instance of {@code Named}; never {@code null} + */ + static Named named(String name, T payload) { + return of(name, payload); + } + + /** + * Get the name of the payload. + * + * @return the name of the payload; never {@code null} or blank + */ + String getName(); + + /** + * Get the payload. + * + * @return the payload; may be {@code null} depending on the use case + */ + T getPayload(); + +} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Nested.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Nested.java index e7f2f4c0dbb0..cde1fb6b07ec 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Nested.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Nested.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-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,7 +28,10 @@ * enclosing class}. The enclosing class may be a top-level test class or * another {@code @Nested} test class, and nesting can be arbitrarily deep. * - *

Test Instance Lifecycle

+ *

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

Test Instance Lifecycle

* *