diff --git a/.github/actions/run-gradle/action.yml b/.github/actions/run-gradle/action.yml
index e88d6ec9146f..39d9e5b68cc4 100644
--- a/.github/actions/run-gradle/action.yml
+++ b/.github/actions/run-gradle/action.yml
@@ -14,10 +14,10 @@ runs:
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
id: setup-gradle-jdk
with:
- distribution: oracle
+ distribution: temurin
java-version: 25
check-latest: true
- - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4
+ - uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
with:
cache-encryption-key: ${{ inputs.encryptionKey }}
- shell: bash
diff --git a/.github/actions/setup-test-jdk/action.yml b/.github/actions/setup-test-jdk/action.yml
index f5ad261ed559..efa6e8465aa1 100644
--- a/.github/actions/setup-test-jdk/action.yml
+++ b/.github/actions/setup-test-jdk/action.yml
@@ -4,17 +4,10 @@ inputs:
distribution:
required: true
description: 'The JDK distribution to use'
- default: 'liberica'
+ default: 'temurin'
runs:
using: "composite"
steps:
- - uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
- with:
- distribution: ${{ inputs.distribution }}
- java-version: 8
- check-latest: true
- - shell: bash
- run: echo "JDK8=$JAVA_HOME" >> $GITHUB_ENV # zizmor: ignore[github-env]
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
distribution: ${{ inputs.distribution }}
diff --git a/.github/workflows/close-inactive-issues.yml b/.github/workflows/close-inactive-issues.yml
index 4e71a9ef67a1..d223076df3ba 100644
--- a/.github/workflows/close-inactive-issues.yml
+++ b/.github/workflows/close-inactive-issues.yml
@@ -11,7 +11,7 @@ jobs:
issues: write
pull-requests: write
steps:
- - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0
+ - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
with:
only-labels: "status: waiting-for-feedback"
days-before-stale: 14
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 38427ceb5afe..1b1be88bcfd3 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -45,7 +45,7 @@ jobs:
with:
persist-credentials: false
- name: Initialize CodeQL
- uses: github/codeql-action/init@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
+ uses: github/codeql-action/init@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@@ -60,6 +60,6 @@ jobs:
-Dscan.tag.CodeQL \
classes
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
+ uses: github/codeql-action/analyze@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
with:
category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/cross-version.yml b/.github/workflows/cross-version.yml
index f20216d73246..a4fdb85f1396 100644
--- a/.github/workflows/cross-version.yml
+++ b/.github/workflows/cross-version.yml
@@ -29,12 +29,14 @@ jobs:
jdk:
- version: 25
type: ga
- distribution: oracle
- version: 26
type: ea
- version: 26
type: ea
- release: leydenpremain
+ release: leyden
+ - version: 26
+ type: ea
+ release: valhalla
name: "OpenJDK ${{ matrix.jdk.version }} (${{ matrix.jdk.release || matrix.jdk.type }})"
runs-on: ubuntu-latest
steps:
diff --git a/.github/workflows/gradle-dependency-submission.yml b/.github/workflows/gradle-dependency-submission.yml
index 861540390c5a..f22581a596fc 100644
--- a/.github/workflows/gradle-dependency-submission.yml
+++ b/.github/workflows/gradle-dependency-submission.yml
@@ -25,8 +25,8 @@ jobs:
- name: Setup Java
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
- distribution: oracle
+ distribution: temurin
java-version: 25
check-latest: true
- name: Generate and submit dependency graph
- uses: gradle/actions/dependency-submission@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4
+ uses: gradle/actions/dependency-submission@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index a9740bffef8f..336e0ff7f45d 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -31,7 +31,7 @@ jobs:
fetch-depth: 1
persist-credentials: false
- name: Install GraalVM
- uses: graalvm/setup-graalvm@e140024fdc2d95d3c7e10a636887a91090d29990 # v1.4.0
+ uses: graalvm/setup-graalvm@2a2412009026a83f51d179f92dc2b3fd4c8142df # v1.4.1
with:
distribution: graalvm-community
version: 'latest'
diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml
index 502b3356afe2..f4bb4c487950 100644
--- a/.github/workflows/ossf-scorecard.yml
+++ b/.github/workflows/ossf-scorecard.yml
@@ -26,7 +26,7 @@ jobs:
persist-credentials: false
- name: "Run analysis"
- uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
+ uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: results.sarif
results_format: sarif
@@ -57,6 +57,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard (optional).
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
- name: "Upload to code-scanning"
- uses: github/codeql-action/upload-sarif@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
+ uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
with:
sarif_file: results.sarif
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 16dba2750461..3b989eadeec6 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -92,8 +92,8 @@ jobs:
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
java-version: 25
- distribution: oracle
- - uses: sbt/setup-sbt@17575ea4e18dd928fe5968dbe32294b97923d65b # v1.1.13
+ distribution: temurin
+ - uses: sbt/setup-sbt@3e125ece5c3e5248e18da9ed8d2cce3d335ec8dd # v1.1.14
- name: Update JUnit dependencies in examples
run: java src/Updater.java ${RELEASE_VERSION}
working-directory: junit-examples
@@ -228,8 +228,8 @@ jobs:
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
java-version: 25
- distribution: oracle
- - uses: sbt/setup-sbt@17575ea4e18dd928fe5968dbe32294b97923d65b # v1.1.13
+ distribution: temurin
+ - uses: sbt/setup-sbt@3e125ece5c3e5248e18da9ed8d2cce3d335ec8dd # v1.1.14
- name: Update JUnit dependencies in examples
run: java src/Updater.java ${RELEASE_VERSION}
- name: Build examples
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index da9a350c8e4c..f1009b0955f8 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -89,6 +89,11 @@
+
+
+
+
+
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9b5fd3cd1caa..462c2cbef8d4 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -177,8 +177,8 @@ To deprecate an item:
## Building the Project
-Please refer to [the readme](README.md#building-from-source) for the most common
-build commands.
+Please refer to [the readme](README.md#building-from-source) and [the documentation readme](documentation/README.md) for
+the most common build commands.
### Build Parameters
diff --git a/README.md b/README.md
index ae6fe4db013c..08cea73aad5f 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ This repository is the home of JUnit Platform, Jupiter, and Vintage.
## Latest Releases
-- General Availability (GA): [JUnit 6.0.0](https://github.com/junit-team/junit-framework/releases/tag/r6.0.0) (September 30, 2025)
+- General Availability (GA): [JUnit 6.0.1](https://github.com/junit-team/junit-framework/releases/tag/r6.0.1) (October 31, 2025)
- Preview (Milestone/Release Candidate): N/A
## Documentation
@@ -84,9 +84,9 @@ projects via the following command:
## Dependency Metadata
-[](https://central.sonatype.com/search?namespace=org.junit.jupiter)
-[](https://central.sonatype.com/search?namespace=org.junit.vintage)
-[](https://central.sonatype.com/search?namespace=org.junit.platform)
+[](https://central.sonatype.com/search?namespace=org.junit.jupiter)
+[](https://central.sonatype.com/search?namespace=org.junit.vintage)
+[](https://central.sonatype.com/search?namespace=org.junit.platform)
Consult the [Dependency Metadata] section of the [User Guide] for a list of all artifacts
of the JUnit Platform, JUnit Jupiter, and JUnit Vintage.
diff --git a/RELEASING.md b/RELEASING.md
index fc1a2d7e201f..bdc52e9d24eb 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -29,6 +29,7 @@
### Feature releases (x.y.0)
+- [ ] Clear `accepted-breaking-changes.txt`, change `previousVersion` in `gradle.properties` to `x.y.0` on the release branch, and commit with message "Update API baseline and clear accepted breaking changes"
- [ ] Fast-forward merge the release branch to `main` and push to GitHub
- [ ] Update the [security policy](https://github.com/junit-team/junit-framework/blob/main/SECURITY.md) and commit with message "Update security policy to reflect 5.x release" or similar
- [ ] Create release notes for the next feature release from the template
@@ -37,5 +38,6 @@
### Patch releases (x.y.z)
- [ ] Cherry-pick the tagged commit from the release branch to `main` and resolve the conflict in `gradle.properties` by choosing the version of the `main` branch
+- [ ] Clear `accepted-breaking-changes.txt`, change `previousVersion` in `gradle.properties` to `x.y.z` on the release branch, and commit with message "Update API baseline and clear accepted breaking changes"
- [ ] Include the release notes of the patch release on `main` if not already present
- [ ] Update [JBang catalog](https://github.com/junit-team/jbang-catalog/blob/main/jbang-catalog.json)
diff --git a/documentation/README.md b/documentation/README.md
index 873a2ff282f6..acb46649d9d5 100644
--- a/documentation/README.md
+++ b/documentation/README.md
@@ -18,7 +18,7 @@ This following Gradle command generates the HTML version of the User Guide as
`build/docs/asciidoc/user-guide/index.html`.
```
-gradlew asciidoctor
+./gradlew asciidoctor
```
On Linux operating systems, the `graphviz` package providing `/usr/bin/dot` must be
@@ -30,5 +30,5 @@ This following Gradle command generates the PDF version of the User Guide to
`build/docs/asciidocPdf/user-guide/index.pdf`.
```
-gradlew asciidoctorPdf
+./gradlew asciidoctorPdf
```
diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts
index ce906c662b2b..fcc9276ca989 100644
--- a/documentation/documentation.gradle.kts
+++ b/documentation/documentation.gradle.kts
@@ -86,6 +86,7 @@ dependencies {
}
asciidoctorj {
+ setJrubyVersion(libs.versions.jruby)
modules {
pdf.version(libs.versions.asciidoctorj.pdf)
}
@@ -268,7 +269,7 @@ tasks {
outputs.cacheIf { true }
}
- val componentDiagram = plantUml.flatMap { it.outputDirectory.file("component-diagram.svg") }
+ val plantUmlOutputDirectory = plantUml.flatMap { it.outputDirectory }
withType().configureEach {
inputs.files(
@@ -278,7 +279,7 @@ tasks {
generateConsoleLauncherEnginesOptions,
generateApiTables,
generateStandaloneConsoleLauncherShadowedArtifactsFile,
- componentDiagram
+ plantUmlOutputDirectory
)
resources {
@@ -286,7 +287,7 @@ tasks {
include("**/images/**/*.png")
include("**/images/**/*.svg")
}
- from(componentDiagram) {
+ from(plantUmlOutputDirectory) {
into("user-guide/images")
}
}
diff --git a/documentation/src/docs/asciidoc/link-attributes.adoc b/documentation/src/docs/asciidoc/link-attributes.adoc
index fd942223b1aa..002e9b0585b3 100644
--- a/documentation/src/docs/asciidoc/link-attributes.adoc
+++ b/documentation/src/docs/asciidoc/link-attributes.adoc
@@ -224,9 +224,9 @@ endif::[]
:TestReporterParameterResolver: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestReporterParameterResolver.java[TestReporterParameterResolver]
:TypeBasedParameterResolver: {current-branch}/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolver.java[TypeBasedParameterResolver]
// Jupiter Examples
-:CustomAnnotationParameterResolver: {current-branch}/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotationParameterResolver.java[CustomAnnotationParameterResolver]
-:CustomTypeParameterResolver: {current-branch}/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomTypeParameterResolver.java[CustomTypeParameterResolver]
-:MapOfListsTypeBasedParameterResolver: {current-branch}/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfListsTypeBasedParameterResolver.java[MapOfListsTypeBasedParameterResolver]
+:CustomAnnotationParameterResolver: {current-branch}/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotationParameterResolver.java[CustomAnnotationParameterResolver]
+:CustomTypeParameterResolver: {current-branch}/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomTypeParameterResolver.java[CustomTypeParameterResolver]
+:MapOfListsTypeBasedParameterResolver: {current-branch}/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfListsTypeBasedParameterResolver.java[MapOfListsTypeBasedParameterResolver]
// Jupiter Migration Support
:EnableJUnit4MigrationSupport: {javadoc-root}/org.junit.jupiter.migrationsupport/org/junit/jupiter/migrationsupport/EnableJUnit4MigrationSupport.html[@EnableJUnit4MigrationSupport]
:EnableRuleMigrationSupport: {javadoc-root}/org.junit.jupiter.migrationsupport/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupport.html[@EnableRuleMigrationSupport]
@@ -251,7 +251,7 @@ endif::[]
:Log4j: https://logging.apache.org/log4j/2.x/[Log4j]
:Log4j_JDK_Logging_Adapter: https://logging.apache.org/log4j/2.x/log4j-jul/index.html[Log4j JDK Logging Adapter]
:Logback: https://logback.qos.ch/[Logback]
-:LogManager: https://docs.oracle.com/javase/8/docs/api/java/util/logging/LogManager.html[LogManager]
+:LogManager: https://docs.oracle.com/en/java/javase/17/docs/api/java.logging/java/util/logging/LogManager.html[LogManager]
:Maven_Central: https://central.sonatype.com/[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]
diff --git a/documentation/src/docs/asciidoc/release-notes/index.adoc b/documentation/src/docs/asciidoc/release-notes/index.adoc
index eea2d33aa356..1885e9c765fb 100644
--- a/documentation/src/docs/asciidoc/release-notes/index.adoc
+++ b/documentation/src/docs/asciidoc/release-notes/index.adoc
@@ -17,6 +17,10 @@ authors as well as build tool and IDE vendors.
include::{includedir}/link-attributes.adoc[]
+include::{basedir}/release-notes-6.0.1.adoc[]
+
include::{basedir}/release-notes-6.0.0.adoc[]
+include::{basedir}/release-notes-5.14.1.adoc[]
+
include::{basedir}/release-notes-5.14.0.adoc[]
diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.14.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.14.1.adoc
new file mode 100644
index 000000000000..dfcc97058460
--- /dev/null
+++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.14.1.adoc
@@ -0,0 +1,38 @@
+[[release-notes-5.14.1]]
+== 5.14.1
+
+*Date of Release:* October 31, 2025
+
+*Scope:* Bug fixes and enhancements since 5.14.0
+
+For a complete list of all _closed_ issues and pull requests for this release, consult the
+link:{junit-framework-repo}+/milestone/111?closed=1+[5.14.1] milestone page in the JUnit
+repository on GitHub.
+
+
+[[release-notes-5.14.1-junit-platform]]
+=== JUnit Platform
+
+No changes.
+
+
+[[release-notes-5.14.1-junit-jupiter]]
+=== JUnit Jupiter
+
+[[release-notes-5.14.1-junit-jupiter-bug-fixes]]
+==== Bug Fixes
+
+* Fix support for test methods with the same signature as a package-private methods
+ declared in super classes in different packages.
+
+[[release-notes-5.14.1-junit-jupiter-new-features-and-improvements]]
+==== New Features and Improvements
+
+* Improve error message when using `@ParameterizedClass` with field injection and not
+ providing enough arguments.
+
+
+[[release-notes-5.14.1-junit-vintage]]
+=== JUnit Vintage
+
+No changes.
diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0.adoc
index 4197899cd504..6a7e50824708 100644
--- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0.adoc
+++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0.adoc
@@ -5,7 +5,7 @@
*Scope:*
-* Java 17 and Kotlin 2.2 baseline
+* Java 17 and Kotlin 2.1 baseline
* Single version number for Platform, Jupiter, and Vintage
* Use of JSpecify annotations to express nullability
* Integration of JFR functionality in `junit-platform-launcher`
diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.1.adoc
new file mode 100644
index 000000000000..77a9f439854b
--- /dev/null
+++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.1.adoc
@@ -0,0 +1,67 @@
+[[release-notes-6.0.1]]
+== 6.0.1
+
+*Date of Release:* October 31, 2025
+
+*Scope:* Bug fixes and enhancements since 6.0.0
+
+For a complete list of all _closed_ issues and pull requests for this release, consult the
+link:{junit-framework-repo}+/milestone/110?closed=1+[6.0.1] milestone page in the JUnit
+repository on GitHub.
+
+
+[[release-notes-6.0.1-junit-platform]]
+=== JUnit Platform
+
+[[release-notes-6.0.1-junit-platform-bug-fixes]]
+==== Bug Fixes
+
+* The `jdk.jfr` package is now an optional import when using the `junit-platform-launcher`
+ as an OSGi bundle.
+* Remove remnants of Java 8 compatibility from User Guide.
+
+
+[[release-notes-6.0.1-junit-jupiter]]
+=== JUnit Jupiter
+
+[[release-notes-6.0.1-junit-jupiter-bug-fixes]]
+==== Bug Fixes
+
+* A regression introduced in version 6.0.0 caused an exception when using `@CsvSource` or
+ `@CsvFileSource` if the `delimiter` or `delimiterString` attribute was set to `+++#+++`.
+ This occurred because `+++#+++` was used as the default comment character without an
+ option to change it. To resolve this, a new `commentCharacter` attribute has been added
+ to both annotations. Its default value remains `+++#+++`, but it can now be customized
+ to avoid conflicts with other control characters.
+* Fix `IllegalAccessError` thrown when using the Kotlin-specific `assertDoesNotThrow`
+ assertion.
+* Stop reporting discovery issues for synthetic methods, particularly in conjunction with
+ Kotlin suspend functions.
+* Fix support for test methods with the same signature as a package-private methods
+ declared in super classes in different packages.
+
+[[release-notes-6.0.1-junit-jupiter-deprecations-and-breaking-changes]]
+==== Deprecations and Breaking Changes
+
+* Mark `org.junit.jupiter.migrationsupport` module descriptor as deprecated for removal.
+
+[[release-notes-6.0.1-junit-jupiter-new-features-and-improvements]]
+==== New Features and Improvements
+
+* The `@CsvSource` and `@CsvFileSource` annotations now allow specifying
+ a custom comment character using the new `commentCharacter` attribute.
+* Improve error message when using `@ParameterizedClass` with field injection and not
+ providing enough arguments.
+* Allow calling `TypedArgumentConverter` constructor for `@Nullable T` target types
+ without having to cast class literals to `Class<@Nullable T>`.
+
+
+[[release-notes-6.0.1-junit-vintage]]
+=== JUnit Vintage
+
+[[release-notes-6.0.1-junit-vintage-new-features-and-improvements]]
+==== New Features and Improvements
+
+* Allow disabling the reporting of discovery issues by the JUnit Vintage engine (including
+ the one reported for its deprecation) by setting the new
+ `junit.vintage.discovery.issue.reporting.enabled` configuration parameter to `false`.
diff --git a/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-suite-engine.adoc b/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-suite-engine.adoc
index 366d4854abb6..52e1a025af9a 100644
--- a/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-suite-engine.adoc
+++ b/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-suite-engine.adoc
@@ -1,8 +1,10 @@
[[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.
+The Suite Engine supports the declarative selection and execution of tests from
+_any_ test engine on the JUnit Platform using the <>.
+
+image::junit-platform-suite-engine-diagram.svg[role=text-center]
[[junit-platform-suite-engine-setup]]
==== Setup
@@ -58,3 +60,19 @@ all tests of the test suite.
----
include::{testDir}/example/BeforeAndAfterSuiteDemo.java[tags=user_guide]
----
+
+[[junit-platform-suite-engine-duplicate-test-execution]]
+==== Duplicate test execution
+
+Depending on the declared test selection, different suites may contain the same tests,
+potentially with different configurations.
+Moreover, tests in a suite are executed in addition to the tests executed by every other
+test engine. This can result in the same tests being executed twice.
+
+image::junit-platform-suite-engine-duplicate-test-execution-diagram.svg[role=text-center]
+
+This can be prevented by configuring your build tool to only include the
+`junit-platform-suite` engine. Or by using a naming pattern. For example, name all suites
+`*Suite` and all tests `*Test` and configure your build tool to only include the former.
+
+Alternatively, consider <> to select specific groups of tests.
diff --git a/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc b/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc
index da003aef2694..434761485d2a 100644
--- a/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc
+++ b/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc
@@ -14,6 +14,8 @@ tests. Moreover, third party test libraries – like Spock or Cucumber – can p
JUnit Platform's launching infrastructure by providing a custom
<>.
+image::launcher-api-diagram.svg[role=text-center]
+
The launcher API is in the `{junit-platform-launcher}` module.
An example consumer of the launcher API is the `{ConsoleLauncher}` in the
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 dff2b2319192..099b875bdd12 100644
--- a/documentation/src/docs/asciidoc/user-guide/migration-from-junit4.adoc
+++ b/documentation/src/docs/asciidoc/user-guide/migration-from-junit4.adoc
@@ -19,10 +19,19 @@ classpath does not lead to any conflicts. It is therefore safe to maintain exist
[[migrating-from-junit4-running]]
=== Running JUnit 4 Tests on the JUnit Platform
-WARNING: The JUnit Vintage engine is deprecated and should only be used temporarily while
+[WARNING]
+====
+The JUnit Vintage engine is deprecated and should only be used temporarily while
migrating tests to JUnit Jupiter or another testing framework with native JUnit Platform
support.
+By default, if the JUnit Vintage engine is registered and discovers at least one test
+class, it reports a <> of INFO severity.
+You can prevent this discovery issue from being reported by setting the
+`junit.vintage.discovery.issue.reporting.enabled`
+<> to `false`.
+====
+
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.
diff --git a/documentation/src/docs/asciidoc/user-guide/running-tests.adoc b/documentation/src/docs/asciidoc/user-guide/running-tests.adoc
index 5d5e9ce0c826..01cb5d23a6fa 100644
--- a/documentation/src/docs/asciidoc/user-guide/running-tests.adoc
+++ b/documentation/src/docs/asciidoc/user-guide/running-tests.adoc
@@ -748,10 +748,12 @@ include::{consoleLauncherDiscoverOptionsFile}[]
===== Executing tests
.Exit Code
-NOTE: The `{ConsoleLauncher}` exits with a status code of `1` if any containers or tests
-failed. If no tests are discovered and the `--fail-if-no-tests` command-line option is
-supplied, the `ConsoleLauncher` exits with a status code of `2`. Otherwise, the exit code
-is `0`.
+NOTE: On successful runs, the `{ConsoleLauncher}` exits with a status code of `0`.
+All non-zero codes indicate an error of some sort. For example, status code `1`
+is returned if any containers or tests failed. If no tests are discovered and the
+`--fail-if-no-tests` command-line option is supplied, the `ConsoleLauncher` exits
+with a status code of `2`. Unexpected or invalid user input yields a status code
+of `3`. An exit code of `-1` indicates an unspecified error condition.
----
include::{consoleLauncherExecuteOptionsFile}[]
@@ -1124,7 +1126,7 @@ option.
Please consult the manual of your build tool for the appropriate commands.
To analyze the recorded events, use the
-https://docs.oracle.com/en/java/javase/14/docs/specs/man/jfr.html[jfr]
+https://docs.oracle.com/en/java/javase/17/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].
diff --git a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
index 828c740cd043..d4f955fa6249 100644
--- a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
+++ b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
@@ -513,33 +513,22 @@ include::{kotlinTestDir}/example/KotlinAssertionsDemo.kt[tags=user_guide]
==== Third-party Assertion Libraries
Even though the assertion facilities provided by JUnit Jupiter are sufficient for many
-testing scenarios, there are times when more power and additional functionality such as
-_matchers_ are desired or required. In such cases, the JUnit team recommends the use of
-third-party assertion libraries such as {AssertJ}, {Hamcrest}, {Truth}, etc. Developers
-are therefore free to use the assertion library of their choice.
+testing scenarios, there are times when more power and additional functionality are
+desired or required. In such cases, the JUnit team recommends the use of third-party
+assertion libraries such as {AssertJ}, {Hamcrest}, {Truth}, etc. Developers are therefore
+free to use the assertion library of their choice.
-For example, the combination of _matchers_ and a fluent API can be used to make
-assertions more descriptive and readable. However, JUnit Jupiter's `{Assertions}` class
-does not provide an
-https://junit.org/junit4/javadoc/latest/org/junit/Assert.html#assertThat[`assertThat()`]
-method like the one found in JUnit 4's `org.junit.Assert` class which accepts a Hamcrest
-https://junit.org/junit4/javadoc/latest/org/hamcrest/Matcher.html[`Matcher`]. Instead,
-developers are encouraged to use the built-in support for matchers provided by third-party
-assertion libraries.
-
-The following example demonstrates how to use the `assertThat()` support from Hamcrest in
-a JUnit Jupiter test. As long as the Hamcrest library has been added to the classpath,
-you can statically import methods such as `assertThat()`, `is()`, and `equalTo()` and
-then use them in tests like in the `assertWithHamcrestMatcher()` method below.
+For example, the following demonstrates how to use the `assertThat()` support from AssertJ
+in a JUnit Jupiter test. As long as the AssertJ library has been added to the classpath,
+you can statically import methods such as `assertThat()`, `assertThatException()`, etc.
+from `org.assertj.core.api.Assertions` and then use them in tests like in the
+`assertWithAssertJ()` method below.
[source,java,indent=0]
----
-include::{testDir}/example/HamcrestAssertionsDemo.java[tags=user_guide]
+include::{testDir}/example/AssertJAssertionsDemo.java[tags=user_guide]
----
-Naturally, legacy tests based on the JUnit 4 programming model can continue using
-`org.junit.Assert#assertThat`.
-
[TIP]
.Excluding Jupiter’s Assertions From a Project’s Classpath
====
@@ -594,6 +583,11 @@ NOTE: It is also possible to use methods from JUnit 4's `org.junit.Assume` class
assumptions. Specifically, JUnit Jupiter supports JUnit 4's `AssumptionViolatedException`
to signal that a test should be aborted instead of marked as a failure.
+TIP: If you use AssertJ for assertions, you may also wish to use AssertJ for assumptions.
+To do so, you can statically import the `assumeThat()` method from
+`org.assertj.core.api.Assumptions` and then use AssertJ's fluent API to specify your
+assumptions.
+
[[writing-tests-exceptions]]
=== Exception Handling
@@ -1755,9 +1749,8 @@ test class.
include::{testDir}/example/ParameterizedClassDemo.java[tags=constructor_injection]
----
-If your programming language level you are using supports _records_ -- for example, Java
-16 or higher -- you may use them to implement parameterized classes that avoid the
-boilerplate code of declaring a test class constructor.
+You may use _records_ to implement parameterized classes that avoid the boilerplate code
+of declaring a test class constructor.
[source,java,indent=0]
----
@@ -2243,11 +2236,12 @@ by default. This behavior can be changed by setting the
| `@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 class or test. The first record may optionally be used to supply CSV headers
-by setting the `useHeadersInDisplayName` attribute to `true` as in the example below.
+If the programming language you are using supports Java _text blocks_ or equivalent
+multi-line string literals, 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 class or 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.
@@ -2276,12 +2270,16 @@ The generated display names for the previous example include the CSV header name
----
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).
+comments. Any line beginning with the value of the `commentCharacter` attribute (`+++#+++`
+by default) will be treated as a comment and ignored. Note that there is one exception
+to this rule: if the comment character appears within a quoted field, it loses
+its special meaning.
+
+The comment character 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]
----
@@ -2306,7 +2304,7 @@ void testWithCsvSource(String fruit, int rank) {
[NOTE]
====
-Java's https://docs.oracle.com/en/java/javase/15/text-blocks/index.html[text block]
+Java's https://docs.oracle.com/en/java/javase/17/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
@@ -2331,8 +2329,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 the value of the `commentCharacter` attribute (`+++#+++`
+by default) will be interpreted as a comment and will be ignored.
[source,java,indent=0]
----
diff --git a/documentation/src/plantuml/junit-platform-suite-engine-diagram.puml b/documentation/src/plantuml/junit-platform-suite-engine-diagram.puml
new file mode 100644
index 000000000000..04ab91b565c0
--- /dev/null
+++ b/documentation/src/plantuml/junit-platform-suite-engine-diagram.puml
@@ -0,0 +1,32 @@
+@startuml
+object "IDEs"
+object "Build Tools"
+object "Console Launcher"
+
+object "JUnit Platform"
+object "Suite Test Engine"
+
+object "@Suite annotated class A"
+object "@Suite annotated class B"
+object "JUnit Platform (A)"
+object "JUnit Platform (B)"
+object "Jupiter Test Engine (A)"
+object "Jupiter Test Engine (B)"
+object "Tests in package A"
+object "Tests in package B"
+
+"IDEs" --> "JUnit Platform"
+"Build Tools" --> "JUnit Platform"
+"Console Launcher" --> "JUnit Platform" : requests discovery and execution
+"JUnit Platform" --> "Suite Test Engine": forwards request
+
+"Suite Test Engine" --> "@Suite annotated class A"
+"@Suite annotated class A" --> "JUnit Platform (A)"
+"JUnit Platform (A)" --> "Jupiter Test Engine (A)"
+"Jupiter Test Engine (A)" --> "Tests in package A"
+
+"Suite Test Engine" --> "@Suite annotated class B" : discovers and executes
+"@Suite annotated class B" --> "JUnit Platform (B)" : requests discovery and execution
+"JUnit Platform (B)" --> "Jupiter Test Engine (B)" : forwards request
+"Jupiter Test Engine (B)" --> "Tests in package B" : discovers and executes
+@enduml
diff --git a/documentation/src/plantuml/junit-platform-suite-engine-duplicate-test-execution-diagram.puml b/documentation/src/plantuml/junit-platform-suite-engine-duplicate-test-execution-diagram.puml
new file mode 100644
index 000000000000..788985b78717
--- /dev/null
+++ b/documentation/src/plantuml/junit-platform-suite-engine-duplicate-test-execution-diagram.puml
@@ -0,0 +1,30 @@
+@startuml
+object "IDEs"
+object "Build Tools"
+object "Console Launcher"
+object "JUnit Platform"
+together {
+ object "Suite Test Engine"
+ object "Jupiter Test Engine"
+}
+object "@Suite annotated class"
+object "JUnit Platform (@Suite)"
+object "Jupiter Test Engine (@Suite)"
+together {
+ object "Example Test A"
+ object "Example Test A (@Suite)"
+}
+
+"IDEs" --> "JUnit Platform"
+"Build Tools" --> "JUnit Platform"
+"Console Launcher" --> "JUnit Platform" : requests discovery and execution
+
+"JUnit Platform" --> "Suite Test Engine"
+"Suite Test Engine" --> "@Suite annotated class" : discovers and executes
+"@Suite annotated class" --> "JUnit Platform (@Suite)" : requests discovery and execution
+"JUnit Platform (@Suite)" --> "Jupiter Test Engine (@Suite)" : forwards request
+"Jupiter Test Engine (@Suite)" --> "Example Test A (@Suite)" : discovers and executes
+
+"JUnit Platform" --> "Jupiter Test Engine": forwards request
+"Jupiter Test Engine" --> "Example Test A" #line:red;line.bold;text:red : also discover and executes!
+@enduml
diff --git a/documentation/src/plantuml/launcher-api-diagram.puml b/documentation/src/plantuml/launcher-api-diagram.puml
new file mode 100644
index 000000000000..10dc6ea4d8c5
--- /dev/null
+++ b/documentation/src/plantuml/launcher-api-diagram.puml
@@ -0,0 +1,24 @@
+@startuml
+object "IDEs"
+object "Build Tools"
+object "Console Launcher"
+object "JUnit Platform"
+object "Jupiter Test Engine"
+object "Cucumber Test Engine"
+object "Spock Test Engine"
+object "Test Classes"
+object "Feature Files"
+object "Specifications"
+
+"IDEs" --> "JUnit Platform"
+"Build Tools" --> "JUnit Platform"
+"Console Launcher" --> "JUnit Platform" : requests discovery and execution
+"JUnit Platform" --> "Jupiter Test Engine"
+"Jupiter Test Engine" --> "Test Classes"
+
+"JUnit Platform" --> "Cucumber Test Engine"
+"Cucumber Test Engine" --> "Feature Files"
+
+"JUnit Platform" --> "Spock Test Engine": forwards request
+"Spock Test Engine" --> "Specifications" : discovers and executes
+@enduml
diff --git a/documentation/src/test/java/example/HamcrestAssertionsDemo.java b/documentation/src/test/java/example/AssertJAssertionsDemo.java
similarity index 65%
rename from documentation/src/test/java/example/HamcrestAssertionsDemo.java
rename to documentation/src/test/java/example/AssertJAssertionsDemo.java
index bb678e06a1d3..6f9771e50842 100644
--- a/documentation/src/test/java/example/HamcrestAssertionsDemo.java
+++ b/documentation/src/test/java/example/AssertJAssertionsDemo.java
@@ -11,21 +11,19 @@
package example;
// tag::user_guide[]
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
+import static org.assertj.core.api.Assertions.assertThat;
import example.util.Calculator;
import org.junit.jupiter.api.Test;
-class HamcrestAssertionsDemo {
+class AssertJAssertionsDemo {
private final Calculator calculator = new Calculator();
@Test
- void assertWithHamcrestMatcher() {
- assertThat(calculator.subtract(4, 1), is(equalTo(3)));
+ void assertWithAssertJ() {
+ assertThat(calculator.subtract(4, 1)).isEqualTo(3);
}
}
diff --git a/documentation/src/test/resources/log4j2-test.xml b/documentation/src/test/resources/log4j2-test.xml
index 9fb7a4a69de1..85bbb36f213e 100644
--- a/documentation/src/test/resources/log4j2-test.xml
+++ b/documentation/src/test/resources/log4j2-test.xml
@@ -1,19 +1,22 @@
-
+
-
+
-
-
+
+
-
-
-
+
+
+
-
\ No newline at end of file
+
diff --git a/gradle.properties b/gradle.properties
index 813ceb1616a3..de2e2b3c2f74 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,4 +1,7 @@
-version = 6.0.0
+version = 6.0.1
+
+# For backward compatibility checks
+apiBaselineVersion = 6.0.0
# We need more metaspace due to apparent memory leak in Asciidoctor/JRuby
org.gradle.jvmargs=-Xmx1g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError
diff --git a/gradle/base/code-generator-model/src/main/resources/jre.yaml b/gradle/base/code-generator-model/src/main/resources/jre.yaml
index 90654b64919d..fa560cee51fa 100644
--- a/gradle/base/code-generator-model/src/main/resources/jre.yaml
+++ b/gradle/base/code-generator-model/src/main/resources/jre.yaml
@@ -31,4 +31,4 @@
- version: 25
since: '5.11.4'
- version: 26
- since: '6.0'
+ since: '5.13.2'
diff --git a/gradle/config/japicmp/accepted-breaking-changes.txt b/gradle/config/japicmp/accepted-breaking-changes.txt
index 406439d2ec90..19c25d71f94d 100644
--- a/gradle/config/japicmp/accepted-breaking-changes.txt
+++ b/gradle/config/japicmp/accepted-breaking-changes.txt
@@ -1,62 +1,2 @@
-org.junit.jupiter.api.AssertionsKt
-org.junit.jupiter.api.extension.ExtensionContext#getConfigurationParameter
-org.junit.jupiter.api.extension.ExtensionContext$Namespace
-org.junit.jupiter.api.extension.ExtensionContext$Store#computeIfAbsent
-org.junit.jupiter.api.extension.ExtensionContext$Store#getOrComputeIfAbsent
-org.junit.jupiter.api.extension.InvocationInterceptor#interceptDynamicTest
-org.junit.jupiter.api.extension.MediaType
-org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider#provideTestTemplateInvocationContexts
-org.junit.jupiter.api.io.TempDir#SCOPE_PROPERTY_NAME
-org.junit.jupiter.api.MethodOrderer$Alphanumeric
-org.junit.jupiter.engine.Constants#TEMP_DIR_SCOPE_PROPERTY_NAME
-org.junit.jupiter.params.ParameterizedClass#quoteTextArguments
-org.junit.jupiter.params.ParameterizedTest#quoteTextArguments
-org.junit.jupiter.params.provider.CsvFileSource#lineSeparator
-org.junit.jupiter.params.ResolutionCache$Concurrent#resolve
-org.junit.platform.commons.PreconditionViolationException
-org.junit.platform.commons.support.ReflectionSupport#loadClass
-org.junit.platform.commons.util.BlacklistedExceptions
-org.junit.platform.commons.util.PreconditionViolationException
-org.junit.platform.console.ConsoleLauncher#ConsoleLauncher
-org.junit.platform.engine.ConfigurationParameters#get
-org.junit.platform.engine.ConfigurationParameters#size
-org.junit.platform.engine.discovery.ClasspathResourceSelector
-org.junit.platform.engine.discovery.ClasspathRootSelector
-org.junit.platform.engine.discovery.ClassSelector
-org.junit.platform.engine.discovery.DirectorySelector
-org.junit.platform.engine.discovery.FilePosition
-org.junit.platform.engine.discovery.FileSelector
-org.junit.platform.engine.discovery.IterationSelector
-org.junit.platform.engine.discovery.MethodSelector
-org.junit.platform.engine.discovery.ModuleSelector
-org.junit.platform.engine.discovery.NestedClassSelector
-org.junit.platform.engine.discovery.NestedMethodSelector
-org.junit.platform.engine.discovery.PackageSelector
-org.junit.platform.engine.discovery.UniqueIdSelector
-org.junit.platform.engine.discovery.UriSelector
-org.junit.platform.engine.reporting.ReportEntry#ReportEntry
-org.junit.platform.engine.support.config.PrefixedConfigurationParameters#get
-org.junit.platform.engine.support.descriptor.ClasspathResourceSource
-org.junit.platform.engine.support.descriptor.ClassSource
-org.junit.platform.engine.support.descriptor.CompositeTestSource
-org.junit.platform.engine.support.descriptor.DirectorySource
-org.junit.platform.engine.support.descriptor.FilePosition
-org.junit.platform.engine.support.descriptor.FileSource
-org.junit.platform.engine.support.descriptor.MethodSource
-org.junit.platform.engine.support.descriptor.PackageSource
-org.junit.platform.engine.support.filter.ClasspathScanningSupport
-org.junit.platform.engine.support.hierarchical.SingleTestExecutor
-org.junit.platform.engine.support.hierarchical.SingleTestExecutor$Executable
-org.junit.platform.engine.support.store.Namespace
-org.junit.platform.engine.support.store.NamespacedHierarchicalStore#getOrComputeIfAbsent
-org.junit.platform.engine.UniqueId
-org.junit.platform.engine.UniqueId$Segment
-org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder
-org.junit.platform.launcher.Launcher
-org.junit.platform.launcher.listeners.LegacyReportingUtils
-org.junit.platform.launcher.TestPlan#add
-org.junit.platform.launcher.TestPlan#getChildren
-org.junit.platform.reporting.open.xml.GitInfoCollector$CliGitInfoCollector
-org.junit.platform.suite.api.UseTechnicalNames
-org.junit.platform.testkit.engine.EngineTestKit#execute
-org.junit.platform.testkit.engine.EngineTestKit$Builder#filters
+org.junit.jupiter.params.provider.CsvFileSource#commentCharacter
+org.junit.jupiter.params.provider.CsvSource#commentCharacter
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index f265ee366e3a..6eff2444b39e 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,15 +1,16 @@
[versions]
ant = "1.10.15"
apiguardian = "1.1.2"
-asciidoctorj-pdf = "2.3.19"
+asciidoctorj-pdf = "2.3.20"
asciidoctor-plugins = "4.0.5" # Check if workaround in documentation.gradle.kts can be removed when upgrading
assertj = "3.27.6"
bnd = "7.1.0"
-checkstyle = "11.1.0"
+checkstyle = "12.0.1"
eclipse = "4.37.0"
jackson = "2.20.0"
-jacoco = "0.8.13"
+jacoco = "0.8.14"
jmh = "1.37"
+jruby = "9.4.14.0"
junit4 = "4.13.2"
junit4Min = "4.12"
ktlint = "1.7.1"
@@ -29,10 +30,10 @@ archunit = { module = "com.tngtech.archunit:archunit-junit5", version = "1.4.1"
assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" }
bndlib = { module = "biz.aQute.bnd:biz.aQute.bndlib", version.ref = "bnd" }
checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" }
-classgraph = { module = "io.github.classgraph:classgraph", version = "4.8.181" }
+classgraph = { module = "io.github.classgraph:classgraph", version = "4.8.184" }
commons-io = { module = "commons-io:commons-io", version = "2.20.0" }
errorProne-core = { module = "com.google.errorprone:error_prone_core", version = "2.42.0" }
-fastcsv = { module = "de.siegmar:fastcsv", version = "4.0.0" }
+fastcsv = { module = "de.siegmar:fastcsv", version = "4.1.0" }
groovy = { module = "org.apache.groovy:groovy", version = "5.0.1" }
groovy2-bom = { module = "org.codehaus.groovy:groovy-bom", version = "2.5.23" }
hamcrest = { module = "org.hamcrest:hamcrest", version = "3.0" }
@@ -51,7 +52,7 @@ log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "lo
log4j-jul = { module = "org.apache.logging.log4j:log4j-jul", version.ref = "log4j" }
maven = { module = "org.apache.maven:apache-maven", version = "3.9.11" }
mavenSurefirePlugin = { module = "org.apache.maven.plugins:maven-surefire-plugin", version.ref = "surefire" }
-memoryfilesystem = { module = "com.github.marschall:memoryfilesystem", version = "2.8.1" }
+memoryfilesystem = { module = "com.github.marschall:memoryfilesystem", version = "2.8.2" }
mockito-bom = { module = "org.mockito:mockito-bom", version = "5.20.0" }
mockito-core = { module = "org.mockito:mockito-core" }
mockito-junit-jupiter = { module = "org.mockito:mockito-junit-jupiter" }
@@ -76,6 +77,7 @@ woodstox = { module = "com.fasterxml.woodstox:woodstox-core", version = "7.1.1"
asciidoctorj-pdf = { module = "org.asciidoctor:asciidoctorj-pdf", version.ref = "asciidoctorj-pdf" }
eclipse-platform = { module = "org.eclipse.platform:org.eclipse.platform", version.ref = "eclipse" }
jacoco = { module = "org.jacoco:jacoco", version.ref = "jacoco" }
+jruby = { module = "org.jruby:jruby", version.ref = "jruby" }
junit4-latest = { module = "junit:junit", version.ref = "junit4" }
junit4-bundle = { module = "org.apache.servicemix.bundles:org.apache.servicemix.bundles.junit", version = "4.13.2_1" }
ktlint-cli = { module = "com.pinterest.ktlint:ktlint-cli", version.ref = "ktlint" }
@@ -91,7 +93,7 @@ asciidoctorPdf = { id = "org.asciidoctor.jvm.pdf", version.ref = "asciidoctor-pl
bnd = { id = "biz.aQute.bnd", version.ref = "bnd" }
buildParameters = { id = "org.gradlex.build-parameters", version = "1.4.4" }
commonCustomUserData = { id = "com.gradle.common-custom-user-data-gradle-plugin", version = "2.4.0" }
-develocity = { id = "com.gradle.develocity", version = "4.2" }
+develocity = { id = "com.gradle.develocity", version = "4.2.1" }
download = { id = "de.undercouch.download", version = "5.6.0" }
errorProne = { id = "net.ltgt.errorprone", version = "4.3.0" }
foojayResolver = { id = "org.gradle.toolchains.foojay-resolver", version = "1.0.0" }
diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.javadoc-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.javadoc-conventions.gradle.kts
index b9787ff64133..ae426ab22bba 100644
--- a/gradle/plugins/common/src/main/kotlin/junitbuild.javadoc-conventions.gradle.kts
+++ b/gradle/plugins/common/src/main/kotlin/junitbuild.javadoc-conventions.gradle.kts
@@ -28,6 +28,7 @@ tasks.javadoc {
(this as StandardJavadocDocletOptions).apply {
addBooleanOption("Xdoclint:all,-missing", true)
addBooleanOption("html5", true)
+ addBooleanOption("Werror", true)
addMultilineStringsOption("tag").value = listOf(
"apiNote:a:API Note:",
"implNote:a:Implementation Note:"
diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts
index 3dd9a2b7ee15..6040ad6663af 100644
--- a/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts
+++ b/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts
@@ -16,6 +16,7 @@ tasks.withType().named {
val importAPIGuardian by extra { "org.apiguardian.*;resolution:=\"optional\"" }
val importJSpecify by extra { "org.jspecify.*;resolution:=\"optional\"" }
+ val importCommonsLogging by extra { "org.junit.platform.commons.logging;status=INTERNAL" }
extensions.create(BundleTaskExtension.NAME, this).apply {
properties.set(projectDescription.map {
@@ -38,7 +39,7 @@ tasks.withType().named {
Import-Package: \
${importAPIGuardian},\
${importJSpecify},\
- org.junit.platform.commons.logging;status=INTERNAL,\
+ ${importCommonsLogging},\
kotlin.*;resolution:="optional",\
*
@@ -46,7 +47,7 @@ tasks.withType().named {
# 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
- -fixupmessages.jspecify.import: "Unused Import-Package instructions: \\[org.jspecify.*\\]";is:=ignore
+ -fixupmessages.warningsAsErrors: ".*";restrict:=warning;is:=error
# Don't scan for Class.forName package imports.
# See https://bnd.bndtools.org/instructions/noclassforname.html
@@ -86,10 +87,10 @@ val osgiProperties by tasks.registering(WriteProperties::class) {
property("-runee", Callable { "JavaSE-${javaLibrary.mainJavaVersion.get()}" })
}
property("-runrequires", "osgi.identity;filter:='(osgi.identity=${project.name})'")
- property("-runsystempackages", "jdk.internal.misc,jdk.jfr,sun.misc")
- // API Guardian should be optional -> instruct resolver to ignore it
+ property("-runsystempackages", "jdk.internal.misc,sun.misc")
+ // API Guardian and JDK JFR should be optional -> instruct resolver to ignore them
// during resolution. Resolve should still pass.
- property("-runblacklist", "org.apiguardian.api")
+ property("-runblacklist", "org.apiguardian.api,jdk.jfr")
}
val osgiVerification = configurations.dependencyScope("osgiVerification")
diff --git a/gradle/plugins/japicmp/src/main/kotlin/junitbuild.japicmp.gradle.kts b/gradle/plugins/japicmp/src/main/kotlin/junitbuild.japicmp.gradle.kts
index ce38f2791b88..fc6c3c5ab08d 100644
--- a/gradle/plugins/japicmp/src/main/kotlin/junitbuild.japicmp.gradle.kts
+++ b/gradle/plugins/japicmp/src/main/kotlin/junitbuild.japicmp.gradle.kts
@@ -29,9 +29,7 @@ val extension = extensions.create("japicmp").apply {
finalizeValueOnRead()
}
previousVersion.apply {
- convention(provider {
- if (group == "org.junit.platform") "1.14.0-RC1" else "5.14.0-RC1"
- })
+ convention(providers.gradleProperty("apiBaselineVersion"))
finalizeValueOnRead()
}
}
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/InvocationInterceptor.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/InvocationInterceptor.java
index 89ee85a7735d..1ca442358209 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/InvocationInterceptor.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/InvocationInterceptor.java
@@ -45,6 +45,16 @@
*
Consult the documentation in {@link Extension} for details on
* constructor requirements.
*
+ *
{@code ExtensionContext} Scope
+ *
+ *
As of JUnit Jupiter 5.12, this API participates in the
+ * {@link TestInstantiationAwareExtension} contract. Implementations of this API
+ * may therefore choose to override
+ * {@link TestInstantiationAwareExtension#getTestInstantiationExtensionContextScope(ExtensionContext)
+ * getTestInstantiationExtensionContextScope(ExtensionContext)}. See
+ * {@link #interceptTestClassConstructor(Invocation, ReflectiveInvocationContext, ExtensionContext)}
+ * for details.
+ *
* @since 5.5
* @see Invocation
* @see ReflectiveInvocationContext
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolver.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolver.java
index 898f73a94760..8e27dd857ae1 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolver.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolver.java
@@ -48,6 +48,15 @@
*
Consult the documentation in {@link Extension} for details on
* constructor requirements.
*
+ *
{@code ExtensionContext} Scope
+ *
+ *
As of JUnit Jupiter 5.12, this API participates in the
+ * {@link TestInstantiationAwareExtension} contract. Implementations of this API
+ * may therefore choose to override
+ * {@link TestInstantiationAwareExtension#getTestInstantiationExtensionContextScope(ExtensionContext)
+ * getTestInstantiationExtensionContextScope(ExtensionContext)} to require a
+ * test-method scoped {@code ExtensionContext}.
+ *
* @since 5.0
* @see #supportsParameter(ParameterContext, ExtensionContext)
* @see #resolveParameter(ParameterContext, ExtensionContext)
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactory.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactory.java
index e06ca4d75c0c..ec4e62c2e9e1 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactory.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactory.java
@@ -43,6 +43,17 @@
*
Consult the documentation in {@link Extension} for details on
* constructor requirements.
*
+ *
{@code ExtensionContext} Scope
+ *
+ *
As of JUnit Jupiter 5.12, this API participates in the
+ * {@link TestInstantiationAwareExtension} contract. Implementations of this API
+ * may therefore choose to override
+ * {@link TestInstantiationAwareExtension#getTestInstantiationExtensionContextScope(ExtensionContext)
+ * getTestInstantiationExtensionContextScope(ExtensionContext)} to require a
+ * test-method scoped {@code ExtensionContext}. See
+ * {@link #createTestInstance(TestInstanceFactoryContext, ExtensionContext)} for
+ * further details.
+ *
* @since 5.3
* @see #createTestInstance(TestInstanceFactoryContext, ExtensionContext)
* @see TestInstanceFactoryContext
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePostProcessor.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePostProcessor.java
index ad70edb7cf41..711746243a8d 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePostProcessor.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePostProcessor.java
@@ -33,6 +33,16 @@
*
Consult the documentation in {@link Extension} for details on
* constructor requirements.
*
+ *
{@code ExtensionContext} Scope
+ *
+ *
As of JUnit Jupiter 5.12, this API participates in the
+ * {@link TestInstantiationAwareExtension} contract. Implementations of this API
+ * may therefore choose to override
+ * {@link TestInstantiationAwareExtension#getTestInstantiationExtensionContextScope(ExtensionContext)
+ * getTestInstantiationExtensionContextScope(ExtensionContext)} to require a
+ * test-method scoped {@code ExtensionContext}. See
+ * {@link #postProcessTestInstance(Object, ExtensionContext)} for further details.
+ *
* @since 5.0
* @see #postProcessTestInstance(Object, ExtensionContext)
* @see TestInstancePreDestroyCallback
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreConstructCallback.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreConstructCallback.java
index 70a0035c826d..44221ebe0d95 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreConstructCallback.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreConstructCallback.java
@@ -38,6 +38,17 @@
*
Consult the documentation in {@link Extension} for details on constructor
* requirements.
*
+ *
{@code ExtensionContext} Scope
+ *
+ *
As of JUnit Jupiter 5.12, this API participates in the
+ * {@link TestInstantiationAwareExtension} contract. Implementations of this API
+ * may therefore choose to override
+ * {@link TestInstantiationAwareExtension#getTestInstantiationExtensionContextScope(ExtensionContext)
+ * getTestInstantiationExtensionContextScope(ExtensionContext)} to require a
+ * test-method scoped {@code ExtensionContext}. See
+ * {@link #preConstructTestInstance(TestInstanceFactoryContext, ExtensionContext)}
+ * for further details.
+ *
* @since 5.9
* @see TestInstancePreDestroyCallback
* @see TestInstanceFactory
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java
index b55725a7a7ae..be13419273f9 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java
@@ -14,79 +14,119 @@
import static org.apiguardian.api.API.Status.MAINTAINED;
import org.apiguardian.api.API;
-import org.junit.jupiter.api.TestInstance;
-import org.junit.jupiter.api.extension.ExtensionContext.Store;
/**
- * Interface for {@link Extension Extensions} that are aware and can influence
- * the instantiation of test instances.
+ * {@code TestInstantiationAwareExtension} defines the API for {@link Extension
+ * Extensions} that are aware of or influence the instantiation of test classes.
*
- *
This interface is not indented to be implemented directly. Instead, extend
- * one of its sub-interfaces.
+ *
This interface is not intended to be implemented directly. Instead, extensions
+ * should implement one of the sub-interfaces listed below.
+ *
+ *
+ *
{@link InvocationInterceptor}
+ *
{@link ParameterResolver}
+ *
{@link TestInstancePreConstructCallback}
+ *
{@link TestInstancePostProcessor}
+ *
{@link TestInstanceFactory}
+ *
+ *
+ *
See {@link #getTestInstantiationExtensionContextScope(ExtensionContext)} for
+ * further details.
*
* @since 5.12
- * @see InvocationInterceptor#interceptTestClassConstructor
- * @see ParameterResolver
- * @see TestInstancePreConstructCallback
- * @see TestInstancePostProcessor
- * @see TestInstanceFactory
*/
@API(status = MAINTAINED, since = "5.13.3")
public interface TestInstantiationAwareExtension extends Extension {
/**
- * Whether this extension should receive a test-scoped
- * {@link ExtensionContext} during the instantiation of test instances.
+ * Determine whether this extension should receive a test-method scoped
+ * {@link ExtensionContext} during the instantiation of test classes or
+ * processing of test instances.
*
- *
If an extension returns
- * {@link ExtensionContextScope#TEST_METHOD TEST_METHOD} from this method,
- * the following extension methods will be called with a test-scoped
- * {@link ExtensionContext} instead of a class-scoped one, unless the
- * {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS} lifecycle is used:
+ *
If an extension returns {@link ExtensionContextScope#TEST_METHOD TEST_METHOD}
+ * from this method, methods defined in the following extension APIs will be
+ * called with a test-method scoped {@code ExtensionContext} instead of a
+ * test-class scoped context. Note, however, that a test-class scoped context
+ * will always be supplied if the
+ * {@link org.junit.jupiter.api.TestInstance.Lifecycle#PER_CLASS PER_CLASS}
+ * test instance lifecycle is used.
*
*
{@link ParameterResolver} when resolving constructor parameters
+ *
{@link InvocationInterceptor}: only the
+ * {@link InvocationInterceptor#interceptTestClassConstructor
+ * interceptTestClassConstructor(...)} method
+ *
{@link ParameterResolver}: only when resolving constructor parameters
*
{@link TestInstancePreConstructCallback}
*
{@link TestInstancePostProcessor}
*
{@link TestInstanceFactory}
*
*
- *
In such cases, implementations of these extension callbacks can
- * observe the following differences:
+ *
When a test-method scoped {@code ExtensionContext} is supplied, implementations
+ * of the above extension APIs will observe the following differences.
*
*
- *
{@link ExtensionContext#getElement() getElement()} may refer to the
- * test method and {@link ExtensionContext#getTestClass() getTestClass()}
- * may refer to a nested test class.
- * Use {@link TestInstanceFactoryContext#getTestClass()} to get the class
- * under construction.
- *
{@link ExtensionContext#getTestMethod() getTestMethod()} is no longer
- * empty, unless the {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS}
- * lifecycle is used.
- *
If the callback adds a new {@link Store.CloseableResource} or
- * {@link AutoCloseable} to the {@link Store Store} (unless the
- * {@code junit.jupiter.extensions.store.close.autocloseable.enabled}
- * configuration parameter is set to {@code false}), then
- * the resource is closed just after the instance is destroyed.
- *
The callbacks can now access data previously stored by
- * {@link TestTemplateInvocationContext}, unless the
- * {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS} lifecycle is used.
+ *
+ * {@link ExtensionContext#getElement() getElement()} may refer to the
+ * test method.
+ *
+ *
+ * {@link ExtensionContext#getTestClass() getTestClass()} may refer to a
+ * nested test class.
+ *
+ *
+ * For {@link TestInstancePostProcessor}, use {@code testInstance.getClass()}
+ * to get the test class associated with the supplied instance.
+ *
+ *
+ * For {@link TestInstanceFactory} and {@link TestInstancePreConstructCallback},
+ * use {@link TestInstanceFactoryContext#getTestClass()} to get the
+ * class under construction.
+ *
+ *
+ * For {@link ParameterResolver}, when resolving a parameter for a
+ * constructor, ensure that the
+ * {@link ParameterContext#getDeclaringExecutable() Executable} is a
+ * {@link java.lang.reflect.Constructor Constructor}, and then use
+ * {@code constructor.getDeclaringClass()} to get the test class
+ * associated with the constructor.
+ *
+ *
+ *
+ *
+ * {@link ExtensionContext#getTestMethod() getTestMethod()} is no longer
+ * empty, unless the
+ * {@link org.junit.jupiter.api.TestInstance.Lifecycle#PER_CLASS PER_CLASS}
+ * test instance lifecycle is used.
+ *
+ *
+ * If the extension adds a {@link ExtensionContext.Store.CloseableResource
+ * CloseableResource} or {@link AutoCloseable} to the
+ * {@link ExtensionContext.Store Store} (unless the
+ * {@code junit.jupiter.extensions.store.close.autocloseable.enabled}
+ * configuration parameter is set to {@code false}), then the resource will
+ * be closed just after the instance is destroyed.
+ *
+ *
+ * Extensions can now access data previously stored by a
+ * {@link TestTemplateInvocationContext}, unless the
+ * {@link org.junit.jupiter.api.TestInstance.Lifecycle#PER_CLASS PER_CLASS}
+ * test instance lifecycle is used.
+ *
*
*
*
Note: The behavior which is enabled by returning
* {@link ExtensionContextScope#TEST_METHOD TEST_METHOD} from this method
* will become the default in future versions of JUnit. To ensure forward
- * compatibility, extension implementors are therefore advised to opt in,
- * even if they don't require the new functionality.
+ * compatibility, extension authors are therefore advised to opt into this
+ * feature, even if they do not require the new functionality.
*
- * @implNote There are no guarantees about how often this method is called.
- * Therefore, implementations should be idempotent and avoid side
- * effects. They may, however, cache the result for performance in
- * the {@link Store Store} of the supplied
- * {@link ExtensionContext}, if necessary.
+ * @implNote There are no guarantees about how often this method will be called.
+ * Therefore, implementations should be idempotent and avoid side effects.
+ * If computation of the return value is costly, implementations may wish to
+ * cache the result in the {@link ExtensionContext.Store Store} of the supplied
+ * {@code ExtensionContext}.
* @param rootContext the root extension context to allow inspection of
- * configuration parameters; never {@code null}
+ * configuration parameters; never {@code null}
* @since 5.12
*/
@API(status = MAINTAINED, since = "5.13.3")
@@ -96,25 +136,25 @@ default ExtensionContextScope getTestInstantiationExtensionContextScope(Extensio
/**
* {@code ExtensionContextScope} is used to define the scope of the
- * {@link ExtensionContext} passed to an extension during the instantiation
- * of test instances.
+ * {@link ExtensionContext} supplied to an extension during the instantiation
+ * of test classes or processing of test instances.
*
* @since 5.12
- * @see TestInstantiationAwareExtension#getTestInstantiationExtensionContextScope
+ * @see TestInstantiationAwareExtension#getTestInstantiationExtensionContextScope(ExtensionContext)
*/
@API(status = MAINTAINED, since = "5.13.3")
enum ExtensionContextScope {
/**
- * The extension should receive an {@link ExtensionContext} scoped to
- * in the default scope.
+ * The extension should receive an {@link ExtensionContext} for the
+ * the default scope.
*
*
The default scope is determined by the configuration parameter
* {@link #DEFAULT_SCOPE_PROPERTY_NAME}. If not specified, extensions
* will receive an {@link ExtensionContext} scoped to the test class.
*
* @deprecated This behavior will be removed from future versions of
- * JUnit and {@link #TEST_METHOD} will become the default.
+ * JUnit, and {@link #TEST_METHOD} will become the default.
*
* @see #DEFAULT_SCOPE_PROPERTY_NAME
*/
@@ -124,8 +164,12 @@ enum ExtensionContextScope {
/**
* The extension should receive an {@link ExtensionContext} scoped to
- * the test method, unless the
- * {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS} lifecycle is used.
+ * the test method.
+ *
+ *
Note, however, that a test-class scoped context will always be
+ * supplied if the
+ * {@link org.junit.jupiter.api.TestInstance.Lifecycle#PER_CLASS PER_CLASS}
+ * test instance lifecycle is used.
*/
TEST_METHOD;
@@ -140,6 +184,7 @@ enum ExtensionContextScope {
* @see #DEFAULT
*/
public static final String DEFAULT_SCOPE_PROPERTY_NAME = "junit.jupiter.extensions.testinstantiation.extensioncontextscope.default";
+
}
}
diff --git a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt
index eef4af258142..932c281ac982 100644
--- a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt
+++ b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt
@@ -14,6 +14,7 @@ package org.junit.jupiter.api
import org.apiguardian.api.API
import org.apiguardian.api.API.Status.MAINTAINED
import org.apiguardian.api.API.Status.STABLE
+import org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure
import org.junit.jupiter.api.function.Executable
import org.junit.platform.commons.util.UnrecoverableExceptions.rethrowIfUnrecoverable
import java.time.Duration
@@ -347,7 +348,6 @@ inline fun assertThrows(
* @see Assertions.assertDoesNotThrow
* @param R the result type of the [executable]
*/
-@Suppress("LESS_VISIBLE_TYPE_ACCESS_IN_INLINE_WARNING")
@OptIn(ExperimentalContracts::class)
@API(status = STABLE, since = "5.11")
inline fun assertDoesNotThrow(executable: () -> R): R {
@@ -359,7 +359,11 @@ inline fun assertDoesNotThrow(executable: () -> R): R {
return executable()
} catch (t: Throwable) {
rethrowIfUnrecoverable(t)
- throw AssertDoesNotThrow.createAssertionFailedError(null, t)
+ val suffix = t.message?.let { if (it.isNotBlank()) ": ${t.message}" else null } ?: ""
+ throw assertionFailure()
+ .reason("Unexpected exception thrown: ${t.javaClass.getName()}$suffix")
+ .cause(t)
+ .build()
}
}
@@ -396,7 +400,6 @@ inline fun assertDoesNotThrow(
* @see Assertions.assertDoesNotThrow
* @param R the result type of the [executable]
*/
-@Suppress("LESS_VISIBLE_TYPE_ACCESS_IN_INLINE_WARNING")
@OptIn(ExperimentalContracts::class)
@API(status = STABLE, since = "5.11")
inline fun assertDoesNotThrow(
@@ -412,7 +415,12 @@ inline fun assertDoesNotThrow(
return executable()
} catch (t: Throwable) {
rethrowIfUnrecoverable(t)
- throw AssertDoesNotThrow.createAssertionFailedError(message(), t)
+ val suffix = t.message?.let { if (it.isNotBlank()) ": ${t.message}" else null } ?: ""
+ throw assertionFailure()
+ .message(message())
+ .reason("Unexpected exception thrown: ${t.javaClass.getName()}$suffix")
+ .cause(t)
+ .build()
}
}
diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertionTestUtils.java b/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/AssertionTestUtils.java
similarity index 58%
rename from jupiter-tests/src/test/java/org/junit/jupiter/api/AssertionTestUtils.java
rename to junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/AssertionTestUtils.java
index a5bb7c0886a0..1bbfb6f7d59d 100644
--- a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertionTestUtils.java
+++ b/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/AssertionTestUtils.java
@@ -10,44 +10,48 @@
package org.junit.jupiter.api;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
import java.io.Serializable;
+import java.util.List;
import java.util.Objects;
-import org.apache.groovy.parser.antlr4.util.StringUtils;
import org.jspecify.annotations.Nullable;
import org.opentest4j.AssertionFailedError;
+import org.opentest4j.MultipleFailuresError;
import org.opentest4j.ValueWrapper;
-class AssertionTestUtils {
+public class AssertionTestUtils {
private AssertionTestUtils() {
/* no-op */
}
- static void expectAssertionFailedError() {
+ public static void expectAssertionFailedError() {
throw new AssertionError("Should have thrown an " + AssertionFailedError.class.getName());
}
- static void assertEmptyMessage(Throwable ex) throws AssertionError {
- if (!StringUtils.isEmpty(ex.getMessage())) {
+ public static void assertEmptyMessage(Throwable ex) throws AssertionError {
+ if (!(ex.getMessage() == null || ex.getMessage().isEmpty())) {
throw new AssertionError("Exception message should be empty, but was [" + ex.getMessage() + "].");
}
}
- static void assertMessageEquals(Throwable ex, String msg) throws AssertionError {
+ public static void assertMessageEquals(Throwable ex, String msg) throws AssertionError {
if (!msg.equals(ex.getMessage())) {
throw new AssertionError("Exception message should be [" + msg + "], but was [" + ex.getMessage() + "].");
}
}
- static void assertMessageMatches(Throwable ex, String regex) throws AssertionError {
+ public static void assertMessageMatches(Throwable ex, String regex) throws AssertionError {
if (ex.getMessage() == null || !ex.getMessage().matches(regex)) {
throw new AssertionError("Exception message should match regular expression [" + regex + "], but was ["
+ ex.getMessage() + "].");
}
}
- static void assertMessageStartsWith(@Nullable Throwable ex, String msg) throws AssertionError {
+ public static void assertMessageStartsWith(@Nullable Throwable ex, String msg) throws AssertionError {
if (ex == null) {
throw new AssertionError("Cause should not have been null");
}
@@ -57,14 +61,14 @@ static void assertMessageStartsWith(@Nullable Throwable ex, String msg) throws A
}
}
- static void assertMessageEndsWith(Throwable ex, String msg) throws AssertionError {
+ public static void assertMessageEndsWith(Throwable ex, String msg) throws AssertionError {
if (ex.getMessage() == null || !ex.getMessage().endsWith(msg)) {
throw new AssertionError(
"Exception message should end with [" + msg + "], but was [" + ex.getMessage() + "].");
}
}
- static void assertMessageContains(@Nullable Throwable ex, String msg) throws AssertionError {
+ public static void assertMessageContains(@Nullable Throwable ex, String msg) throws AssertionError {
if (ex == null) {
throw new AssertionError("Cause should not have been null");
}
@@ -74,7 +78,7 @@ static void assertMessageContains(@Nullable Throwable ex, String msg) throws Ass
}
}
- static void assertExpectedAndActualValues(AssertionFailedError ex, @Nullable Object expected,
+ public static void assertExpectedAndActualValues(AssertionFailedError ex, @Nullable Object expected,
@Nullable Object actual) throws AssertionError {
if (!wrapsEqualValue(ex.getExpected(), expected)) {
throw new AssertionError("Expected value in AssertionFailedError should equal ["
@@ -86,7 +90,7 @@ static void assertExpectedAndActualValues(AssertionFailedError ex, @Nullable Obj
}
}
- static boolean wrapsEqualValue(ValueWrapper wrapper, @Nullable Object value) {
+ public static boolean wrapsEqualValue(ValueWrapper wrapper, @Nullable Object value) {
if (value == null || value instanceof Serializable) {
return Objects.equals(value, wrapper.getValue());
}
@@ -95,14 +99,32 @@ static boolean wrapsEqualValue(ValueWrapper wrapper, @Nullable Object value) {
&& Objects.equals(wrapper.getType(), value.getClass());
}
- static void recurseIndefinitely() {
+ public static void recurseIndefinitely() {
// simulate infinite recursion
throw new StackOverflowError();
}
- static void runOutOfMemory() {
+ public static void runOutOfMemory() {
// simulate running out of memory
throw new OutOfMemoryError("boom");
}
+ @SafeVarargs
+ public static void assertExpectedExceptionTypes(MultipleFailuresError multipleFailuresError,
+ Class extends Throwable>... exceptionTypes) {
+
+ assertNotNull(multipleFailuresError, "MultipleFailuresError");
+ List failures = multipleFailuresError.getFailures();
+ assertEquals(exceptionTypes.length, failures.size(), "number of failures");
+
+ // Verify that exceptions are also present as suppressed exceptions.
+ // https://github.com/junit-team/junit-framework/issues/1602
+ Throwable[] suppressed = multipleFailuresError.getSuppressed();
+ assertEquals(exceptionTypes.length, suppressed.length, "number of suppressed exceptions");
+
+ for (int i = 0; i < exceptionTypes.length; i++) {
+ assertEquals(exceptionTypes[i], failures.get(i).getClass(), "exception type [" + i + "]");
+ assertEquals(exceptionTypes[i], suppressed[i].getClass(), "suppressed exception type [" + i + "]");
+ }
+ }
}
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java
index 3515a5cfabd7..2f64d22a7af2 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java
@@ -323,11 +323,7 @@ private DiscoverySelector selectClass(List> classes) {
}
private DiscoverySelector selectMethod(List> classes, Method method) {
- if (classes.size() == 1) {
- return DiscoverySelectors.selectMethod(classes.get(0), method);
- }
- int lastIndex = classes.size() - 1;
- return DiscoverySelectors.selectNestedMethod(classes.subList(0, lastIndex), classes.get(lastIndex), method);
+ return new DeclaredMethodSelector(classes, method);
}
static class DummyClassTemplateInvocationContext implements ClassTemplateInvocationContext {
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DeclaredMethodSelector.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DeclaredMethodSelector.java
new file mode 100644
index 000000000000..7f9e5de70e89
--- /dev/null
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DeclaredMethodSelector.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015-2025 the original author or authors.
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v2.0 which
+ * accompanies this distribution and is available at
+ *
+ * https://www.eclipse.org/legal/epl-v20.html
+ */
+
+package org.junit.jupiter.engine.discovery;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+import org.junit.platform.commons.util.Preconditions;
+import org.junit.platform.engine.DiscoverySelector;
+import org.junit.platform.engine.discovery.MethodSelector;
+
+/**
+ * Jupiter-specific selector for methods, potentially in nested classes.
+ *
+ *
The important difference to {@link MethodSelector} is that this selector's
+ * {@link #equals(Object)} method takes into account the selected method's
+ * {@linkplain Method#getDeclaringClass() declaring class} to support cases
+ * where a package-private method is declared in a super class in a different
+ * package and a method with the same signature is declared in a subclass. In
+ * that case both methods should be discovered because the one declared in the
+ * subclass does not override the one in the super class.
+ *
+ * @since 5.14.1
+ */
+record DeclaredMethodSelector(List> testClasses, Method method) implements DiscoverySelector {
+ DeclaredMethodSelector {
+ Preconditions.notEmpty(testClasses, "testClasses must not be empty");
+ Preconditions.containsNoNullElements(testClasses, "testClasses must not contain null elements");
+ Preconditions.notNull(method, "method must not be null");
+ }
+}
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodFinder.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodFinder.java
deleted file mode 100644
index 10bb24ab5139..000000000000
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodFinder.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2015-2025 the original author or authors.
- *
- * All rights reserved. This program and the accompanying materials are
- * made available under the terms of the Eclipse Public License v2.0 which
- * accompanies this distribution and is available at
- *
- * https://www.eclipse.org/legal/epl-v20.html
- */
-
-package org.junit.jupiter.engine.discovery;
-
-import java.lang.reflect.Method;
-import java.util.Optional;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.junit.platform.commons.support.ReflectionSupport;
-import org.junit.platform.commons.util.Preconditions;
-
-/**
- * @since 5.0
- */
-class MethodFinder {
-
- // Pattern: methodName(comma-separated list of parameter type names)
- private static final Pattern METHOD_PATTERN = Pattern.compile("(.+)\\((.*)\\)");
-
- Optional findMethod(String methodSpecPart, Class> clazz) {
- Matcher matcher = METHOD_PATTERN.matcher(methodSpecPart);
-
- Preconditions.condition(matcher.matches(),
- () -> "Method [%s] does not match pattern [%s]".formatted(methodSpecPart, METHOD_PATTERN));
-
- String methodName = matcher.group(1);
- String parameterTypeNames = matcher.group(2);
- return ReflectionSupport.findMethod(clazz, methodName, parameterTypeNames);
- }
-
-}
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSegmentResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSegmentResolver.java
new file mode 100644
index 000000000000..75ec26e2b110
--- /dev/null
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSegmentResolver.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2015-2025 the original author or authors.
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v2.0 which
+ * accompanies this distribution and is available at
+ *
+ * https://www.eclipse.org/legal/epl-v20.html
+ */
+
+package org.junit.jupiter.engine.discovery;
+
+import static org.junit.platform.commons.util.ReflectionUtils.isDeclaredInSamePackage;
+import static org.junit.platform.commons.util.ReflectionUtils.isPackagePrivate;
+
+import java.lang.reflect.Method;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.junit.platform.commons.PreconditionViolationException;
+import org.junit.platform.commons.support.ReflectionSupport;
+import org.junit.platform.commons.util.ClassUtils;
+import org.junit.platform.commons.util.Preconditions;
+import org.junit.platform.commons.util.ReflectionUtils;
+
+/**
+ * @since 5.0
+ */
+class MethodSegmentResolver {
+
+ // Pattern: [declaringClassName#]methodName(comma-separated list of parameter type names)
+ private static final Pattern METHOD_PATTERN = Pattern.compile(
+ "(?:(?.+)#)?(?.+)\\((?.*)\\)");
+
+ /**
+ * If the {@code method} is package-private and declared a class in a
+ * different package than {@code testClass}, the declaring class name is
+ * included in the method's unique ID segment. Otherwise, it only
+ * consists of the method name and its parameter types.
+ */
+ String formatMethodSpecPart(Method method, Class> testClass) {
+ var parameterTypes = ClassUtils.nullSafeToString(method.getParameterTypes());
+ if (isPackagePrivate(method) && !isDeclaredInSamePackage(method.getDeclaringClass(), testClass)) {
+ return "%s#%s(%s)".formatted(method.getDeclaringClass().getName(), method.getName(), parameterTypes);
+ }
+ return "%s(%s)".formatted(method.getName(), parameterTypes);
+ }
+
+ Optional findMethod(String methodSpecPart, Class> testClass) {
+ Matcher matcher = METHOD_PATTERN.matcher(methodSpecPart);
+
+ Preconditions.condition(matcher.matches(),
+ () -> "Method [%s] does not match pattern [%s]".formatted(methodSpecPart, METHOD_PATTERN));
+
+ Class> targetClass = testClass;
+ String declaringClass = matcher.group("declaringClass");
+ if (declaringClass != null) {
+ targetClass = ReflectionUtils.tryToLoadClass(declaringClass).getNonNullOrThrow(
+ cause -> new PreconditionViolationException(
+ "Could not load declaring class with name: " + declaringClass, cause));
+ }
+ String methodName = matcher.group("method");
+ String parameterTypeNames = matcher.group("parameters");
+ return ReflectionSupport.findMethod(targetClass, methodName, parameterTypeNames);
+ }
+
+}
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java
index ad70aaa7913f..07dbb1b3d068 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java
@@ -39,7 +39,6 @@
import org.junit.jupiter.engine.discovery.predicates.IsTestMethod;
import org.junit.jupiter.engine.discovery.predicates.IsTestTemplateMethod;
import org.junit.jupiter.engine.discovery.predicates.TestClassPredicates;
-import org.junit.platform.commons.util.ClassUtils;
import org.junit.platform.engine.DiscoveryIssue;
import org.junit.platform.engine.DiscoveryIssue.Severity;
import org.junit.platform.engine.DiscoverySelector;
@@ -59,7 +58,7 @@
*/
class MethodSelectorResolver implements SelectorResolver {
- private static final MethodFinder methodFinder = new MethodFinder();
+ private static final MethodSegmentResolver methodSegmentResolver = new MethodSegmentResolver();
private final Predicate> testClassPredicate;
private final JupiterConfiguration configuration;
@@ -84,6 +83,20 @@ public Resolution resolve(NestedMethodSelector selector, Context context) {
Match::exact);
}
+ @Override
+ public Resolution resolve(DiscoverySelector selector, Context context) {
+ if (selector instanceof DeclaredMethodSelector methodSelector) {
+ var testClasses = methodSelector.testClasses();
+ if (testClasses.size() == 1) {
+ return resolve(context, emptyList(), testClasses.get(0), methodSelector::method, Match::exact);
+ }
+ int lastIndex = testClasses.size() - 1;
+ return resolve(context, testClasses.subList(0, lastIndex), testClasses.get(lastIndex),
+ methodSelector::method, Match::exact);
+ }
+ return unresolved();
+ }
+
private Resolution resolve(Context context, List> enclosingClasses, Class> testClass,
Supplier methodSupplier,
BiFunction>, Match> matchFactory) {
@@ -209,7 +222,7 @@ Optional resolveUniqueIdIntoTestDescriptor(UniqueId uniqueId, Co
String methodSpecPart = lastSegment.getValue();
Class> testClass = ((TestClassAware) parent).getTestClass();
// @formatter:off
- return methodFinder.findMethod(methodSpecPart, testClass)
+ return methodSegmentResolver.findMethod(methodSpecPart, testClass)
.filter(methodPredicate)
.map(method -> createTestDescriptor(parent, testClass, method, configuration));
// @formatter:on
@@ -223,15 +236,14 @@ Optional resolveUniqueIdIntoTestDescriptor(UniqueId uniqueId, Co
private TestDescriptor createTestDescriptor(TestDescriptor parent, Class> testClass, Method method,
JupiterConfiguration configuration) {
- UniqueId uniqueId = createUniqueId(method, parent);
+ UniqueId uniqueId = createUniqueId(method, parent, testClass);
return testDescriptorFactory.create(uniqueId, testClass, method,
((TestClassAware) parent)::getEnclosingTestClasses, configuration);
}
- private UniqueId createUniqueId(Method method, TestDescriptor parent) {
- String methodId = "%s(%s)".formatted(method.getName(),
- ClassUtils.nullSafeToString(method.getParameterTypes()));
- return parent.getUniqueId().append(segmentType, methodId);
+ private UniqueId createUniqueId(Method method, TestDescriptor parent, Class> testClass) {
+ return parent.getUniqueId().append(segmentType,
+ methodSegmentResolver.formatMethodSpecPart(method, testClass));
}
interface TestDescriptorFactory {
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestableMethod.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestableMethod.java
index 7e95f77f8307..cb54c7b2020e 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestableMethod.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestableMethod.java
@@ -45,7 +45,7 @@ abstract class IsTestableMethod implements Predicate {
@Override
public boolean test(Method candidate) {
- if (isAnnotated(candidate, this.annotationType)) {
+ if (!candidate.isSynthetic() && isAnnotated(candidate, this.annotationType)) {
return condition.check(candidate) && isNotAbstract(candidate);
}
return false;
diff --git a/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts b/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts
index 3ccd1df2b2aa..4db9604a0dbb 100644
--- a/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts
+++ b/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts
@@ -26,13 +26,14 @@ tasks {
bundle {
val importAPIGuardian: String by extra
val importJSpecify: String by extra
+ val importCommonsLogging: String by extra
bnd("""
# Import JUnit4 packages with a version
Import-Package: \
$importAPIGuardian,\
$importJSpecify,\
+ $importCommonsLogging,\
org.junit;version="[${libs.versions.junit4Min.get()},5)",\
- org.junit.platform.commons.logging;status=INTERNAL,\
org.junit.rules;version="[${libs.versions.junit4Min.get()},5)",\
*
""")
diff --git a/junit-jupiter-migrationsupport/src/main/java/module-info.java b/junit-jupiter-migrationsupport/src/main/java/module-info.java
index f2e222996666..91a0ce89d35f 100644
--- a/junit-jupiter-migrationsupport/src/main/java/module-info.java
+++ b/junit-jupiter-migrationsupport/src/main/java/module-info.java
@@ -12,7 +12,10 @@
* Support for migrating from JUnit 4 to JUnit Jupiter.
*
* @since 5.0
+ * @deprecated Please migrate to the corresponding APIs and extensions provided
+ * by JUnit Jupiter.
*/
+@Deprecated(forRemoval = true)
module org.junit.jupiter.migrationsupport {
requires static transitive org.apiguardian.api;
diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentCountValidator.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentCountValidator.java
index eabcb067044f..cbb1330f53a9 100644
--- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentCountValidator.java
+++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentCountValidator.java
@@ -13,6 +13,7 @@
import java.util.Arrays;
import java.util.Optional;
+import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.extension.ExtensionConfigurationException;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
@@ -36,6 +37,7 @@ class ArgumentCountValidator {
}
void validate(ExtensionContext extensionContext) {
+ validateRequiredArgumentsArePresent();
ArgumentCountValidationMode argumentCountValidationMode = getArgumentCountValidationMode(extensionContext);
switch (argumentCountValidationMode) {
case DEFAULT, NONE -> {
@@ -45,17 +47,34 @@ void validate(ExtensionContext extensionContext) {
this.arguments);
int totalCount = this.arguments.getTotalLength();
Preconditions.condition(consumedCount == totalCount,
- () -> "Configuration error: @%s consumes %s %s but there %s %s %s provided.%nNote: the provided arguments were %s".formatted(
- this.declarationContext.getAnnotationName(), consumedCount,
- pluralize(consumedCount, "parameter", "parameters"), pluralize(totalCount, "was", "were"),
- totalCount, pluralize(totalCount, "argument", "arguments"),
- Arrays.toString(this.arguments.getAllPayloads())));
+ () -> wrongNumberOfArgumentsMessages("consumes", consumedCount, null, null));
}
default -> throw new ExtensionConfigurationException(
"Unsupported argument count validation mode: " + argumentCountValidationMode);
}
}
+ private void validateRequiredArgumentsArePresent() {
+ var requiredParameterCount = this.declarationContext.getResolverFacade().getRequiredParameterCount();
+ if (requiredParameterCount != null) {
+ var totalCount = this.arguments.getTotalLength();
+ Preconditions.condition(requiredParameterCount.value() <= totalCount,
+ () -> wrongNumberOfArgumentsMessages("has", requiredParameterCount.value(), "required",
+ requiredParameterCount.reason()));
+ }
+ }
+
+ private String wrongNumberOfArgumentsMessages(String verb, int actualCount, @Nullable String parameterAdjective,
+ @Nullable String reason) {
+ int totalCount = this.arguments.getTotalLength();
+ return "Configuration error: @%s %s %s %s%s%s but there %s %s %s provided.%nNote: the provided arguments were %s".formatted(
+ this.declarationContext.getAnnotationName(), verb, actualCount,
+ parameterAdjective == null ? "" : parameterAdjective + " ",
+ pluralize(actualCount, "parameter", "parameters"), reason == null ? "" : " (due to %s)".formatted(reason),
+ pluralize(totalCount, "was", "were"), totalCount, pluralize(totalCount, "argument", "arguments"),
+ Arrays.toString(this.arguments.getAllPayloads()));
+ }
+
private ArgumentCountValidationMode getArgumentCountValidationMode(ExtensionContext extensionContext) {
ArgumentCountValidationMode mode = declarationContext.getArgumentCountValidationMode();
if (mode != ArgumentCountValidationMode.DEFAULT) {
diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassContext.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassContext.java
index f0402064a5a8..67f40bbc649b 100644
--- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassContext.java
+++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassContext.java
@@ -130,6 +130,7 @@ public ResolverFacade getResolverFacade() {
@Override
public ParameterizedClassInvocationContext createInvocationContext(ParameterizedInvocationNameFormatter formatter,
Arguments arguments, int invocationIndex) {
+
return new ParameterizedClassInvocationContext(this, formatter, arguments, invocationIndex);
}
diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ResolverFacade.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ResolverFacade.java
index 29d1cd9dae5c..1eb0c2d721f3 100644
--- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ResolverFacade.java
+++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ResolverFacade.java
@@ -98,7 +98,9 @@ static ResolverFacade create(Class> clazz, List fields) {
Stream.concat(uniqueIndexedParameters.values().stream(), aggregatorParameters.stream()) //
.forEach(declaration -> makeAccessible(declaration.getField()));
- return new ResolverFacade(clazz, uniqueIndexedParameters, aggregatorParameters, 0);
+ var requiredParameterCount = new RequiredParameterCount(uniqueIndexedParameters.size(), "field injection");
+
+ return new ResolverFacade(clazz, uniqueIndexedParameters, aggregatorParameters, 0, requiredParameterCount);
}
static ResolverFacade create(Constructor> constructor, ParameterizedClass annotation) {
@@ -155,27 +157,35 @@ else if (aggregatorParameters.isEmpty()) {
}
}
return new ResolverFacade(executable, indexedParameters, new LinkedHashSet<>(aggregatorParameters.values()),
- indexOffset);
+ indexOffset, null);
}
private final int parameterIndexOffset;
private final Map resolvers;
private final DefaultParameterDeclarations indexedParameterDeclarations;
private final Set extends ResolvableParameterDeclaration> aggregatorParameters;
+ private final @Nullable RequiredParameterCount requiredParameterCount;
private ResolverFacade(AnnotatedElement sourceElement,
NavigableMap indexedParameters,
- Set extends ResolvableParameterDeclaration> aggregatorParameters, int parameterIndexOffset) {
+ Set extends ResolvableParameterDeclaration> aggregatorParameters, int parameterIndexOffset,
+ @Nullable RequiredParameterCount requiredParameterCount) {
this.aggregatorParameters = aggregatorParameters;
this.parameterIndexOffset = parameterIndexOffset;
this.resolvers = new ConcurrentHashMap<>(indexedParameters.size() + aggregatorParameters.size());
this.indexedParameterDeclarations = new DefaultParameterDeclarations(sourceElement, indexedParameters);
+ this.requiredParameterCount = requiredParameterCount;
}
ParameterDeclarations getIndexedParameterDeclarations() {
return this.indexedParameterDeclarations;
}
+ @Nullable
+ RequiredParameterCount getRequiredParameterCount() {
+ return this.requiredParameterCount;
+ }
+
boolean isSupportedParameter(ParameterContext parameterContext, EvaluatedArgumentSet arguments) {
int index = toLogicalIndex(parameterContext);
if (this.indexedParameterDeclarations.get(index).isPresent()) {
@@ -495,6 +505,7 @@ private record Converter(ArgumentConverter argumentConverter) implements Resolve
@Override
public @Nullable Object resolve(FieldContext fieldContext, ExtensionContext extensionContext,
EvaluatedArgumentSet arguments, int invocationIndex) {
+
Object argument = arguments.getConsumedPayload(fieldContext.getParameterIndex());
try {
return this.argumentConverter.convert(argument, fieldContext);
@@ -752,4 +763,7 @@ public boolean supports(ParameterContext parameterContext) {
invocationIndex, Optional.of(parameterContext)));
}
}
+
+ record RequiredParameterCount(int value, String reason) {
+ }
}
diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java
index 37ad452ac19d..3b77849b8e52 100644
--- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java
+++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java
@@ -13,6 +13,7 @@
import static org.apiguardian.api.API.Status.STABLE;
import org.apiguardian.api.API;
+import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.params.support.FieldContext;
@@ -43,7 +44,8 @@ public abstract class TypedArgumentConverter impl
* @param targetType the type of the target object to create from the source;
* never {@code null}
*/
- protected TypedArgumentConverter(Class sourceType, Class targetType) {
+ protected TypedArgumentConverter(Class sourceType,
+ @SuppressWarnings("NullableProblems") Class<@NonNull T> targetType) {
this.sourceType = Preconditions.notNull(sourceType, "sourceType must not be null");
this.targetType = Preconditions.notNull(targetType, "targetType must not be null");
}
diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java
index 28a1bf469c77..2e4287b314ec 100644
--- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java
+++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java
@@ -10,6 +10,7 @@
package org.junit.jupiter.params.provider;
+import static org.apiguardian.api.API.Status.EXPERIMENTAL;
import static org.apiguardian.api.API.Status.STABLE;
import java.lang.annotation.Documented;
@@ -35,8 +36,8 @@
* that the first record may optionally be used to supply CSV headers (see
* {@link #useHeadersInDisplayName}).
*
- *
Any line beginning with a {@code #} symbol will be interpreted as a comment
- * and will be ignored.
+ *
Any line beginning with a {@link #commentCharacter}
+ * will be interpreted as a comment and will be ignored.
*
*
The column delimiter (which defaults to a comma ({@code ,})) can be customized
* via either {@link #delimiter} or {@link #delimiterString}.
@@ -63,6 +64,10 @@
* column is trimmed by default. This behavior can be changed by setting the
* {@link #ignoreLeadingAndTrailingWhitespace} attribute to {@code true}.
*
+ *
Note that {@link #delimiter} (or {@link #delimiterString}),
+ * {@link #quoteCharacter}, and {@link #commentCharacter} are treated as
+ * control characters and must all be distinct.
+ *
*
Inheritance
*
*
This annotation is inherited to subclasses.
@@ -235,4 +240,22 @@
@API(status = STABLE, since = "5.10")
boolean ignoreLeadingAndTrailingWhitespace() default true;
+ /**
+ * The character used to denote comments when reading the CSV files.
+ *
+ *
Any line that begins with this character will be treated as a comment
+ * and ignored during parsing. Note that there is one exception to this rule:
+ * if the comment character appears within a quoted field, it loses its
+ * special meaning.
+ *
+ *
The comment character must be the first character on the line without
+ * any leading whitespace.
+ *
+ *
Note that {@link #delimiter} (or {@link #delimiterString}),
+ * {@link #quoteCharacter}, and {@link #commentCharacter} (when
+ * {@link #textBlock} is used) are treated as control characters.
+ *
+ *
+ *
{@link #delimiter} and {@link #quoteCharacter} must always be distinct.
+ *
{@link #commentCharacter} must be distinct from the others only when
+ * {@link #textBlock} is used.
+ *
+ *
*
Inheritance
*
*
This annotation is inherited to subclasses.
@@ -123,7 +134,7 @@
* via this attribute or the {@link #value} attribute.
*
*
Text block syntax is supported by various languages on the JVM
- * including Java SE 15 or higher. If text blocks are not supported, you
+ * including Java SE. If text blocks are not supported, you
* should declare your CSV content via the {@link #value} attribute.
*
*
Each record in the text block corresponds to a record in a CSV file and will
@@ -132,17 +143,20 @@
* {@link #useHeadersInDisplayName}).
*
*
In contrast to CSV records supplied via {@link #value}, a text block
- * can contain comments. Any line beginning with a hash tag ({@code #}) will
- * be treated as a comment and ignored. Note, however, that the {@code #}
- * symbol must be the first character on the line without any leading
- * whitespace. It is therefore recommended that the closing text block
+ * can contain comments. Any line beginning with a {@link #commentCharacter}
+ * will be treated as a comment and ignored. Note that there is one exception
+ * to this rule: if the comment character appears within a quoted field,
+ * it loses its special meaning.
+ *
+ *
The comment character must be the first character on the line without
+ * any leading whitespace. It is therefore recommended that the closing text block
* delimiter {@code """} be placed either at the end of the last line of
* input or on the following line, vertically aligned with the rest of the
* input (as can be seen in the example below).
*
- *
Java's text block
* feature automatically removes incidental whitespace when the code
- * is compiled. However other JVM languages such as Groovy and Kotlin do not.
+ * 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.
@@ -296,4 +310,22 @@
@API(status = STABLE, since = "5.10")
boolean ignoreLeadingAndTrailingWhitespace() default true;
+ /**
+ * The character used to denote comments in a {@linkplain #textBlock text block}.
+ *
+ *
Any line that begins with this character will be treated as a comment
+ * and ignored during parsing. Note that there is one exception to this rule:
+ * if the comment character appears within a quoted field, it loses its
+ * special meaning.
+ *
+ *
The comment character must be the first character on the line without
+ * any leading whitespace.
+ *
+ *
Defaults to {@code '#'}.
+ *
+ * @since 6.0.1
+ */
+ @API(status = EXPERIMENTAL, since = "6.0.1")
+ char commentCharacter() default '#';
+
}
diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/KotlinReflectionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/KotlinReflectionUtils.java
index 626f3247c552..f3a04fe2e1a7 100644
--- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/KotlinReflectionUtils.java
+++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/KotlinReflectionUtils.java
@@ -67,7 +67,7 @@ private static Try> tryToLoadKotlinMetadataClass() {
*/
@API(status = INTERNAL, since = "6.0")
public static boolean isKotlinSuspendingFunction(Method method) {
- if (kotlinCoroutineContinuation != null && isKotlinType(method.getDeclaringClass())) {
+ if (!method.isSynthetic() && kotlinCoroutineContinuation != null && isKotlinType(method.getDeclaringClass())) {
int parameterCount = method.getParameterCount();
return parameterCount > 0 //
&& method.getParameterTypes()[parameterCount - 1] == kotlinCoroutineContinuation;
diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java
index 1ae566fde234..aa1ccc5ce011 100644
--- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java
+++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java
@@ -1841,20 +1841,32 @@ private static boolean isMethodOverriddenBy(Method upper, Method lower) {
}
// Cannot override a package-private method in another package.
- if (isPackagePrivate(upper) && !declaredInSamePackage(upper, lower)) {
+ if (isPackagePrivate(upper) && !isDeclaredInSamePackage(upper, lower)) {
return false;
}
return hasCompatibleSignature(upper, lower.getName(), lower.getParameterTypes());
}
- private static boolean isPackagePrivate(Member member) {
+ /**
+ * @since 5.14.1
+ */
+ @API(status = INTERNAL, since = "5.14.1")
+ public static boolean isPackagePrivate(Member member) {
int modifiers = member.getModifiers();
return !(Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers) || Modifier.isPrivate(modifiers));
}
- private static boolean declaredInSamePackage(Method m1, Method m2) {
- return m1.getDeclaringClass().getPackageName().equals(m2.getDeclaringClass().getPackageName());
+ private static boolean isDeclaredInSamePackage(Method m1, Method m2) {
+ return isDeclaredInSamePackage(m1.getDeclaringClass(), m2.getDeclaringClass());
+ }
+
+ /**
+ * @since 5.14.1
+ */
+ @API(status = INTERNAL, since = "5.14.1")
+ public static boolean isDeclaredInSamePackage(Class> c1, Class> c2) {
+ return c1.getPackageName().equals(c2.getPackageName());
}
/**
diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java
index 1fd222935030..738b0dbe6590 100644
--- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java
+++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java
@@ -46,7 +46,7 @@ public final class StringUtils {
* @see #1800
*/
static Pattern compileIsoControlPattern() {
- // https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html#posix
+ // https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/regex/Pattern.html#posix
try {
// All of the characters that Unicode refers to as 'control characters'
return Pattern.compile("\\p{Cntrl}", UNICODE_CHARACTER_CLASS);
diff --git a/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/PreconditionAssertions.java b/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/PreconditionAssertions.java
index 926ff63204e3..b8cc0bd19998 100644
--- a/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/PreconditionAssertions.java
+++ b/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/PreconditionAssertions.java
@@ -32,10 +32,22 @@ public static void assertPreconditionViolationNotNullFor(String name, ThrowingCa
assertPreconditionViolationFor(throwingCallable).withMessage("%s must not be null", name);
}
+ public static void assertPreconditionViolationNotBlankFor(String name, ThrowingCallable throwingCallable) {
+ assertPreconditionViolationFor(throwingCallable).withMessageContaining("%s must not be blank", name);
+ }
+
+ public static void assertPreconditionViolationNotEmptyFor(String name, ThrowingCallable throwingCallable) {
+ assertPreconditionViolationFor(throwingCallable).withMessage("%s must not be empty", name);
+ }
+
public static void assertPreconditionViolationNotNullOrBlankFor(String name, ThrowingCallable throwingCallable) {
assertPreconditionViolationFor(throwingCallable).withMessage("%s must not be null or blank", name);
}
+ public static void assertPreconditionViolationNotNullOrEmptyFor(String name, ThrowingCallable throwingCallable) {
+ assertPreconditionViolationFor(throwingCallable).withMessage("%s must not be null or empty", name);
+ }
+
public static ThrowableAssertAlternative assertPreconditionViolationFor(
ThrowingCallable throwingCallable) {
diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/command/BaseCommand.java b/junit-platform-console/src/main/java/org/junit/platform/console/command/BaseCommand.java
index 30a53d6e6169..99c9872107cf 100644
--- a/junit-platform-console/src/main/java/org/junit/platform/console/command/BaseCommand.java
+++ b/junit-platform-console/src/main/java/org/junit/platform/console/command/BaseCommand.java
@@ -59,14 +59,14 @@ static CommandLine initialize(CommandLine commandLine) {
return commandLine //
.setParameterExceptionHandler((ex, args) -> {
defaultParameterExceptionHandler.handleParseException(ex, args);
- return CommandResult.FAILURE;
+ return ExitCode.ANY_ERROR;
}) //
.setExecutionExceptionHandler((ex, cmd, __) -> {
commandLine.getErr().println(cmd.getColorScheme().richStackTraceString(ex));
commandLine.getErr().println();
commandLine.getErr().flush();
cmd.usage(commandLine.getOut());
- return CommandResult.FAILURE;
+ return ExitCode.ANY_ERROR;
}) //
.setCaseInsensitiveEnumValuesAllowed(true) //
.setAtFileCommentChar(null);
diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/command/CommandResult.java b/junit-platform-console/src/main/java/org/junit/platform/console/command/CommandResult.java
index 93285654aca3..3d104c59258e 100644
--- a/junit-platform-console/src/main/java/org/junit/platform/console/command/CommandResult.java
+++ b/junit-platform-console/src/main/java/org/junit/platform/console/command/CommandResult.java
@@ -22,23 +22,8 @@
*/
@API(status = INTERNAL, since = "1.10")
public class CommandResult {
-
- /**
- * Exit code indicating successful execution.
- */
- public static final int SUCCESS = 0;
-
- /**
- * Exit code indicating any failure(s).
- */
- protected static final int FAILURE = -1;
-
public static CommandResult success() {
- return create(SUCCESS, null);
- }
-
- public static CommandResult failure() {
- return create(FAILURE, null);
+ return create(ExitCode.SUCCESS, null);
}
public static CommandResult create(int exitCode, @Nullable T value) {
diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/command/ExecuteTestsCommand.java b/junit-platform-console/src/main/java/org/junit/platform/console/command/ExecuteTestsCommand.java
index 79a1207a4790..76add36ffa70 100644
--- a/junit-platform-console/src/main/java/org/junit/platform/console/command/ExecuteTestsCommand.java
+++ b/junit-platform-console/src/main/java/org/junit/platform/console/command/ExecuteTestsCommand.java
@@ -10,7 +10,9 @@
package org.junit.platform.console.command;
-import static org.junit.platform.console.command.CommandResult.SUCCESS;
+import static org.junit.platform.console.command.ExitCode.NO_TESTS_FOUND;
+import static org.junit.platform.console.command.ExitCode.SUCCESS;
+import static org.junit.platform.console.command.ExitCode.TEST_FAILED;
import java.io.PrintWriter;
import java.nio.file.Path;
@@ -34,17 +36,6 @@
description = "Execute tests" //
)
class ExecuteTestsCommand extends BaseCommand implements CommandLine.IExitCodeGenerator {
-
- /**
- * Exit code indicating test failure(s)
- */
- private static final int TEST_FAILED = 1;
-
- /**
- * Exit code indicating no tests found
- */
- private static final int NO_TESTS_FOUND = 2;
-
private final ConsoleTestExecutor.Factory consoleTestExecutorFactory;
@Mixin
diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/command/ExitCode.java b/junit-platform-console/src/main/java/org/junit/platform/console/command/ExitCode.java
new file mode 100644
index 000000000000..6e3a60a80c1f
--- /dev/null
+++ b/junit-platform-console/src/main/java/org/junit/platform/console/command/ExitCode.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2015-2025 the original author or authors.
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v2.0 which
+ * accompanies this distribution and is available at
+ *
+ * https://www.eclipse.org/legal/epl-v20.html
+ */
+
+package org.junit.platform.console.command;
+
+/**
+ * Well-known exit codes of the {@code junit} tool.
+ *
+ * @since 6.0.1
+ */
+final class ExitCode {
+ /**
+ * Exit code indicating a successful tool run.
+ */
+ public static final int SUCCESS = 0;
+
+ /**
+ * Exit code indicating an unsuccessful run.
+ */
+ public static final int ANY_ERROR = -1;
+
+ /**
+ * Exit code indicating test failure(s).
+ */
+ public static final int TEST_FAILED = 1;
+
+ /**
+ * Exit code indicating no tests found.
+ */
+ public static final int NO_TESTS_FOUND = 2;
+
+ /**
+ * Exit code indicating invalid user input.
+ */
+ public static final int INVALID_INPUT = 3;
+
+ private ExitCode() {
+ }
+}
diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/command/MainCommand.java b/junit-platform-console/src/main/java/org/junit/platform/console/command/MainCommand.java
index 3d49ffb18175..224afd8887fe 100644
--- a/junit-platform-console/src/main/java/org/junit/platform/console/command/MainCommand.java
+++ b/junit-platform-console/src/main/java/org/junit/platform/console/command/MainCommand.java
@@ -39,8 +39,8 @@
footer = "For more information, please refer to the JUnit User Guide at%n" //
+ "@|underline https://docs.junit.org/${junit.docs.version}/user-guide/|@", //
scope = CommandLine.ScopeType.INHERIT, //
- exitCodeOnInvalidInput = CommandResult.FAILURE, //
- exitCodeOnExecutionException = CommandResult.FAILURE, //
+ exitCodeOnInvalidInput = ExitCode.INVALID_INPUT, //
+ exitCodeOnExecutionException = ExitCode.ANY_ERROR, //
versionProvider = ManifestVersionProvider.class //
)
class MainCommand implements Runnable, IExitCodeGenerator {
diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java
index 27fd6aea479a..5e90ac31538c 100644
--- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java
+++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java
@@ -82,44 +82,38 @@ public final class MethodSelector implements DiscoverySelector {
}
MethodSelector(Class> javaClass, String methodName, String parameterTypeNames) {
- this.classLoader = javaClass.getClassLoader();
+ this(javaClass.getClassLoader(), javaClass.getName(), methodName, parameterTypeNames);
this.javaClass = javaClass;
- this.className = javaClass.getName();
- this.methodName = methodName;
- this.parameterTypeNames = parameterTypeNames;
}
/**
* @since 1.10
*/
MethodSelector(@Nullable ClassLoader classLoader, String className, String methodName, Class>... parameterTypes) {
- this.classLoader = classLoader;
- this.className = className;
- this.methodName = methodName;
+ this(classLoader, className, methodName, ClassUtils.nullSafeToString(Class::getTypeName, parameterTypes));
this.parameterTypes = parameterTypes.clone();
- this.parameterTypeNames = ClassUtils.nullSafeToString(Class::getTypeName, this.parameterTypes);
}
/**
* @since 1.10
*/
MethodSelector(Class> javaClass, String methodName, Class>... parameterTypes) {
- this.classLoader = javaClass.getClassLoader();
+ this(javaClass.getClassLoader(), javaClass.getName(), methodName,
+ ClassUtils.nullSafeToString(Class::getTypeName, parameterTypes));
this.javaClass = javaClass;
- this.className = javaClass.getName();
- this.methodName = methodName;
this.parameterTypes = parameterTypes.clone();
- this.parameterTypeNames = ClassUtils.nullSafeToString(Class::getTypeName, this.parameterTypes);
}
MethodSelector(Class> javaClass, Method method) {
- this.classLoader = javaClass.getClassLoader();
+ this(javaClass, method, method.getParameterTypes());
+ }
+
+ private MethodSelector(Class> javaClass, Method method, Class>... parameterTypes) {
+ this(javaClass.getClassLoader(), javaClass.getName(), method.getName(),
+ ClassUtils.nullSafeToString(Class::getTypeName, parameterTypes));
this.javaClass = javaClass;
- this.className = javaClass.getName();
this.javaMethod = method;
- this.methodName = method.getName();
- this.parameterTypes = method.getParameterTypes();
- this.parameterTypeNames = ClassUtils.nullSafeToString(Class::getTypeName, this.parameterTypes);
+ this.parameterTypes = parameterTypes;
}
/**
diff --git a/junit-platform-launcher/junit-platform-launcher.gradle.kts b/junit-platform-launcher/junit-platform-launcher.gradle.kts
index 5683af963816..fd9688f2090f 100644
--- a/junit-platform-launcher/junit-platform-launcher.gradle.kts
+++ b/junit-platform-launcher/junit-platform-launcher.gradle.kts
@@ -23,8 +23,17 @@ javadocConventions {
tasks {
jar {
bundle {
+ val importAPIGuardian: String by extra
+ val importJSpecify: String by extra
+ val importCommonsLogging: String by extra
val version = project.version
bnd("""
+ Import-Package: \
+ ${importAPIGuardian},\
+ ${importJSpecify},\
+ ${importCommonsLogging},\
+ jdk.jfr;resolution:="optional",\
+ *
Provide-Capability:\
org.junit.platform.launcher;\
org.junit.platform.launcher='junit-platform-launcher';\
diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Suite.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Suite.java
index c44c5d9a6bc8..4edaa75c64f5 100644
--- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Suite.java
+++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Suite.java
@@ -26,7 +26,7 @@
* {@code @Suite} marks a class as a test suite on the JUnit Platform.
*
*
Selector and filter annotations are used to control the contents of the
- * suite. Additionally configuration can be passed to the suite via the
+ * suite. Additionally, configuration can be passed to the suite via the
* configuration annotations.
*
*
When the {@link IncludeClassNamePatterns @IncludeClassNamePatterns}
@@ -44,6 +44,17 @@
* annotation disables the latter as a source of parameters so that only explicit
* configuration parameters are taken into account.
*
+ *
Note: Depending on the declared test selection, different suites may contain the
+ * same tests, potentially with different configurations.
+ * Moreover, tests in a suite are executed in addition to the tests executed by every
+ * other test engine. This can result in the same tests being executed twice and can be
+ * prevented by configuring your build tool to only include the
+ * {@code junit-platform-suite} engine. Or by using a naming pattern. For example, name
+ * all suites {@code *Suite} and all tests {@code *Test} and configure your build tool
+ * to only include the former.
+ *
+ *
Alternatively, consider using tags to select specific groups of tests.
+ *
* @since 1.8
* @see Select
* @see SelectClasses
diff --git a/junit-vintage-engine/junit-vintage-engine.gradle.kts b/junit-vintage-engine/junit-vintage-engine.gradle.kts
index 493ac94e0605..57d83723d23b 100644
--- a/junit-vintage-engine/junit-vintage-engine.gradle.kts
+++ b/junit-vintage-engine/junit-vintage-engine.gradle.kts
@@ -38,8 +38,8 @@ tasks {
}
compileTestFixturesGroovy {
javaLauncher = project.javaToolchains.launcherFor {
- // Groovy 2.x (used for Spock tests) does not support current JDKs
- languageVersion = JavaLanguageVersion.of(8)
+ // Groovy 2.x (used for Spock tests) does not run on more recent JDKs
+ languageVersion = JavaLanguageVersion.of(17)
}
}
jar {
@@ -48,16 +48,17 @@ tasks {
val version = project.version
val importAPIGuardian: String by extra
val importJSpecify: String by extra
+ val importCommonsLogging: String by extra
bnd("""
# Import JUnit4 packages with a version
Import-Package: \
${importAPIGuardian},\
${importJSpecify},\
+ ${importCommonsLogging},\
junit.runner;version="[${junit4Min},5)",\
org.junit;version="[${junit4Min},5)",\
org.junit.experimental.categories;version="[${junit4Min},5)",\
org.junit.internal.builders;version="[${junit4Min},5)",\
- org.junit.platform.commons.logging;status=INTERNAL,\
org.junit.runner.*;version="[${junit4Min},5)",\
org.junit.runners.model;version="[${junit4Min},5)",\
*
diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java
index 97838f336ab4..770848c22884 100644
--- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java
+++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java
@@ -72,6 +72,18 @@ public final class Constants {
@API(status = MAINTAINED, since = "5.13.3")
public static final String PARALLEL_METHOD_EXECUTION = "junit.vintage.execution.parallel.methods";
+ /**
+ * Property name used to configure whether the JUnit Vintage engine should
+ * report discovery issues such as deprecation notices.
+ *
+ *
Set this property to {@code false} to disable reporting of discovery
+ * issues. Defaults to {@code true}.
+ *
+ * @since 6.0.1
+ */
+ @API(status = MAINTAINED, since = "6.0.1")
+ public static final String DISCOVERY_ISSUE_REPORTING_ENABLED_PROPERTY_NAME = "junit.vintage.discovery.issue.reporting.enabled";
+
private Constants() {
/* no-op */
}
diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java
index df0e99c50462..e91e684e4d0a 100644
--- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java
+++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java
@@ -19,6 +19,7 @@
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver;
+import org.junit.vintage.engine.Constants;
import org.junit.vintage.engine.descriptor.RunnerTestDescriptor;
import org.junit.vintage.engine.descriptor.VintageEngineDescriptor;
@@ -46,7 +47,7 @@ public VintageEngineDescriptor discover(EngineDiscoveryRequest discoveryRequest,
RunnerTestDescriptor runnerTestDescriptor = (RunnerTestDescriptor) testDescriptor;
postProcessor.applyFiltersAndCreateDescendants(runnerTestDescriptor);
}
- if (!engineDescriptor.getChildren().isEmpty()) {
+ if (isDiscoveryIssueReportingEnabled(discoveryRequest) && !engineDescriptor.getChildren().isEmpty()) {
var issue = DiscoveryIssue.create(DiscoveryIssue.Severity.INFO, //
"The JUnit Vintage engine is deprecated and should only be " //
+ "used temporarily while migrating tests to JUnit Jupiter or another testing " //
@@ -56,4 +57,11 @@ public VintageEngineDescriptor discover(EngineDiscoveryRequest discoveryRequest,
return engineDescriptor;
}
+ @SuppressWarnings("deprecation")
+ private static boolean isDiscoveryIssueReportingEnabled(EngineDiscoveryRequest discoveryRequest) {
+ return discoveryRequest.getConfigurationParameters() //
+ .getBoolean(Constants.DISCOVERY_ISSUE_REPORTING_ENABLED_PROPERTY_NAME) //
+ .orElse(true);
+ }
+
}
diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineDiscoveryTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineDiscoveryTests.java
index 26b6eeb12d63..bf35843dbfcf 100644
--- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineDiscoveryTests.java
+++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineDiscoveryTests.java
@@ -713,7 +713,7 @@ void reportsNoDiscoveryIssuesWhenNoTestsAreFound() {
}
@Test
- void reportDiscoveryIssueWhenTestsAreFound() {
+ void reportDiscoveryIssueWhenTestsAreFoundByDefault() {
var request = discoveryRequestForClass(PlainJUnit4TestCaseWithSingleTestWhichFails.class);
var results = discover(request);
@@ -725,6 +725,18 @@ void reportDiscoveryIssueWhenTestsAreFound() {
assertThat(issue.message()).contains("JUnit Vintage engine is deprecated");
}
+ @SuppressWarnings("deprecation")
+ @Test
+ void reportNoDiscoveryIssueWhenTestsAreFoundButConfigurationParameterIsSet() {
+ var request = request() //
+ .selectors(selectClass(PlainJUnit4TestCaseWithSingleTestWhichFails.class)) //
+ .configurationParameter(Constants.DISCOVERY_ISSUE_REPORTING_ENABLED_PROPERTY_NAME, "false").build();
+
+ var results = discover(request);
+
+ assertThat(results.getDiscoveryIssues()).isEmpty();
+ }
+
private TestDescriptor findChildByDisplayName(TestDescriptor runnerDescriptor, String displayName) {
// @formatter:off
var children = runnerDescriptor.getChildren();
diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/OrFilterTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/OrFilterTests.java
index 6e5d29569b23..470fe5a525e7 100644
--- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/OrFilterTests.java
+++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/OrFilterTests.java
@@ -11,8 +11,8 @@
package org.junit.vintage.engine.descriptor;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotEmptyFor;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.mock;
@@ -23,7 +23,6 @@
import java.util.Set;
import org.junit.jupiter.api.Test;
-import org.junit.platform.commons.PreconditionViolationException;
import org.junit.runner.Description;
import org.junit.runner.manipulation.Filter;
@@ -34,8 +33,7 @@ class OrFilterTests {
@Test
void exceptionWithoutAnyFilters() {
- var actual = assertThrows(PreconditionViolationException.class, () -> new OrFilter(Set.of()));
- assertEquals("filters must not be empty", actual.getMessage());
+ assertPreconditionViolationNotEmptyFor("filters", () -> new OrFilter(Set.of()));
}
@Test
diff --git a/junit-vintage-engine/src/test/resources/log4j2-test.xml b/junit-vintage-engine/src/test/resources/log4j2-test.xml
index 063e415913fd..3bade9825b86 100644
--- a/junit-vintage-engine/src/test/resources/log4j2-test.xml
+++ b/junit-vintage-engine/src/test/resources/log4j2-test.xml
@@ -1,16 +1,19 @@
-
+
-
+
-
-
-
-
-
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java
index 0bee5bb609be..bce9f639e92e 100644
--- a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java
@@ -12,21 +12,20 @@
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals;
+import static org.junit.jupiter.api.AssertionTestUtils.assertExpectedExceptionTypes;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor;
+import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullOrEmptyFor;
import java.io.IOException;
import java.util.Collection;
-import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.function.Executable;
-import org.junit.platform.commons.PreconditionViolationException;
import org.opentest4j.AssertionFailedError;
import org.opentest4j.MultipleFailuresError;
@@ -40,36 +39,36 @@ class AssertAllAssertionsTests {
@SuppressWarnings("DataFlowIssue")
@Test
void assertAllWithNullExecutableArray() {
- assertPrecondition("executables array must not be null or empty", () -> assertAll((Executable[]) null));
+ assertPreconditionViolationNotNullOrEmptyFor("executables array", () -> assertAll((Executable[]) null));
}
@SuppressWarnings("DataFlowIssue")
@Test
void assertAllWithNullExecutableCollection() {
- assertPrecondition("executables collection must not be null", () -> assertAll((Collection) null));
+ assertPreconditionViolationNotNullFor("executables collection", () -> assertAll((Collection) null));
}
@SuppressWarnings("DataFlowIssue")
@Test
void assertAllWithNullExecutableStream() {
- assertPrecondition("executables stream must not be null", () -> assertAll((Stream) null));
+ assertPreconditionViolationNotNullFor("executables stream", () -> assertAll((Stream) null));
}
@SuppressWarnings("DataFlowIssue")
@Test
void assertAllWithNullInExecutableArray() {
- assertPrecondition("individual executables must not be null", () -> assertAll((Executable) null));
+ assertPreconditionViolationNotNullFor("individual executables", () -> assertAll((Executable) null));
}
@Test
void assertAllWithNullInExecutableCollection() {
- assertPrecondition("individual executables must not be null", () -> assertAll(asList((Executable) null)));
+ assertPreconditionViolationNotNullFor("individual executables", () -> assertAll(asList((Executable) null)));
}
@SuppressWarnings("DataFlowIssue")
@Test
void assertAllWithNullInExecutableStream() {
- assertPrecondition("individual executables must not be null", () -> assertAll(Stream.of((Executable) null)));
+ assertPreconditionViolationNotNullFor("individual executables", () -> assertAll(Stream.of((Executable) null)));
}
@Test
@@ -198,30 +197,6 @@ void assertAllWithParallelStream() {
assertThat(multipleFailuresError.getFailures()).hasSize(100).doesNotContainNull();
}
- private void assertPrecondition(String msg, Executable executable) {
- PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, executable);
- assertMessageEquals(exception, msg);
- }
-
- @SafeVarargs
- static void assertExpectedExceptionTypes(MultipleFailuresError multipleFailuresError,
- Class extends Throwable>... exceptionTypes) {
-
- assertNotNull(multipleFailuresError, "MultipleFailuresError");
- List failures = multipleFailuresError.getFailures();
- assertEquals(exceptionTypes.length, failures.size(), "number of failures");
-
- // Verify that exceptions are also present as suppressed exceptions.
- // https://github.com/junit-team/junit-framework/issues/1602
- Throwable[] suppressed = multipleFailuresError.getSuppressed();
- assertEquals(exceptionTypes.length, suppressed.length, "number of suppressed exceptions");
-
- for (int i = 0; i < exceptionTypes.length; i++) {
- assertEquals(exceptionTypes[i], failures.get(i).getClass(), "exception type [" + i + "]");
- assertEquals(exceptionTypes[i], suppressed[i].getClass(), "suppressed exception type [" + i + "]");
- }
- }
-
@SuppressWarnings("serial")
private static class EnigmaThrowable extends Throwable {
}
diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertLinesMatchAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertLinesMatchAssertionsTests.java
index 9bfa03d7bb81..23bd6ece74b6 100644
--- a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertLinesMatchAssertionsTests.java
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertLinesMatchAssertionsTests.java
@@ -12,7 +12,6 @@
import static org.junit.jupiter.api.AssertLinesMatch.isFastForwardLine;
import static org.junit.jupiter.api.AssertLinesMatch.parseFastForwardLimit;
-import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -20,6 +19,8 @@
import static org.junit.jupiter.api.Assertions.assertLinesMatch;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor;
+import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor;
import java.util.ArrayList;
import java.util.Arrays;
@@ -31,7 +32,6 @@
import java.util.stream.Stream;
import org.jspecify.annotations.Nullable;
-import org.junit.platform.commons.PreconditionViolationException;
import org.opentest4j.AssertionFailedError;
/**
@@ -97,9 +97,9 @@ void assertLinesMatchUsingFastForwardMarkerWithLimit3() {
@Test
@SuppressWarnings({ "unchecked", "rawtypes", "DataFlowIssue" })
void assertLinesMatchWithNullFails() {
- assertThrows(PreconditionViolationException.class, () -> assertLinesMatch(null, (List) null));
- assertThrows(PreconditionViolationException.class, () -> assertLinesMatch(null, Collections.emptyList()));
- assertThrows(PreconditionViolationException.class, () -> assertLinesMatch(Collections.emptyList(), null));
+ assertPreconditionViolationFor(() -> assertLinesMatch(null, (List) null));
+ assertPreconditionViolationFor(() -> assertLinesMatch(null, Collections.emptyList()));
+ assertPreconditionViolationFor(() -> assertLinesMatch(Collections.emptyList(), null));
}
@Test
@@ -107,10 +107,8 @@ void assertLinesMatchWithNullElementsFails() {
var list = List.of("1", "2", "3");
var withNullElement = Arrays.asList("1", null, "3"); // List.of() doesn't permit null values.
assertDoesNotThrow(() -> assertLinesMatch(withNullElement, withNullElement));
- var e1 = assertThrows(PreconditionViolationException.class, () -> assertLinesMatch(withNullElement, list));
- assertEquals("expected line must not be null", e1.getMessage());
- var e2 = assertThrows(PreconditionViolationException.class, () -> assertLinesMatch(list, withNullElement));
- assertEquals("actual line must not be null", e2.getMessage());
+ assertPreconditionViolationNotNullFor("expected line", () -> assertLinesMatch(withNullElement, list));
+ assertPreconditionViolationNotNullFor("actual line", () -> assertLinesMatch(list, withNullElement));
}
private void assertError(AssertionFailedError error, String expectedMessage, List expectedLines,
@@ -215,12 +213,12 @@ void assertLinesMatchParseFastForwardLimit() {
() -> assertEquals(9, parseFastForwardLimit(">> 9 >>")),
() -> assertEquals(9, parseFastForwardLimit(" >> 9 >> ")),
() -> assertEquals(9, parseFastForwardLimit(" >> 9 >> ")));
- Throwable error = assertThrows(PreconditionViolationException.class, () -> parseFastForwardLimit(">>0>>"));
- assertMessageEquals(error, "fast-forward(0) limit must be greater than zero");
- error = assertThrows(PreconditionViolationException.class, () -> parseFastForwardLimit(">>-1>>"));
- assertMessageEquals(error, "fast-forward(-1) limit must be greater than zero");
- error = assertThrows(PreconditionViolationException.class, () -> parseFastForwardLimit(">>-2147483648>>"));
- assertMessageEquals(error, "fast-forward(-2147483648) limit must be greater than zero");
+ assertPreconditionViolationFor(() -> parseFastForwardLimit(">>0>>"))//
+ .withMessage("fast-forward(0) limit must be greater than zero");
+ assertPreconditionViolationFor(() -> parseFastForwardLimit(">>-1>>"))//
+ .withMessage("fast-forward(-1) limit must be greater than zero");
+ assertPreconditionViolationFor(() -> parseFastForwardLimit(">>-2147483648>>"))//
+ .withMessage("fast-forward(-2147483648) limit must be greater than zero");
}
@Test
diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/DynamicTestTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/DynamicTestTests.java
index 094408a7da9e..d45427ed1621 100644
--- a/jupiter-tests/src/test/java/org/junit/jupiter/api/DynamicTestTests.java
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/DynamicTestTests.java
@@ -15,6 +15,7 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.DynamicContainer.dynamicContainer;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
+import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -29,7 +30,6 @@
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.function.Executable;
import org.junit.jupiter.api.function.ThrowingConsumer;
-import org.junit.platform.commons.PreconditionViolationException;
import org.junit.platform.commons.support.ReflectionSupport;
import org.opentest4j.AssertionFailedError;
@@ -50,12 +50,9 @@ void streamFromStreamPreconditions() {
};
Function