From 715cc2c743a4240351f237d7e544d4ccd367f436 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 2 May 2025 13:36:03 +0200 Subject: [PATCH 01/42] Back to snapshots for further development --- gradle.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 1921177d772d..1f57336a6a6a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,13 +1,13 @@ group = org.junit -version = 5.13.0-M3 +version = 5.13.0-SNAPSHOT jupiterGroup = org.junit.jupiter platformGroup = org.junit.platform -platformVersion = 1.13.0-M3 +platformVersion = 1.13.0-SNAPSHOT vintageGroup = org.junit.vintage -vintageVersion = 5.13.0-M3 +vintageVersion = 5.13.0-SNAPSHOT # We need more metaspace due to apparent memory leak in Asciidoctor/JRuby org.gradle.jvmargs=-Xmx1g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError From 942435ef8fae5c4bdfdfd75f0ae0123e012516fc Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 2 May 2025 13:48:44 +0200 Subject: [PATCH 02/42] Create initial 5.13.0-RC1 release notes from template --- .../docs/asciidoc/release-notes/index.adoc | 2 + .../release-notes-5.13.0-RC1.adoc | 67 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-RC1.adoc diff --git a/documentation/src/docs/asciidoc/release-notes/index.adoc b/documentation/src/docs/asciidoc/release-notes/index.adoc index af8a4feabdaf..c17df220c575 100644 --- a/documentation/src/docs/asciidoc/release-notes/index.adoc +++ b/documentation/src/docs/asciidoc/release-notes/index.adoc @@ -17,6 +17,8 @@ authors as well as build tool and IDE vendors. include::{includedir}/link-attributes.adoc[] +include::{basedir}/release-notes-5.13.0-RC1.adoc[] + include::{basedir}/release-notes-5.13.0-M3.adoc[] include::{basedir}/release-notes-5.13.0-M2.adoc[] diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-RC1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-RC1.adoc new file mode 100644 index 000000000000..50e9e4b123cf --- /dev/null +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-RC1.adoc @@ -0,0 +1,67 @@ +[[release-notes-5.13.0-RC1]] +== 5.13.0-RC1 + +*Date of Release:* May 16, 2025 + +*Scope:* ❓ + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit5-repo}+/milestone/96?closed=1+[5.13.0-RC1] milestone page in the JUnit +repository on GitHub. + + +[[release-notes-5.13.0-RC1-junit-platform]] +=== JUnit Platform + +[[release-notes-5.13.0-RC1-junit-platform-bug-fixes]] +==== Bug Fixes + +* ❓ + +[[release-notes-5.13.0-RC1-junit-platform-deprecations-and-breaking-changes]] +==== Deprecations and Breaking Changes + +* ❓ + +[[release-notes-5.13.0-RC1-junit-platform-new-features-and-improvements]] +==== New Features and Improvements + +* ❓ + + +[[release-notes-5.13.0-RC1-junit-jupiter]] +=== JUnit Jupiter + +[[release-notes-5.13.0-RC1-junit-jupiter-bug-fixes]] +==== Bug Fixes + +* ❓ + +[[release-notes-5.13.0-RC1-junit-jupiter-deprecations-and-breaking-changes]] +==== Deprecations and Breaking Changes + +* ❓ + +[[release-notes-5.13.0-RC1-junit-jupiter-new-features-and-improvements]] +==== New Features and Improvements + +* ❓ + + +[[release-notes-5.13.0-RC1-junit-vintage]] +=== JUnit Vintage + +[[release-notes-5.13.0-RC1-junit-vintage-bug-fixes]] +==== Bug Fixes + +* ❓ + +[[release-notes-5.13.0-RC1-junit-vintage-deprecations-and-breaking-changes]] +==== Deprecations and Breaking Changes + +* ❓ + +[[release-notes-5.13.0-RC1-junit-vintage-new-features-and-improvements]] +==== New Features and Improvements + +* ❓ From fce51071880cb554d447ad93b1aa4f414ab28e46 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 May 2025 11:51:31 +0000 Subject: [PATCH 03/42] Update github/codeql-action action to v3.28.17 --- .github/workflows/codeql-analysis.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 31c9a5baa610..e27db6e3b99f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -34,7 +34,7 @@ jobs: - name: Check out repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Initialize CodeQL - uses: github/codeql-action/init@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/init@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: languages: ${{ matrix.language }} tools: linked @@ -47,4 +47,4 @@ jobs: -Dscan.tag.CodeQL \ allMainClasses - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/analyze@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 56874e9f9c9d..ea885c523a17 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -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@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: sarif_file: results.sarif From 3236b94f98d24934690f57547ca77d4eccad040e Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 2 May 2025 14:41:36 +0200 Subject: [PATCH 04/42] Run Renovate also on `develop/.*` branches --- .github/renovate.json5 | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index ea024d3d17e6..d256a62d0f55 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -3,6 +3,7 @@ extends: [ 'github>junit-team/renovate-config', ], + baseBranches: ["main", "/^develop\\/.*/"], packageRules: [ { matchCurrentValue: '/^2\\./', From 6f287d04b2080aba5ca4c13ded52c0d38c832b6e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 May 2025 12:43:36 +0000 Subject: [PATCH 05/42] Update plugin spotless to v7 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 95679e033664..c7f89d6bcdc5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -103,5 +103,5 @@ kotlin = { id = "org.jetbrains.kotlin.jvm", version = "2.1.20" } nexusPublish = { id = "io.github.gradle-nexus.publish-plugin", version = "2.0.0" } plantuml = { id = "io.freefair.plantuml", version = "8.13.1" } shadow = { id = "com.gradleup.shadow", version = "8.3.6" } -spotless = { id = "com.diffplug.spotless", version = "6.25.0" } +spotless = { id = "com.diffplug.spotless", version = "7.0.3" } versions = { id = "com.github.ben-manes.versions", version = "0.52.0" } From 2958022f8a93420ab72ca974f196dd2e35262869 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 2 May 2025 17:52:56 +0200 Subject: [PATCH 06/42] Update sponsors --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7268c5826da6..71888458aae1 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ This repository is the home of _JUnit 5_. [![Support JUnit](https://img.shields.io/badge/%F0%9F%92%9A-Support%20JUnit-brightgreen.svg)](https://junit.org/sponsoring) -* **Gold Sponsors:** [JetBrains](https://jb.gg/junit-logo) +* **Gold Sponsors:** [JetBrains](https://jb.gg/junit-logo), [Netflix](https://www.netflix.com/) * **Silver Sponsors:** [Micromata](https://www.micromata.de), [Quo Card](https://quo-digital.jp) -* **Bronze Sponsors:** [Premium Minds](https://www.premium-minds.com), [Testmo](https://www.testmo.com), [codefortynine](https://codefortynine.com), [Info Support](https://www.infosupport.com), [Stiltsoft](https://stiltsoft.com), [Code Intelligence](https://www.code-intelligence.com), [Route4Me](https://route4me.com/), [Testiny](https://www.testiny.io/) +* **Bronze Sponsors:** [Premium Minds](https://www.premium-minds.com), [codefortynine](https://codefortynine.com), [Info Support](https://www.infosupport.com), [Code Intelligence](https://www.code-intelligence.com), [Route4Me](https://route4me.com/), [Testiny](https://www.testiny.io/) ## Latest Releases From c27907daeef1d7bbaee456e7330de849e0a497a5 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 3 May 2025 14:00:22 +0200 Subject: [PATCH 07/42] Resolve Spotless deprecation warning --- .../src/main/kotlin/junitbuild.spotless-conventions.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.spotless-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.spotless-conventions.gradle.kts index 12a1d64470b0..70ebbb416212 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.spotless-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.spotless-conventions.gradle.kts @@ -9,7 +9,7 @@ spotless { format("misc") { target("*.gradle.kts", "gradle/plugins/**/*.gradle.kts", "*.gitignore") targetExclude("gradle/plugins/**/build/**") - indentWithTabs() + leadingSpacesToTabs() trimTrailingWhitespace() endWithNewline() } From 3d1131bed123d1617a539360cb03b5699ece46c9 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 6 May 2025 13:17:13 +0200 Subject: [PATCH 08/42] Include all modules in API reports --- documentation/documentation.gradle.kts | 3 ++- .../java/org/junit/api/tools/ApiReportGenerator.java | 10 ++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index 799c16ae3833..51958bd0771e 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -247,7 +247,8 @@ tasks { val generateApiTables by registering(JavaExec::class) { classpath = tools.runtimeClasspath mainClass = "org.junit.api.tools.ApiReportGenerator" - jvmArgumentProviders += ClasspathSystemPropertyProvider("api.classpath", apiReportClasspath.get()) + systemProperty("api.moduleNames", modularProjects.map { it.javaModuleName }.sorted().joinToString(",")) + jvmArgumentProviders += ClasspathSystemPropertyProvider("api.modulePath", apiReportClasspath.get()) argumentProviders += CommandLineArgumentProvider { listOf( "DEPRECATED=${deprecatedApisTableFile.get().asFile.absolutePath}", diff --git a/documentation/src/tools/java/org/junit/api/tools/ApiReportGenerator.java b/documentation/src/tools/java/org/junit/api/tools/ApiReportGenerator.java index 01e6b7832988..1cf2308ed574 100644 --- a/documentation/src/tools/java/org/junit/api/tools/ApiReportGenerator.java +++ b/documentation/src/tools/java/org/junit/api/tools/ApiReportGenerator.java @@ -12,6 +12,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.stream.Collectors.toCollection; +import static java.util.stream.Collectors.toUnmodifiableSet; import java.io.BufferedOutputStream; import java.io.File; @@ -28,7 +29,6 @@ import java.util.EnumSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.stream.Stream; @@ -141,12 +141,14 @@ private static ScanResult scanClasspath() { .enableClassInfo() // .enableMethodInfo() // .enableAnnotationInfo(); // - var apiClasspath = System.getProperty("api.classpath"); - if (apiClasspath != null) { + var apiClasspath = System.getProperty("api.modulePath"); + var apiModules = System.getProperty("api.moduleNames"); + if (apiClasspath != null && apiModules != null) { var paths = Arrays.stream(apiClasspath.split(File.pathSeparator)).map(Path::of).toArray(Path[]::new); var bootLayer = ModuleLayer.boot(); + var roots = Arrays.stream(apiModules.split(",")).collect(toUnmodifiableSet()); var configuration = bootLayer.configuration().resolveAndBind(ModuleFinder.of(), ModuleFinder.of(paths), - Set.of()); + roots); var layer = bootLayer.defineModulesWithOneLoader(configuration, ClassLoader.getPlatformClassLoader()); classGraph = classGraph.overrideModuleLayers(layer); } From 2f4a45a7f7fc7caf98a4110f8a791e89d5acb673 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 8 May 2025 05:25:49 +0000 Subject: [PATCH 09/42] Update dependency com.tngtech.archunit:archunit-junit5 to v1.4.1 (#4520) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c7f89d6bcdc5..fadbe683d179 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,7 +28,7 @@ ant-junitlauncher = { module = "org.apache.ant:ant-junitlauncher", version.ref = apiguardian = { module = "org.apiguardian:apiguardian-api", version.ref = "apiguardian" } # check whether the Java condition in platform-tooling-support-tests.gradle.kts can be changed when updating -archunit = { module = "com.tngtech.archunit:archunit-junit5", version = "1.4.0" } +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" } From de157c2202575250944451d1ad46cfdfc33cfdcc Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Wed, 7 May 2025 12:29:22 +0200 Subject: [PATCH 10/42] Validate all versions in `@EnabledOnJre` and `@DisabledOnJre` Prior to this commit, versions were not validated exhaustively due to short-circuiting operations being used to evaluate the `Stream` returned from `validatedVersions` in `EnabledOnJreCondition` and `DisabledOnJreCondition`. --- .../release-notes-5.13.0-RC1.adoc | 2 +- .../api/condition/AbstractJreCondition.java | 21 +++++++++---------- .../DisabledOnJreIntegrationTests.java.jte | 2 +- .../EnabledOnJreIntegrationTests.java.jte | 2 +- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-RC1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-RC1.adoc index 50e9e4b123cf..814bbc9229e8 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-RC1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-RC1.adoc @@ -35,7 +35,7 @@ repository on GitHub. [[release-notes-5.13.0-RC1-junit-jupiter-bug-fixes]] ==== Bug Fixes -* ❓ +* Validate _all_ versions specified in `@EnabledOnJre` and `@DisabledOnJre` annotations. [[release-notes-5.13.0-RC1-junit-jupiter-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractJreCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractJreCondition.java index 43ca0b92555d..1eb301b2d894 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractJreCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractJreCondition.java @@ -10,6 +10,8 @@ package org.junit.jupiter.api.condition; +import static java.util.function.Predicate.isEqual; + import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.function.Function; @@ -42,18 +44,15 @@ protected final IntStream validatedVersions(JRE[] jres, int[] versions) { Preconditions.condition(jres.length > 0 || versions.length > 0, () -> "You must declare at least one JRE or version in @" + this.annotationName); + Preconditions.condition(Arrays.stream(jres).noneMatch(isEqual(JRE.UNDEFINED)), + () -> "JRE.UNDEFINED is not supported in @" + this.annotationName); + Arrays.stream(versions).min().ifPresent(version -> Preconditions.condition(version >= JRE.MINIMUM_VERSION, + () -> String.format("Version [%d] in @%s must be greater than or equal to %d", version, this.annotationName, + JRE.MINIMUM_VERSION))); + return IntStream.concat(// - Arrays.stream(jres).mapToInt(jre -> { - Preconditions.condition(jre != JRE.UNDEFINED, - () -> "JRE.UNDEFINED is not supported in @" + this.annotationName); - return jre.version(); - }), // - Arrays.stream(versions).map(version -> { - Preconditions.condition(version >= JRE.MINIMUM_VERSION, - () -> String.format("Version [%d] in @%s must be greater than or equal to %d", version, - this.annotationName, JRE.MINIMUM_VERSION)); - return version; - })// + Arrays.stream(jres).mapToInt(JRE::version), // + Arrays.stream(versions) // ).distinct(); } diff --git a/jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/DisabledOnJreIntegrationTests.java.jte b/jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/DisabledOnJreIntegrationTests.java.jte index c84b159c4078..509b5d47fa48 100644 --- a/jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/DisabledOnJreIntegrationTests.java.jte +++ b/jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/DisabledOnJreIntegrationTests.java.jte @@ -50,7 +50,7 @@ class DisabledOnJreIntegrationTests { @Test @Disabled("Only used in a unit test via reflection") - @DisabledOnJre(versions = 7) + @DisabledOnJre(value = JAVA_17, versions = { 21, 7 }) void version7() { } diff --git a/jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/EnabledOnJreIntegrationTests.java.jte b/jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/EnabledOnJreIntegrationTests.java.jte index c3b71df58ab4..651345ea02b3 100644 --- a/jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/EnabledOnJreIntegrationTests.java.jte +++ b/jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/EnabledOnJreIntegrationTests.java.jte @@ -49,7 +49,7 @@ class EnabledOnJreIntegrationTests { @Test @Disabled("Only used in a unit test via reflection") - @EnabledOnJre(versions = 7) + @EnabledOnJre(value = JAVA_17, versions = { 21, 7 }) void version7() { } From dc06d099d93c02af60814ff27d5602d9bf0e7b61 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Wed, 7 May 2025 12:42:58 +0200 Subject: [PATCH 11/42] Remove unreachable validation Since `minVersion` is already validate to be at least `JRE.MINIMUM_VERSION` and there's no way to `min` to a `JRE` less than `JRE.JAVA_8` (since there are no enum constants for JRE 7 and earlier), this validation never found any illegal values. --- .../junit/jupiter/api/condition/AbstractJreRangeCondition.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractJreRangeCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractJreRangeCondition.java index ea2290e5c8de..7bc7d8a0b117 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractJreRangeCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractJreRangeCondition.java @@ -70,9 +70,6 @@ protected final boolean isCurrentVersionWithinRange(JRE minJre, JRE maxJre, int // Finally, we need to validate the effective minimum and maximum values. Preconditions.condition((min != JRE.MINIMUM_VERSION || max != Integer.MAX_VALUE), () -> "You must declare a non-default value for the minimum or maximum value in @" + this.annotationName); - Preconditions.condition(min >= JRE.MINIMUM_VERSION, - () -> String.format("@%s's minimum value [%d] must greater than or equal to %d", this.annotationName, min, - JRE.MINIMUM_VERSION)); Preconditions.condition(min <= max, () -> String.format("@%s's minimum value [%d] must be less than or equal to its maximum value [%d]", this.annotationName, min, max)); From 4083551d4c54eb56e9aa769de3bc79223142b0ec Mon Sep 17 00:00:00 2001 From: Hans Zuidervaart <49289674+hanszt@users.noreply.github.com> Date: Thu, 8 May 2025 08:51:49 +0200 Subject: [PATCH 12/42] Add support for converting Kotlin `Sequence` to `Stream` (#3377) Kotlin's `Sequence` type may now be used as return type of `@TestFactory` and `@MethodSource`-referenced methods as well as fields referenced by `@FieldSource` annotations. Resolves #3376. --------- Co-authored-by: Marc Philipp --- .../release-notes-5.13.0-RC1.adoc | 3 +- .../asciidoc/user-guide/writing-tests.adoc | 24 +++++--- .../org/junit/jupiter/api/TestFactory.java | 4 +- .../predicates/IsTestFactoryMethod.java | 2 +- .../jupiter/params/provider/FieldSource.java | 12 ++-- .../jupiter/params/provider/MethodSource.java | 13 ++-- .../commons/util/CollectionUtils.java | 28 +++++++-- .../predicates/IsTestFactoryMethodTests.java | 6 +- .../junit/jupiter/api/KotlinDynamicTests.kt | 56 +++++++++++++++++ ...rizedTestKotlinSequenceIntegrationTests.kt | 52 ++++++++++++++++ .../commons/util/CollectionUtilsTests.java | 60 +++++++++++++++---- 11 files changed, 221 insertions(+), 39 deletions(-) create mode 100644 jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinDynamicTests.kt create mode 100644 jupiter-tests/src/test/kotlin/org/junit/jupiter/params/ParameterizedTestKotlinSequenceIntegrationTests.kt diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-RC1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-RC1.adoc index 814bbc9229e8..5be59b89bdbe 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-RC1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-RC1.adoc @@ -45,7 +45,8 @@ repository on GitHub. [[release-notes-5.13.0-RC1-junit-jupiter-new-features-and-improvements]] ==== New Features and Improvements -* ❓ +* Add support for Kotlin `Sequence` to `@MethodSource`, `@FieldSource`, and + `@TestFactory`. [[release-notes-5.13.0-RC1-junit-vintage]] diff --git a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc index e84ed85c59be..b9c86dbb8a89 100644 --- a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc +++ b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc @@ -1949,10 +1949,11 @@ of the annotated `@ParameterizedClass` or `@ParameterizedTest`. Generally speaki translates to a `Stream` of `Arguments` (i.e., `Stream`); however, the actual concrete return type can take on many forms. In this context, a "stream" is anything that JUnit can reliably convert into a `Stream`, such as `Stream`, `DoubleStream`, -`LongStream`, `IntStream`, `Collection`, `Iterator`, `Iterable`, an array of objects, or -an array of primitives. The "arguments" within the stream can be supplied as an instance -of `Arguments`, an array of objects (e.g., `Object[]`), or a single value if the -parameterized class or test method accepts a single argument. +`LongStream`, `IntStream`, `Collection`, `Iterator`, `Iterable`, an array of objects or +primitives, or any type that provides an `iterator(): Iterator` method (such as, for +example, a `kotlin.sequences.Sequence`). The "arguments" within the stream can be supplied +as an instance of `Arguments`, an array of objects (e.g., `Object[]`), or a single value +if the parameterized class or test method accepts a single argument. If the return type is `Stream` or one of the primitive streams, JUnit will properly close it by calling `BaseStream.close()`, @@ -2038,10 +2039,11 @@ In this context, a "stream" is anything that JUnit can reliably convert to a `St however, the actual concrete field type can take on many forms. Generally speaking this translates to a `Collection`, an `Iterable`, a `Supplier` of a stream (`Stream`, `DoubleStream`, `LongStream`, or `IntStream`), a `Supplier` of an `Iterator`, an array of -objects, or an array of primitives. Each set of "arguments" within the "stream" can be -supplied as an instance of `Arguments`, an array of objects (for example, `Object[]`, -`String[]`, etc.), or a single value if the parameterized class or test method accepts a -single argument. +objects or primitives, or any type that provides an `iterator(): Iterator` method (such +as, for example, a `kotlin.sequences.Sequence`). Each set of "arguments" within the +"stream" can be supplied as an instance of `Arguments`, an array of objects (for example, +`Object[]`, `String[]`, etc.), or a single value if the parameterized class ortest method accepts +a single argument. [WARNING] ==== @@ -2870,7 +2872,11 @@ generated at runtime by a factory method that is annotated with `@TestFactory`. In contrast to `@Test` methods, a `@TestFactory` method is not itself a test case but rather a factory for test cases. Thus, a dynamic test is the product of a factory. Technically speaking, a `@TestFactory` method must return a single `DynamicNode` or a -`Stream`, `Collection`, `Iterable`, `Iterator`, or array of `DynamicNode` instances. +_stream_ of `DynamicNode` instances or any of its subclasses. In this context, a "stream" +is anything that JUnit can reliably convert into a `Stream`, such as `Stream`, +`Collection`, `Iterator`, `Iterable`, an array of objects, or any type that provides an +`iterator(): Iterator` method (such as, for example, a `kotlin.sequences.Sequence`). + Instantiable subclasses of `DynamicNode` are `DynamicContainer` and `DynamicTest`. `DynamicContainer` instances are composed of a _display name_ and a list of dynamic child nodes, enabling the creation of arbitrarily nested hierarchies of dynamic nodes. diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestFactory.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestFactory.java index 42835ebd6888..b502f382c321 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestFactory.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestFactory.java @@ -30,7 +30,9 @@ * *

{@code @TestFactory} methods must not be {@code private} or {@code static} * and must return a {@code Stream}, {@code Collection}, {@code Iterable}, - * {@code Iterator}, or array of {@link DynamicNode} instances. Supported + * {@code Iterator}, array of {@link DynamicNode} instances, or any type that + * provides an {@link java.util.Iterator Iterator}-returning {@code iterator()} + * method (such as, for example, a {@code kotlin.sequences.Sequence}). Supported * subclasses of {@code DynamicNode} include {@link DynamicContainer} and * {@link DynamicTest}. Dynamic tests will be executed lazily, * enabling dynamic and even non-deterministic generation of test cases. diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java index aaf90d6db727..fcc37c85bf2f 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java @@ -40,7 +40,7 @@ public class IsTestFactoryMethod extends IsTestableMethod { private static final String EXPECTED_RETURN_TYPE_MESSAGE = String.format( - "must return a single %1$s or a Stream, Collection, Iterable, Iterator, or array of %1$s", + "must return a single %1$s or a Stream, Collection, Iterable, Iterator, Iterator provider, or array of %1$s", DynamicNode.class.getName()); public IsTestFactoryMethod(DiscoveryIssueReporter issueReporter) { diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSource.java index 4ca4717fd4d7..32666855bb9c 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSource.java @@ -44,11 +44,13 @@ * {@link java.util.stream.DoubleStream DoubleStream}, * {@link java.util.stream.LongStream LongStream}, or * {@link java.util.stream.IntStream IntStream}), a {@code Supplier} of an - * {@link java.util.Iterator Iterator}, an array of objects, or an array of - * primitives. Each set of "arguments" within the "stream" can be supplied as an - * instance of {@link Arguments}, an array of objects (for example, {@code Object[]}, - * {@code String[]}, etc.), or a single value if the parameterized - * class or test accepts a single argument. + * {@link java.util.Iterator Iterator}, an array of objects or primitives, or + * any type that provides an {@link java.util.Iterator Iterator}-returning + * {@code iterator()} method (such as, for example, a + * {@code kotlin.sequences.Sequence}). Each set of "arguments" within the + * "stream" can be supplied as an instance of {@link Arguments}, an array of + * objects (for example, {@code Object[]}, {@code String[]}, etc.), or a single + * value if the parameterized class or test accepts a single argument. * *

In contrast to the supported return types for {@link MethodSource @MethodSource} * factory methods, the value of a {@code @FieldSource} field cannot be an instance of diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java index aec745a5188b..d6b4d3599fa3 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java @@ -43,12 +43,13 @@ * {@link java.util.stream.LongStream LongStream}, * {@link java.util.stream.IntStream IntStream}, * {@link java.util.Collection Collection}, - * {@link java.util.Iterator Iterator}, - * {@link Iterable}, an array of objects, or an array of primitives. Each set of - * "arguments" within the "stream" can be supplied as an instance of - * {@link Arguments}, an array of objects (e.g., {@code Object[]}, - * {@code String[]}, etc.), or a single value if the parameterized test - * method accepts a single argument. + * {@link java.util.Iterator Iterator}, an array of objects or primitives, or + * any type that provides an {@link java.util.Iterator Iterator}-returning + * {@code iterator()} method (such as, for example, a + * {@code kotlin.sequences.Sequence}). Each set of "arguments" within the + * "stream" can be supplied as an instance of {@link Arguments}, an array of + * objects (e.g., {@code Object[]}, {@code String[]}, etc.), or a single + * value if the parameterized test method accepts a single argument. * *

If the return type is {@code Stream} or * one of the primitive streams, JUnit will properly close it by calling diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java index e12d0421f286..6f250e532e76 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java @@ -16,8 +16,10 @@ import static java.util.stream.Collectors.toList; import static java.util.stream.StreamSupport.stream; import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.commons.support.ReflectionSupport.invokeMethod; import java.lang.reflect.Array; +import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -36,6 +38,7 @@ import org.apiguardian.api.API; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.support.ReflectionSupport; /** * Collection of utilities for working with {@link Collection Collections}. @@ -122,7 +125,7 @@ public static Set toSet(T[] values) { * returned, so if more control over the returned list is required, * consider creating a new {@code Collector} implementation like the * following: - * + *

*

 	 * public static <T> Collector<T, ?, List<T>> toUnmodifiableList(Supplier<List<T>> listSupplier) {
 	 *     return Collectors.collectingAndThen(Collectors.toCollection(listSupplier), Collections::unmodifiableList);
@@ -161,7 +164,8 @@ public static boolean isConvertibleToStream(Class type) {
 				|| Iterable.class.isAssignableFrom(type)//
 				|| Iterator.class.isAssignableFrom(type)//
 				|| Object[].class.isAssignableFrom(type)//
-				|| (type.isArray() && type.getComponentType().isPrimitive()));
+				|| (type.isArray() && type.getComponentType().isPrimitive())//
+				|| findIteratorMethod(type).isPresent());
 	}
 
 	/**
@@ -177,6 +181,9 @@ public static boolean isConvertibleToStream(Class type) {
 	 * 
  • {@link Iterator}
  • *
  • {@link Object} array
  • *
  • primitive array
  • + *
  • any type that provides an + * {@link java.util.Iterator Iterator}-returning {@code iterator()} method + * (such as, for example, a {@code kotlin.sequences.Sequence})
  • * * * @param object the object to convert into a stream; never {@code null} @@ -223,8 +230,21 @@ public static Stream toStream(Object object) { if (object.getClass().isArray() && object.getClass().getComponentType().isPrimitive()) { return IntStream.range(0, Array.getLength(object)).mapToObj(i -> Array.get(object, i)); } - throw new PreconditionViolationException( - "Cannot convert instance of " + object.getClass().getName() + " into a Stream: " + object); + return tryConvertToStreamByReflection(object); + } + + private static Stream tryConvertToStreamByReflection(Object object) { + return findIteratorMethod(object.getClass()) // + .map(method -> (Iterator) invokeMethod(method, object)) // + .map(iterator -> spliteratorUnknownSize(iterator, ORDERED)) // + .map(spliterator -> stream(spliterator, false)) // + .orElseThrow(() -> new PreconditionViolationException(String.format( + "Cannot convert instance of %s into a Stream: %s", object.getClass().getName(), object))); + } + + private static Optional findIteratorMethod(Class type) { + return ReflectionSupport.findMethod(type, "iterator") // + .filter(method -> method.getReturnType() == Iterator.class); } /** diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethodTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethodTests.java index f7b7534e38be..22c295d74864 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethodTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethodTests.java @@ -66,7 +66,8 @@ void invalidFactoryMethods(String methodName) { var issue = getOnlyElement(discoveryIssues); assertThat(issue.severity()).isEqualTo(DiscoveryIssue.Severity.WARNING); assertThat(issue.message()).isEqualTo( - "@TestFactory method '%s' must return a single org.junit.jupiter.api.DynamicNode or a Stream, Collection, Iterable, Iterator, or array of org.junit.jupiter.api.DynamicNode. " + "@TestFactory method '%s' must return a single org.junit.jupiter.api.DynamicNode or a " + + "Stream, Collection, Iterable, Iterator, Iterator provider, or array of org.junit.jupiter.api.DynamicNode. " + "It will not be executed.", method.toGenericString()); assertThat(issue.source()).contains(MethodSource.from(method)); @@ -83,7 +84,8 @@ void suspiciousFactoryMethods(String methodName) { assertThat(issue.severity()).isEqualTo(DiscoveryIssue.Severity.INFO); assertThat(issue.message()).isEqualTo( "The declared return type of @TestFactory method '%s' does not support static validation. " - + "It must return a single org.junit.jupiter.api.DynamicNode or a Stream, Collection, Iterable, Iterator, or array of org.junit.jupiter.api.DynamicNode.", + + "It must return a single org.junit.jupiter.api.DynamicNode or a " + + "Stream, Collection, Iterable, Iterator, Iterator provider, or array of org.junit.jupiter.api.DynamicNode.", method.toGenericString()); assertThat(issue.source()).contains(MethodSource.from(method)); } diff --git a/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinDynamicTests.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinDynamicTests.kt new file mode 100644 index 000000000000..ba0dec561903 --- /dev/null +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinDynamicTests.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +package org.junit.jupiter.api + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.DynamicTest.dynamicTest +import java.math.BigDecimal +import java.math.BigDecimal.ONE +import java.math.MathContext +import java.math.BigInteger as BigInt +import java.math.RoundingMode as Rounding + +/** + * Unit tests for JUnit Jupiter [TestFactory] use in kotlin classes. + * + * @since 5.12 + */ +class KotlinDynamicTests { + @Nested + inner class SequenceReturningTestFactoryTests { + @TestFactory + fun `Dynamic tests returned as Kotlin sequence`() = + generateSequence(0) { it + 2 } + .map { dynamicTest("$it should be even") { assertEquals(0, it % 2) } } + .take(10) + + @TestFactory + fun `Consecutive fibonacci nr ratios, should converge to golden ratio as n increases`(): Sequence { + val scale = 5 + val goldenRatio = + (ONE + 5.toBigDecimal().sqrt(MathContext(scale + 10, Rounding.HALF_UP))) + .divide(2.toBigDecimal(), scale, Rounding.HALF_UP) + + fun shouldApproximateGoldenRatio( + cur: BigDecimal, + next: BigDecimal + ) = next.divide(cur, scale, Rounding.HALF_UP).let { + dynamicTest("$cur / $next = $it should approximate the golden ratio in $scale decimals") { + assertEquals(goldenRatio, it) + } + } + return generateSequence(BigInt.ONE to BigInt.ONE) { (cur, next) -> next to cur + next } + .map { (cur) -> cur.toBigDecimal() } + .zipWithNext(::shouldApproximateGoldenRatio) + .drop(14) + .take(10) + } + } +} diff --git a/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/ParameterizedTestKotlinSequenceIntegrationTests.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/ParameterizedTestKotlinSequenceIntegrationTests.kt new file mode 100644 index 000000000000..800407845e11 --- /dev/null +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/ParameterizedTestKotlinSequenceIntegrationTests.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +package org.junit.jupiter.params + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.params.provider.Arguments.arguments +import org.junit.jupiter.params.provider.FieldSource +import org.junit.jupiter.params.provider.MethodSource +import java.time.Month + +/** + * Tests for Kotlin compatibility of ParameterizedTest + */ +object ParameterizedTestKotlinSequenceIntegrationTests { + @ParameterizedTest + @MethodSource("dataProvidedByKotlinSequenceMethod") + fun `a method source can be supplied by a Sequence-returning method`( + value: Int, + month: Month + ) { + assertEquals(value, month.value) + } + + @JvmStatic + private fun dataProvidedByKotlinSequenceMethod() = dataProvidedByKotlinSequenceField + + @JvmStatic + val dataProvidedByKotlinSequenceField = + sequenceOf( + arguments(1, Month.JANUARY), + arguments(3, Month.MARCH), + arguments(8, Month.AUGUST), + arguments(5, Month.MAY), + arguments(12, Month.DECEMBER) + ) + + @ParameterizedTest + @FieldSource("dataProvidedByKotlinSequenceField") + fun `a field source can be supplied by a Sequence-typed field`( + value: Int, + month: Month + ) { + assertEquals(value, month.value) + } +} diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java index 9e6f01daccbb..749f62161073 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java @@ -25,6 +25,7 @@ import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.Spliterator; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.DoubleStream; import java.util.stream.IntStream; @@ -139,6 +140,7 @@ class StreamConversion { Collection.class, // Iterable.class, // Iterator.class, // + IteratorProvider.class, // Object[].class, // String[].class, // int[].class, // @@ -160,10 +162,11 @@ static Stream objectsConvertibleToStreams() { Stream.of("cat", "dog"), // DoubleStream.of(42.3), // IntStream.of(99), // - LongStream.of(100000000), // + LongStream.of(100_000_000), // Set.of(1, 2, 3), // Arguments.of((Object) new Object[] { 9, 8, 7 }), // - new int[] { 5, 10, 15 }// + new int[] { 5, 10, 15 }, // + new IteratorProvider(1, 2, 3, 4, 5)// ); } @@ -174,6 +177,8 @@ static Stream objectsConvertibleToStreams() { Object.class, // Integer.class, // String.class, // + UnusableIteratorProvider.class, // + Spliterator.class, // int.class, // boolean.class // }) @@ -242,16 +247,10 @@ void toStreamWithLongStream() { } @Test - @SuppressWarnings({ "unchecked", "serial" }) + @SuppressWarnings({ "unchecked" }) void toStreamWithCollection() { var collectionStreamClosed = new AtomicBoolean(false); - Collection input = new ArrayList<>() { - - { - add("foo"); - add("bar"); - } - + var input = new ArrayList<>(List.of("foo", "bar")) { @Override public Stream stream() { return super.stream().onClose(() -> collectionStreamClosed.set(true)); @@ -287,6 +286,25 @@ void toStreamWithIterator() { assertThat(result).containsExactly("foo", "bar"); } + @Test + @SuppressWarnings("unchecked") + void toStreamWithIteratorProvider() { + var input = new IteratorProvider("foo", "bar"); + + var result = (Stream) CollectionUtils.toStream(input); + + assertThat(result).containsExactly("foo", "bar"); + } + + @Test + void throwWhenIteratorNamedMethodDoesNotReturnAnIterator() { + var o = new UnusableIteratorProvider("Test"); + var e = assertThrows(PreconditionViolationException.class, () -> CollectionUtils.toStream(o)); + + assertEquals("Cannot convert instance of %s into a Stream: %s".formatted( + UnusableIteratorProvider.class.getName(), o), e.getMessage()); + } + @Test @SuppressWarnings("unchecked") void toStreamWithArray() { @@ -355,4 +373,26 @@ public Object convert(Object source, ParameterContext context) throws ArgumentCo } } } + + /** + * An interface that has a method with name 'iterator', returning a java.util/Iterator as a return type + */ + private record IteratorProvider(Object... elements) { + + @SuppressWarnings("unused") + Iterator iterator() { + return Arrays.stream(elements).iterator(); + } + } + + /** + * An interface that has a method with name 'iterator', but does not return java.util/Iterator as a return type + */ + private record UnusableIteratorProvider(Object... elements) { + + @SuppressWarnings("unused") + Object iterator() { + return Arrays.stream(elements).iterator(); + } + } } From a135f908d4ce52ebf8999add7add25f8cf19af79 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 8 May 2025 16:02:40 +0200 Subject: [PATCH 13/42] Add/fix `@API(status = DEPRECATED)` annotations --- .../java/org/junit/jupiter/api/extension/ExtensionContext.java | 3 ++- .../params/provider/AnnotationBasedArgumentsProvider.java | 2 ++ .../java/org/junit/platform/commons/support/SearchOption.java | 3 ++- .../suite/commons/SuiteLauncherDiscoveryRequestBuilder.java | 2 ++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java index ae6daf29e2c3..1f1eeda903f4 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java @@ -11,6 +11,7 @@ package org.junit.jupiter.api.extension; import static java.util.Collections.unmodifiableList; +import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; @@ -506,7 +507,7 @@ interface Store { * @deprecated Please extend {@code AutoCloseable} directly. */ @Deprecated - @API(status = STABLE, since = "5.1") + @API(status = DEPRECATED, since = "5.13") interface CloseableResource { /** diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProvider.java index ee9a2747a6f1..3abbbe760d15 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProvider.java @@ -10,6 +10,7 @@ package org.junit.jupiter.params.provider; +import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.lang.annotation.Annotation; @@ -69,6 +70,7 @@ public Stream provideArguments(ParameterDeclarations parame * instead. */ @Deprecated + @API(status = DEPRECATED, since = "5.13") protected Stream provideArguments(ExtensionContext context, A annotation) { throw new JUnitException(String.format( "AnnotationBasedArgumentsProvider does not override the provideArguments(ParameterDeclarations, ExtensionContext, Annotation) method. " diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/SearchOption.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/SearchOption.java index 4152d14e8220..ab3d804c5b13 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/SearchOption.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/SearchOption.java @@ -43,7 +43,8 @@ public enum SearchOption { * @deprecated because it is preferable to inspect the runtime enclosing * types of a class rather than where they are declared. */ - @Deprecated @API(status = DEPRECATED, since = "1.12") + @Deprecated // + @API(status = DEPRECATED, since = "1.12") INCLUDE_ENCLOSING_CLASSES } diff --git a/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java b/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java index 79e8ef209728..44985338fd1b 100644 --- a/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java +++ b/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java @@ -11,6 +11,7 @@ package org.junit.platform.suite.commons; import static java.util.stream.Collectors.toList; +import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations; @@ -286,6 +287,7 @@ public SuiteLauncherDiscoveryRequestBuilder listener(LauncherDiscoveryListener l * {@link #applySelectorsAndFiltersFromSuite} */ @Deprecated + @API(status = DEPRECATED, since = "1.11") public SuiteLauncherDiscoveryRequestBuilder suite(Class suiteClass) { Preconditions.notNull(suiteClass, "Suite class must not be null"); applyConfigurationParametersFromSuite(suiteClass); From cdc7f9a4e184843273f929e98fb8a335445f2dfc Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 10 May 2025 13:25:55 +0200 Subject: [PATCH 14/42] Remove empty `

    ` Javadoc tag --- .../java/org/junit/platform/commons/util/CollectionUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java index 6f250e532e76..c50a4b178518 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java @@ -125,7 +125,7 @@ public static Set toSet(T[] values) { * returned, so if more control over the returned list is required, * consider creating a new {@code Collector} implementation like the * following: - *

    + * *

     	 * public static <T> Collector<T, ?, List<T>> toUnmodifiableList(Supplier<List<T>> listSupplier) {
     	 *     return Collectors.collectingAndThen(Collectors.toCollection(listSupplier), Collections::unmodifiableList);
    
    From b7228bef894b17880a18b975a5c9a8bab1a8efd8 Mon Sep 17 00:00:00 2001
    From: Marc Philipp 
    Date: Sat, 10 May 2025 13:26:13 +0200
    Subject: [PATCH 15/42] Treat Javadoc warnings as errors
    
    ---
     documentation/documentation.gradle.kts | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts
    index 51958bd0771e..3d6b3b646a47 100644
    --- a/documentation/documentation.gradle.kts
    +++ b/documentation/documentation.gradle.kts
    @@ -425,6 +425,7 @@ tasks {
     			this as StandardJavadocDocletOptions
     			splitIndex(true)
     			addBooleanOption("Xdoclint:all,-missing", true)
    +			addBooleanOption("Werror", true)
     			addBooleanOption("html5", true)
     			addMultilineStringsOption("tag").value = listOf(
     					"apiNote:a:API Note:",
    
    From b0f78a2a9980a789e140ec384bd00f8750ff9963 Mon Sep 17 00:00:00 2001
    From: Marc Philipp 
    Date: Sat, 10 May 2025 13:57:06 +0200
    Subject: [PATCH 16/42] Update version in release notes intro
    
    ---
     documentation/src/docs/asciidoc/release-notes/index.adoc | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/documentation/src/docs/asciidoc/release-notes/index.adoc b/documentation/src/docs/asciidoc/release-notes/index.adoc
    index c17df220c575..33d93ad199b4 100644
    --- a/documentation/src/docs/asciidoc/release-notes/index.adoc
    +++ b/documentation/src/docs/asciidoc/release-notes/index.adoc
    @@ -9,7 +9,7 @@ Stefan Bechtold; Sam Brannen; Johannes Link; Matthias Merdes; Marc Philipp; Juli
     :last-update-label!:
     //
     
    -This document contains the _change log_ for all JUnit 5 releases since 5.11 GA.
    +This document contains the _change log_ for all JUnit 5 releases since 5.12 GA.
     
     Please refer to the <<../user-guide/index.adoc#user-guide,User Guide>> for comprehensive
     reference documentation for programmers writing tests, extension authors, and engine
    
    From dab2016c831f52e4a5769c96915da1caf8b89cb0 Mon Sep 17 00:00:00 2001
    From: Marc Philipp 
    Date: Sat, 10 May 2025 15:00:05 +0200
    Subject: [PATCH 17/42] Enable all compiler warnings for Java test sources
    
    ---
     ...junitbuild.java-library-conventions.gradle.kts |  3 +--
     .../jupiter/api/AssertEqualsAssertionsTests.java  |  1 +
     .../api/AssertNotEqualsAssertionsTests.java       |  1 +
     .../FallbackStringToObjectConverterTests.java     | 15 +++++++++++++++
     4 files changed, 18 insertions(+), 2 deletions(-)
    
    diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts
    index b14973e05474..8e4cb4bddf37 100644
    --- a/gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts
    +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts
    @@ -274,8 +274,7 @@ tasks.compileJava {
     tasks.compileTestJava {
     	// See: https://docs.oracle.com/en/java/javase/12/tools/javac.html
     	options.compilerArgs.addAll(listOf(
    -			"-Xlint", // Enables all recommended warnings.
    -			"-Xlint:-overrides", // Disables "method overrides" warnings.
    +			"-Xlint:all", // Enables all recommended warnings.
     			"-Werror", // Terminates compilation when warnings occur.
     			"-parameters" // Generates metadata for reflection on method parameters.
     	))
    diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertEqualsAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertEqualsAssertionsTests.java
    index df84f44540a2..ec30168e7605 100644
    --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertEqualsAssertionsTests.java
    +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertEqualsAssertionsTests.java
    @@ -752,6 +752,7 @@ void chars() {
     
     	// -------------------------------------------------------------------------
     
    +	@SuppressWarnings("overrides")
     	private static class EqualsThrowsException {
     
     		@Override
    diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNotEqualsAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNotEqualsAssertionsTests.java
    index 450653ecd327..842e7f463a8f 100644
    --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNotEqualsAssertionsTests.java
    +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNotEqualsAssertionsTests.java
    @@ -757,6 +757,7 @@ void chars() {
     
     	// -------------------------------------------------------------------------
     
    +	@SuppressWarnings("overrides")
     	private static class EqualsThrowsExceptionClass {
     
     		@Override
    diff --git a/platform-tests/src/test/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverterTests.java b/platform-tests/src/test/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverterTests.java
    index e679f3d4d531..cc2e8b38469d 100644
    --- a/platform-tests/src/test/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverterTests.java
    +++ b/platform-tests/src/test/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverterTests.java
    @@ -167,6 +167,11 @@ public boolean equals(Object obj) {
     			return Objects.equals(this.title, that.title);
     		}
     
    +		@Override
    +		public int hashCode() {
    +			return Objects.hash(title);
    +		}
    +
     	}
     
     	static class Journal {
    @@ -188,6 +193,11 @@ public boolean equals(Object obj) {
     			return Objects.equals(this.title, that.title);
     		}
     
    +		@Override
    +		public int hashCode() {
    +			return Objects.hash(title);
    +		}
    +
     	}
     
     	static class Newspaper {
    @@ -217,6 +227,11 @@ public boolean equals(Object obj) {
     			return Objects.equals(this.title, that.title);
     		}
     
    +		@Override
    +		public int hashCode() {
    +			return Objects.hash(title);
    +		}
    +
     	}
     
     	static class Magazine {
    
    From df7c8f631aaf534cd91869c85471f9f2b0128109 Mon Sep 17 00:00:00 2001
    From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
    Date: Tue, 13 May 2025 08:24:34 +0000
    Subject: [PATCH 18/42] Update plugin kotlin to v2.1.21
    
    ---
     gradle/libs.versions.toml                                       | 2 +-
     .../projects/gradle-kotlin-extensions/build.gradle.kts          | 2 +-
     2 files changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
    index fadbe683d179..bda66ff217a2 100644
    --- a/gradle/libs.versions.toml
    +++ b/gradle/libs.versions.toml
    @@ -99,7 +99,7 @@ foojayResolver = { id = "org.gradle.toolchains.foojay-resolver", version = "0.10
     gitPublish = { id = "org.ajoberstar.git-publish", version = "5.1.1" }
     jmh = { id = "me.champeau.jmh", version = "0.7.3" }
     # check if workaround in gradle.properties can be removed when updating
    -kotlin = { id = "org.jetbrains.kotlin.jvm", version = "2.1.20" }
    +kotlin = { id = "org.jetbrains.kotlin.jvm", version = "2.1.21" }
     nexusPublish = { id = "io.github.gradle-nexus.publish-plugin", version = "2.0.0" }
     plantuml = { id = "io.freefair.plantuml", version = "8.13.1" }
     shadow = { id = "com.gradleup.shadow", version = "8.3.6" }
    diff --git a/platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts b/platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts
    index 0fef24865e1f..b8a6f83ed106 100644
    --- a/platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts
    +++ b/platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts
    @@ -1,7 +1,7 @@
     import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
     
     plugins {
    -	kotlin("jvm") version "2.1.20"
    +	kotlin("jvm") version "2.1.21"
     }
     
     repositories {
    
    From ef5a5072ceee7e1b834abd0d1c362e4fdc958dfd Mon Sep 17 00:00:00 2001
    From: Marc Philipp 
    Date: Thu, 17 Apr 2025 10:07:28 +0200
    Subject: [PATCH 19/42] Set up build to publish via Maven Central Portal
    
    Snapshots are now being published to
    https://central.sonatype.com/repository/maven-snapshots and the
    JReleaser Gradle plugin is configured for publishing releases.
    
    Issue: #4483
    ---
     README.md                                     |  2 +-
     build.gradle.kts                              |  9 +--
     .../src/docs/asciidoc/link-attributes.adoc    |  2 +-
     gradle/libs.versions.toml                     |  2 +-
     gradle/plugins/common/build.gradle.kts        |  1 +
     ...tbuild.maven-central-publishing.gradle.kts | 70 +++++++++++++++++++
     .../junitbuild.temp-maven-repo.gradle.kts     |  7 +-
     .../tooling/support/tests/MavenRepoProxy.java |  2 +-
     8 files changed, 81 insertions(+), 14 deletions(-)
     create mode 100644 gradle/plugins/common/src/main/kotlin/junitbuild.maven-central-publishing.gradle.kts
    
    diff --git a/README.md b/README.md
    index 71888458aae1..d2378544982e 100644
    --- a/README.md
    +++ b/README.md
    @@ -94,7 +94,7 @@ Consult the [Dependency Metadata] section of the [User Guide] for a list of all
     of the JUnit Platform, JUnit Jupiter, and JUnit Vintage.
     
     See also  for releases and
    - for snapshots.
    + for snapshots.
     
     
     [Codecov]: https://codecov.io/gh/junit-team/junit5
    diff --git a/build.gradle.kts b/build.gradle.kts
    index cd79a120700b..a59ed31c9619 100644
    --- a/build.gradle.kts
    +++ b/build.gradle.kts
    @@ -1,10 +1,10 @@
     plugins {
    -	alias(libs.plugins.nexusPublish)
     	id("junitbuild.base-conventions")
     	id("junitbuild.build-metadata")
     	id("junitbuild.checkstyle-nohttp")
     	id("junitbuild.dependency-update-check")
     	id("junitbuild.jacoco-aggregation-conventions")
    +	id("junitbuild.maven-central-publishing")
     	id("junitbuild.temp-maven-repo")
     }
     
    @@ -55,10 +55,3 @@ dependencies {
     	jacocoAggregation(projects.jupiterTests)
     	jacocoAggregation(projects.platformTests)
     }
    -
    -nexusPublishing {
    -	packageGroup = "org.junit"
    -	repositories {
    -		sonatype()
    -	}
    -}
    diff --git a/documentation/src/docs/asciidoc/link-attributes.adoc b/documentation/src/docs/asciidoc/link-attributes.adoc
    index fbfc2021e938..2342763e5680 100644
    --- a/documentation/src/docs/asciidoc/link-attributes.adoc
    +++ b/documentation/src/docs/asciidoc/link-attributes.adoc
    @@ -3,7 +3,7 @@ ifdef::backend-pdf[]
     :javadoc-root:                               https://junit.org/junit5/docs/{docs-version}/api
     endif::[]
     // Snapshot Repository
    -:snapshot-repo:                              https://oss.sonatype.org/content/repositories/snapshots
    +:snapshot-repo:                              https://central.sonatype.com/repository/maven-snapshots
     // Base Links
     :junit-team:                                 https://github.com/junit-team
     :junit5-repo:                                {junit-team}/junit5
    diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
    index bda66ff217a2..d8c82070a471 100644
    --- a/gradle/libs.versions.toml
    +++ b/gradle/libs.versions.toml
    @@ -98,9 +98,9 @@ develocity = { id = "com.gradle.develocity", version = "4.0.1" }
     foojayResolver = { id = "org.gradle.toolchains.foojay-resolver", version = "0.10.0" }
     gitPublish = { id = "org.ajoberstar.git-publish", version = "5.1.1" }
     jmh = { id = "me.champeau.jmh", version = "0.7.3" }
    +jreleaser = { id = "org.jreleaser", version = "1.17.0" }
     # check if workaround in gradle.properties can be removed when updating
     kotlin = { id = "org.jetbrains.kotlin.jvm", version = "2.1.21" }
    -nexusPublish = { id = "io.github.gradle-nexus.publish-plugin", version = "2.0.0" }
     plantuml = { id = "io.freefair.plantuml", version = "8.13.1" }
     shadow = { id = "com.gradleup.shadow", version = "8.3.6" }
     spotless = { id = "com.diffplug.spotless", version = "7.0.3" }
    diff --git a/gradle/plugins/common/build.gradle.kts b/gradle/plugins/common/build.gradle.kts
    index 48c05c7f0a80..30291987ffd8 100644
    --- a/gradle/plugins/common/build.gradle.kts
    +++ b/gradle/plugins/common/build.gradle.kts
    @@ -10,6 +10,7 @@ dependencies {
     	implementation(libs.plugins.develocity.markerCoordinates)
     	implementation(libs.plugins.foojayResolver.markerCoordinates)
     	implementation(libs.plugins.jmh.markerCoordinates)
    +	implementation(libs.plugins.jreleaser.markerCoordinates)
     	implementation(libs.plugins.shadow.markerCoordinates)
     	implementation(libs.plugins.spotless.markerCoordinates)
     	implementation(libs.plugins.versions.markerCoordinates)
    diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.maven-central-publishing.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.maven-central-publishing.gradle.kts
    new file mode 100644
    index 000000000000..cb13045847d1
    --- /dev/null
    +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.maven-central-publishing.gradle.kts
    @@ -0,0 +1,70 @@
    +
    +import org.jreleaser.model.Active.RELEASE
    +import org.jreleaser.model.api.deploy.maven.MavenCentralMavenDeployer.Stage
    +import java.util.Properties
    +
    +plugins {
    +	id("org.jreleaser")
    +	id("junitbuild.temp-maven-repo")
    +}
    +
    +val tempRepoDir: File by extra
    +
    +tasks.jreleaserDeploy {
    +	dependsOn("publishAllSubprojectsToTempRepository")
    +	outputs.upToDateWhen { false }
    +	doLast {
    +		val outputProperties = Properties()
    +		layout.buildDirectory.file("jreleaser/output.properties").get().asFile.inputStream().use { input ->
    +			outputProperties.load(input)
    +		}
    +		val deploymentId = outputProperties.getProperty("deploymentId")
    +		if (deploymentId != null) {
    +			println("Deployment ID: $deploymentId")
    +			println("Staging Repo URL: https://central.sonatype.com/api/v1/publisher/deployment/$deploymentId/download")
    +		}
    +	}
    +}
    +
    +val mavenCentralUsername = providers.gradleProperty("mavenCentralUsername")
    +val mavenCentralPassword = providers.gradleProperty("mavenCentralPassword")
    +
    +jreleaser {
    +	deploy {
    +		maven {
    +			mavenCentral {
    +				register("artifacts") {
    +					active = RELEASE
    +					url = "https://central.sonatype.com/api/v1/publisher"
    +					username = mavenCentralUsername
    +					password = mavenCentralPassword
    +					stagingRepository(tempRepoDir.absolutePath)
    +					applyMavenCentralRules = true
    +					sign = false
    +					checksums = false
    +					namespace = "org.junit"
    +					stage = providers.gradleProperty("jreleaser.mavencentral.stage")
    +						.map(Stage::of)
    +						.orElse(Stage.UPLOAD)
    +				}
    +			}
    +		}
    +	}
    +}
    +
    +subprojects {
    +	pluginManager.withPlugin("maven-publish") {
    +		configure {
    +			repositories {
    +				maven {
    +					name = "mavenCentralSnapshots"
    +					url = uri("https://central.sonatype.com/repository/maven-snapshots")
    +					credentials {
    +						username = mavenCentralUsername.orNull
    +						password = mavenCentralPassword.orNull
    +					}
    +				}
    +			}
    +		}
    +	}
    +}
    diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.temp-maven-repo.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.temp-maven-repo.gradle.kts
    index 3f48f5d656d7..d2c25e9096c8 100644
    --- a/gradle/plugins/common/src/main/kotlin/junitbuild.temp-maven-repo.gradle.kts
    +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.temp-maven-repo.gradle.kts
    @@ -13,7 +13,10 @@ val clearTempRepoDir by tasks.registering {
     	}
     }
     
    -val verifyArtifactsInStagingRepositoryAreReproducible by tasks.registering(VerifyBinaryArtifactsAreIdentical::class) {
    +val publishAllSubprojectsToTempRepository by tasks.registering
    +
    +tasks.register("verifyArtifactsInStagingRepositoryAreReproducible") {
    +	dependsOn(publishAllSubprojectsToTempRepository)
     	localRepoDir.set(tempRepoDir)
     }
     
    @@ -32,7 +35,7 @@ subprojects {
     		publishingTasks.configureEach {
     			dependsOn(clearTempRepoDir)
     		}
    -		verifyArtifactsInStagingRepositoryAreReproducible {
    +		publishAllSubprojectsToTempRepository {
     			dependsOn(publishingTasks)
     		}
     	}
    diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenRepoProxy.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenRepoProxy.java
    index 96a657d750c8..2525a5c8c588 100644
    --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenRepoProxy.java
    +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenRepoProxy.java
    @@ -32,7 +32,7 @@ public MavenRepoProxy() throws IOException {
     
     	@SuppressWarnings("unused")
     	public MavenRepoProxy(int port) throws IOException {
    -		this("https://oss.sonatype.org/content/repositories/snapshots", port);
    +		this("https://central.sonatype.com/repository/maven-snapshots", port);
     	}
     
     	private MavenRepoProxy(String proxiedUrl, int port) throws IOException {
    
    From 8c33d7bc1df0afa1ac3d7b05850c68c82b787c3b Mon Sep 17 00:00:00 2001
    From: Marc Philipp 
    Date: Thu, 17 Apr 2025 11:44:24 +0200
    Subject: [PATCH 20/42] Move DSL extensions and publishing plugins to separate
     projects
    
    ---
     build.gradle.kts                                      |  2 ++
     documentation/documentation.gradle.kts                |  3 +++
     gradle/base/code-generator-model/build.gradle.kts     |  6 ------
     gradle/base/dsl-extensions/build.gradle.kts           |  3 +++
     .../junitbuild/extensions/DependencyExtensions.kt     |  8 ++++++++
     .../junitbuild/extensions}/ProjectExtensions.kt       |  2 ++
     .../kotlin/junitbuild/extensions/StringExtensions.kt} |  0
     .../kotlin/junitbuild/extensions}/TaskExtensions.kt   |  2 ++
     .../junitbuild/extensions}/VersionExtensions.kt       |  2 ++
     .../extensions/junitbuild.dsl-extensions.gradle.kts   |  1 +
     gradle/base/gradle.properties                         |  1 +
     gradle/base/settings.gradle.kts                       |  7 +++++++
     gradle/plugins/code-generator/build.gradle.kts        |  1 +
     .../main/kotlin/junitbuild.code-generator.gradle.kts  |  1 +
     gradle/plugins/common/build.gradle.kts                |  7 +++----
     .../junitbuild.checkstyle-conventions.gradle.kts      |  2 ++
     .../kotlin/junitbuild.checkstyle-nohttp.gradle.kts    |  3 +++
     .../kotlin/junitbuild.jacoco-conventions.gradle.kts   |  2 ++
     .../junitbuild.java-library-conventions.gradle.kts    |  2 ++
     .../main/kotlin/junitbuild.jmh-conventions.gradle.kts |  3 +++
     .../kotlin/junitbuild.junit4-compatibility.gradle.kts |  2 ++
     .../junitbuild.publishing-conventions.gradle.kts      |  2 ++
     .../kotlin/junitbuild.spotless-conventions.gradle.kts |  2 ++
     .../kotlin/junitbuild.testing-conventions.gradle.kts  |  4 +++-
     .../main/kotlin/junitbuild/exec/RunConsoleLauncher.kt |  2 +-
     .../junitbuild/java/PatchModuleArgumentProvider.kt    |  2 +-
     gradle/plugins/publishing/build.gradle.kts            | 10 ++++++++++
     .../junitbuild.maven-central-publishing.gradle.kts    |  1 -
     .../main/kotlin/junitbuild.temp-maven-repo.gradle.kts |  0
     .../release/VerifyBinaryArtifactsAreIdentical.kt      |  0
     gradle/plugins/settings.gradle.kts                    | 11 +++++++++--
     junit-jupiter-params/junit-jupiter-params.gradle.kts  |  2 ++
     .../junit-platform-console-standalone.gradle.kts      |  1 +
     .../junit-platform-console.gradle.kts                 |  1 +
     .../junit-platform-reporting.gradle.kts               |  2 ++
     platform-tests/platform-tests.gradle.kts              |  2 +-
     .../platform-tooling-support-tests.gradle.kts         |  2 +-
     37 files changed, 86 insertions(+), 18 deletions(-)
     create mode 100644 gradle/base/dsl-extensions/build.gradle.kts
     create mode 100644 gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/DependencyExtensions.kt
     rename gradle/{plugins/common/src/main/kotlin => base/dsl-extensions/src/main/kotlin/junitbuild/extensions}/ProjectExtensions.kt (96%)
     rename gradle/{plugins/common/src/main/kotlin/junitbuild/extensions/Extensions.kt => base/dsl-extensions/src/main/kotlin/junitbuild/extensions/StringExtensions.kt} (100%)
     rename gradle/{plugins/common/src/main/kotlin => base/dsl-extensions/src/main/kotlin/junitbuild/extensions}/TaskExtensions.kt (85%)
     rename gradle/{plugins/common/src/main/kotlin => base/dsl-extensions/src/main/kotlin/junitbuild/extensions}/VersionExtensions.kt (67%)
     create mode 100644 gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/junitbuild.dsl-extensions.gradle.kts
     create mode 100644 gradle/base/gradle.properties
     create mode 100644 gradle/plugins/publishing/build.gradle.kts
     rename gradle/plugins/{common => publishing}/src/main/kotlin/junitbuild.maven-central-publishing.gradle.kts (99%)
     rename gradle/plugins/{common => publishing}/src/main/kotlin/junitbuild.temp-maven-repo.gradle.kts (100%)
     rename gradle/plugins/{common => publishing}/src/main/kotlin/junitbuild/release/VerifyBinaryArtifactsAreIdentical.kt (100%)
    
    diff --git a/build.gradle.kts b/build.gradle.kts
    index a59ed31c9619..5f40357a56cf 100644
    --- a/build.gradle.kts
    +++ b/build.gradle.kts
    @@ -1,3 +1,5 @@
    +import junitbuild.extensions.dependencyProject
    +
     plugins {
     	id("junitbuild.base-conventions")
     	id("junitbuild.build-metadata")
    diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts
    index 3d6b3b646a47..bceab93de5c0 100644
    --- a/documentation/documentation.gradle.kts
    +++ b/documentation/documentation.gradle.kts
    @@ -2,6 +2,9 @@ import junitbuild.exec.CaptureJavaExecOutput
     import junitbuild.exec.ClasspathSystemPropertyProvider
     import junitbuild.exec.GenerateStandaloneConsoleLauncherShadowedArtifactsFile
     import junitbuild.exec.RunConsoleLauncher
    +import junitbuild.extensions.dependencyProject
    +import junitbuild.extensions.isSnapshot
    +import junitbuild.extensions.javaModuleName
     import junitbuild.javadoc.ModuleSpecificJavadocFileOption
     import org.asciidoctor.gradle.base.AsciidoctorAttributeProvider
     import org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask
    diff --git a/gradle/base/code-generator-model/build.gradle.kts b/gradle/base/code-generator-model/build.gradle.kts
    index aa3ba93c438a..bc0172f0f072 100644
    --- a/gradle/base/code-generator-model/build.gradle.kts
    +++ b/gradle/base/code-generator-model/build.gradle.kts
    @@ -1,9 +1,3 @@
     plugins {
         `kotlin-dsl`
     }
    -
    -group = "junitbuild.base"
    -
    -repositories {
    -    gradlePluginPortal()
    -}
    diff --git a/gradle/base/dsl-extensions/build.gradle.kts b/gradle/base/dsl-extensions/build.gradle.kts
    new file mode 100644
    index 000000000000..bc0172f0f072
    --- /dev/null
    +++ b/gradle/base/dsl-extensions/build.gradle.kts
    @@ -0,0 +1,3 @@
    +plugins {
    +    `kotlin-dsl`
    +}
    diff --git a/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/DependencyExtensions.kt b/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/DependencyExtensions.kt
    new file mode 100644
    index 000000000000..ad04bf5f33ab
    --- /dev/null
    +++ b/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/DependencyExtensions.kt
    @@ -0,0 +1,8 @@
    +package junitbuild.extensions
    +
    +import org.gradle.api.provider.Provider
    +import org.gradle.plugin.use.PluginDependency
    +
    +// see https://docs.gradle.org/current/userguide/plugins.html#sec:plugin_markers
    +val Provider.markerCoordinates: Provider
    +    get() = map { "${it.pluginId}:${it.pluginId}.gradle.plugin:${it.version}" }
    diff --git a/gradle/plugins/common/src/main/kotlin/ProjectExtensions.kt b/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/ProjectExtensions.kt
    similarity index 96%
    rename from gradle/plugins/common/src/main/kotlin/ProjectExtensions.kt
    rename to gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/ProjectExtensions.kt
    index 33c7a591b546..8dc61fa656de 100644
    --- a/gradle/plugins/common/src/main/kotlin/ProjectExtensions.kt
    +++ b/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/ProjectExtensions.kt
    @@ -1,3 +1,5 @@
    +package junitbuild.extensions
    +
     import org.gradle.api.Project
     import org.gradle.api.artifacts.ProjectDependency
     import org.gradle.api.artifacts.VersionCatalog
    diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/extensions/Extensions.kt b/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/StringExtensions.kt
    similarity index 100%
    rename from gradle/plugins/common/src/main/kotlin/junitbuild/extensions/Extensions.kt
    rename to gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/StringExtensions.kt
    diff --git a/gradle/plugins/common/src/main/kotlin/TaskExtensions.kt b/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/TaskExtensions.kt
    similarity index 85%
    rename from gradle/plugins/common/src/main/kotlin/TaskExtensions.kt
    rename to gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/TaskExtensions.kt
    index 7ad4e7ab46cb..9cd596dfbfda 100644
    --- a/gradle/plugins/common/src/main/kotlin/TaskExtensions.kt
    +++ b/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/TaskExtensions.kt
    @@ -1,3 +1,5 @@
    +package junitbuild.extensions
    +
     import org.gradle.api.Task
     import org.gradle.internal.os.OperatingSystem
     
    diff --git a/gradle/plugins/common/src/main/kotlin/VersionExtensions.kt b/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/VersionExtensions.kt
    similarity index 67%
    rename from gradle/plugins/common/src/main/kotlin/VersionExtensions.kt
    rename to gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/VersionExtensions.kt
    index 8d106218b896..0991e5d1c3fd 100644
    --- a/gradle/plugins/common/src/main/kotlin/VersionExtensions.kt
    +++ b/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/VersionExtensions.kt
    @@ -1 +1,3 @@
    +package junitbuild.extensions
    +
     fun Any.isSnapshot(): Boolean = toString().contains("SNAPSHOT")
    diff --git a/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/junitbuild.dsl-extensions.gradle.kts b/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/junitbuild.dsl-extensions.gradle.kts
    new file mode 100644
    index 000000000000..11b39c463b79
    --- /dev/null
    +++ b/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/junitbuild.dsl-extensions.gradle.kts
    @@ -0,0 +1 @@
    +// Just a dummy plugin to get the extensions on the classpath of downstream builds
    diff --git a/gradle/base/gradle.properties b/gradle/base/gradle.properties
    new file mode 100644
    index 000000000000..de911ccbf7bf
    --- /dev/null
    +++ b/gradle/base/gradle.properties
    @@ -0,0 +1 @@
    +group = junitbuild.base
    diff --git a/gradle/base/settings.gradle.kts b/gradle/base/settings.gradle.kts
    index 66f6697452eb..67d0cdacc360 100644
    --- a/gradle/base/settings.gradle.kts
    +++ b/gradle/base/settings.gradle.kts
    @@ -1,3 +1,10 @@
     rootProject.name = "base"
     
    +dependencyResolutionManagement {
    +    repositories {
    +        gradlePluginPortal()
    +    }
    +}
    +
     include("code-generator-model")
    +include("dsl-extensions")
    diff --git a/gradle/plugins/code-generator/build.gradle.kts b/gradle/plugins/code-generator/build.gradle.kts
    index e9f2ef657e47..fda16a028335 100644
    --- a/gradle/plugins/code-generator/build.gradle.kts
    +++ b/gradle/plugins/code-generator/build.gradle.kts
    @@ -4,6 +4,7 @@ plugins {
     
     dependencies {
     	implementation("junitbuild.base:code-generator-model")
    +	implementation("junitbuild.base:dsl-extensions")
     	implementation(projects.common)
     	implementation(libs.jackson.dataformat.yaml)
     	implementation(libs.jackson.module.kotlin)
    diff --git a/gradle/plugins/code-generator/src/main/kotlin/junitbuild.code-generator.gradle.kts b/gradle/plugins/code-generator/src/main/kotlin/junitbuild.code-generator.gradle.kts
    index 59d9fa36ff43..1846eb6ae728 100644
    --- a/gradle/plugins/code-generator/src/main/kotlin/junitbuild.code-generator.gradle.kts
    +++ b/gradle/plugins/code-generator/src/main/kotlin/junitbuild.code-generator.gradle.kts
    @@ -1,3 +1,4 @@
    +import junitbuild.extensions.dependencyFromLibs
     import junitbuild.generator.GenerateJreRelatedSourceCode
     
     plugins {
    diff --git a/gradle/plugins/common/build.gradle.kts b/gradle/plugins/common/build.gradle.kts
    index 30291987ffd8..e15665d0d6e0 100644
    --- a/gradle/plugins/common/build.gradle.kts
    +++ b/gradle/plugins/common/build.gradle.kts
    @@ -1,8 +1,11 @@
    +import junitbuild.extensions.markerCoordinates
    +
     plugins {
     	`kotlin-dsl`
     }
     
     dependencies {
    +	implementation("junitbuild.base:dsl-extensions")
     	implementation(projects.buildParameters)
     	implementation(libs.plugins.kotlin.markerCoordinates)
     	implementation(libs.plugins.bnd.markerCoordinates)
    @@ -15,7 +18,3 @@ dependencies {
     	implementation(libs.plugins.spotless.markerCoordinates)
     	implementation(libs.plugins.versions.markerCoordinates)
     }
    -
    -// see https://docs.gradle.org/current/userguide/plugins.html#sec:plugin_markers
    -val Provider.markerCoordinates: Provider
    -	get() = map { "${it.pluginId}:${it.pluginId}.gradle.plugin:${it.version}" }
    diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-conventions.gradle.kts
    index ae6f47ef34d7..f207138111fe 100644
    --- a/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-conventions.gradle.kts
    +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-conventions.gradle.kts
    @@ -1,3 +1,5 @@
    +import junitbuild.extensions.requiredVersionFromLibs
    +
     plugins {
     	base
     	checkstyle
    diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-nohttp.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-nohttp.gradle.kts
    index 641002f94ad2..0bcb0ce67641 100644
    --- a/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-nohttp.gradle.kts
    +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-nohttp.gradle.kts
    @@ -1,3 +1,6 @@
    +import junitbuild.extensions.dependencyFromLibs
    +import junitbuild.extensions.requiredVersionFromLibs
    +
     plugins {
     	id("junitbuild.checkstyle-conventions")
     }
    diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.jacoco-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.jacoco-conventions.gradle.kts
    index ef29df71f8c0..eb2965a09a5a 100644
    --- a/gradle/plugins/common/src/main/kotlin/junitbuild.jacoco-conventions.gradle.kts
    +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.jacoco-conventions.gradle.kts
    @@ -1,3 +1,5 @@
    +import junitbuild.extensions.requiredVersionFromLibs
    +
     plugins {
     	jacoco
     	id("junitbuild.build-parameters")
    diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts
    index 8e4cb4bddf37..becfe893a62a 100644
    --- a/gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts
    +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts
    @@ -1,4 +1,6 @@
     import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
    +import junitbuild.extensions.javaModuleName
    +import junitbuild.extensions.isSnapshot
     import junitbuild.java.ModuleCompileOptions
     import junitbuild.java.ModulePathArgumentProvider
     import junitbuild.java.PatchModuleArgumentProvider
    diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.jmh-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.jmh-conventions.gradle.kts
    index a1d529652650..377a49fb9085 100644
    --- a/gradle/plugins/common/src/main/kotlin/junitbuild.jmh-conventions.gradle.kts
    +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.jmh-conventions.gradle.kts
    @@ -1,3 +1,6 @@
    +import junitbuild.extensions.requiredVersionFromLibs
    +import junitbuild.extensions.dependencyFromLibs
    +
     plugins {
     	id("me.champeau.jmh")
     }
    diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.junit4-compatibility.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.junit4-compatibility.gradle.kts
    index 64b1074d345d..1366ce3159dc 100644
    --- a/gradle/plugins/common/src/main/kotlin/junitbuild.junit4-compatibility.gradle.kts
    +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.junit4-compatibility.gradle.kts
    @@ -1,3 +1,5 @@
    +import junitbuild.extensions.dependencyFromLibs
    +
     plugins {
     	`java-library`
     }
    diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.publishing-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.publishing-conventions.gradle.kts
    index 90146181e380..08664ee9a0d0 100644
    --- a/gradle/plugins/common/src/main/kotlin/junitbuild.publishing-conventions.gradle.kts
    +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.publishing-conventions.gradle.kts
    @@ -1,3 +1,5 @@
    +import junitbuild.extensions.isSnapshot
    +
     plugins {
     	`maven-publish`
     	signing
    diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.spotless-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.spotless-conventions.gradle.kts
    index 70ebbb416212..ee94535fab22 100644
    --- a/gradle/plugins/common/src/main/kotlin/junitbuild.spotless-conventions.gradle.kts
    +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.spotless-conventions.gradle.kts
    @@ -1,3 +1,5 @@
    +import junitbuild.extensions.requiredVersionFromLibs
    +
     plugins {
     	id("com.diffplug.spotless")
     }
    diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.testing-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.testing-conventions.gradle.kts
    index b5f63a5906a5..b79be1dbbeed 100644
    --- a/gradle/plugins/common/src/main/kotlin/junitbuild.testing-conventions.gradle.kts
    +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.testing-conventions.gradle.kts
    @@ -1,6 +1,8 @@
    -
     import com.gradle.develocity.agent.gradle.internal.test.PredictiveTestSelectionConfigurationInternal
     import com.gradle.develocity.agent.gradle.test.PredictiveTestSelectionMode
    +import junitbuild.extensions.trackOperationSystemAsInput
    +import junitbuild.extensions.dependencyFromLibs
    +import junitbuild.extensions.bundleFromLibs
     import org.gradle.api.tasks.PathSensitivity.RELATIVE
     import org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
     import org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED
    diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/exec/RunConsoleLauncher.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/exec/RunConsoleLauncher.kt
    index 896e76090c6f..4ab463b11eba 100644
    --- a/gradle/plugins/common/src/main/kotlin/junitbuild/exec/RunConsoleLauncher.kt
    +++ b/gradle/plugins/common/src/main/kotlin/junitbuild/exec/RunConsoleLauncher.kt
    @@ -20,7 +20,7 @@ import org.gradle.kotlin.dsl.get
     import org.gradle.kotlin.dsl.the
     import org.gradle.process.CommandLineArgumentProvider
     import org.gradle.process.ExecOperations
    -import trackOperationSystemAsInput
    +import junitbuild.extensions.trackOperationSystemAsInput
     import java.io.ByteArrayOutputStream
     import javax.inject.Inject
     
    diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/java/PatchModuleArgumentProvider.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/java/PatchModuleArgumentProvider.kt
    index 8149094e6128..6b21c5a1bb7b 100644
    --- a/gradle/plugins/common/src/main/kotlin/junitbuild/java/PatchModuleArgumentProvider.kt
    +++ b/gradle/plugins/common/src/main/kotlin/junitbuild/java/PatchModuleArgumentProvider.kt
    @@ -1,6 +1,6 @@
     package junitbuild.java
     
    -import javaModuleName
    +import junitbuild.extensions.javaModuleName
     import org.gradle.api.Named
     import org.gradle.api.Project
     import org.gradle.api.file.ConfigurableFileCollection
    diff --git a/gradle/plugins/publishing/build.gradle.kts b/gradle/plugins/publishing/build.gradle.kts
    new file mode 100644
    index 000000000000..e010a00708d4
    --- /dev/null
    +++ b/gradle/plugins/publishing/build.gradle.kts
    @@ -0,0 +1,10 @@
    +import junitbuild.extensions.markerCoordinates
    +
    +plugins {
    +	`kotlin-dsl`
    +}
    +
    +dependencies {
    +	implementation("junitbuild.base:dsl-extensions")
    +	implementation(libs.plugins.jreleaser.markerCoordinates)
    +}
    diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.maven-central-publishing.gradle.kts b/gradle/plugins/publishing/src/main/kotlin/junitbuild.maven-central-publishing.gradle.kts
    similarity index 99%
    rename from gradle/plugins/common/src/main/kotlin/junitbuild.maven-central-publishing.gradle.kts
    rename to gradle/plugins/publishing/src/main/kotlin/junitbuild.maven-central-publishing.gradle.kts
    index cb13045847d1..bdbd857ea8ae 100644
    --- a/gradle/plugins/common/src/main/kotlin/junitbuild.maven-central-publishing.gradle.kts
    +++ b/gradle/plugins/publishing/src/main/kotlin/junitbuild.maven-central-publishing.gradle.kts
    @@ -1,4 +1,3 @@
    -
     import org.jreleaser.model.Active.RELEASE
     import org.jreleaser.model.api.deploy.maven.MavenCentralMavenDeployer.Stage
     import java.util.Properties
    diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.temp-maven-repo.gradle.kts b/gradle/plugins/publishing/src/main/kotlin/junitbuild.temp-maven-repo.gradle.kts
    similarity index 100%
    rename from gradle/plugins/common/src/main/kotlin/junitbuild.temp-maven-repo.gradle.kts
    rename to gradle/plugins/publishing/src/main/kotlin/junitbuild.temp-maven-repo.gradle.kts
    diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/release/VerifyBinaryArtifactsAreIdentical.kt b/gradle/plugins/publishing/src/main/kotlin/junitbuild/release/VerifyBinaryArtifactsAreIdentical.kt
    similarity index 100%
    rename from gradle/plugins/common/src/main/kotlin/junitbuild/release/VerifyBinaryArtifactsAreIdentical.kt
    rename to gradle/plugins/publishing/src/main/kotlin/junitbuild/release/VerifyBinaryArtifactsAreIdentical.kt
    diff --git a/gradle/plugins/settings.gradle.kts b/gradle/plugins/settings.gradle.kts
    index 41935db0aa36..bb41fd436361 100644
    --- a/gradle/plugins/settings.gradle.kts
    +++ b/gradle/plugins/settings.gradle.kts
    @@ -1,3 +1,11 @@
    +pluginManagement {
    +	includeBuild("../base")
    +}
    +
    +plugins {
    +	id("junitbuild.dsl-extensions") apply false
    +}
    +
     dependencyResolutionManagement {
     	versionCatalogs {
     		create("libs") {
    @@ -11,10 +19,9 @@ dependencyResolutionManagement {
     
     rootProject.name = "plugins"
     
    -includeBuild("../base")
    -
     include("build-parameters")
     include("common")
     include("code-generator")
    +include("publishing")
     
     enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
    diff --git a/junit-jupiter-params/junit-jupiter-params.gradle.kts b/junit-jupiter-params/junit-jupiter-params.gradle.kts
    index 80ade03f4b27..7b3d65577455 100644
    --- a/junit-jupiter-params/junit-jupiter-params.gradle.kts
    +++ b/junit-jupiter-params/junit-jupiter-params.gradle.kts
    @@ -1,3 +1,5 @@
    +import junitbuild.extensions.javaModuleName
    +
     plugins {
     	id("junitbuild.kotlin-library-conventions")
     	id("junitbuild.shadow-conventions")
    diff --git a/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts b/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts
    index 99ed213d9837..503f1a4a744c 100644
    --- a/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts
    +++ b/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts
    @@ -1,3 +1,4 @@
    +import junitbuild.extensions.dependencyProject
     import junitbuild.java.WriteArtifactsFile
     
     plugins {
    diff --git a/junit-platform-console/junit-platform-console.gradle.kts b/junit-platform-console/junit-platform-console.gradle.kts
    index cd256959b629..7a032e5ae482 100644
    --- a/junit-platform-console/junit-platform-console.gradle.kts
    +++ b/junit-platform-console/junit-platform-console.gradle.kts
    @@ -1,3 +1,4 @@
    +import junitbuild.extensions.javaModuleName
     import junitbuild.java.UpdateJarAction
     
     plugins {
    diff --git a/junit-platform-reporting/junit-platform-reporting.gradle.kts b/junit-platform-reporting/junit-platform-reporting.gradle.kts
    index f1dede61555d..5f11aab2adf9 100644
    --- a/junit-platform-reporting/junit-platform-reporting.gradle.kts
    +++ b/junit-platform-reporting/junit-platform-reporting.gradle.kts
    @@ -1,3 +1,5 @@
    +import junitbuild.extensions.javaModuleName
    +
     plugins {
     	id("junitbuild.java-library-conventions")
     	id("junitbuild.shadow-conventions")
    diff --git a/platform-tests/platform-tests.gradle.kts b/platform-tests/platform-tests.gradle.kts
    index 3e63fe5fe14f..38e1b9ed1e4e 100644
    --- a/platform-tests/platform-tests.gradle.kts
    +++ b/platform-tests/platform-tests.gradle.kts
    @@ -1,5 +1,5 @@
    -
     import junitbuild.extensions.capitalized
    +import junitbuild.extensions.dependencyProject
     import org.gradle.api.tasks.PathSensitivity.RELATIVE
     import org.gradle.internal.os.OperatingSystem
     
    diff --git a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts
    index 94bc99c60916..2f2d9cd00a68 100644
    --- a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts
    +++ b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts
    @@ -1,6 +1,6 @@
    -
     import com.gradle.develocity.agent.gradle.internal.test.TestDistributionConfigurationInternal
     import junitbuild.extensions.capitalized
    +import junitbuild.extensions.dependencyProject
     import org.gradle.api.tasks.PathSensitivity.RELATIVE
     import org.gradle.kotlin.dsl.support.listFilesOrdered
     import java.time.Duration
    
    From 52497df072f371d1e1acbee626490e30c992c636 Mon Sep 17 00:00:00 2001
    From: Marc Philipp 
    Date: Thu, 17 Apr 2025 11:45:00 +0200
    Subject: [PATCH 21/42] Resolve snakeyaml classpath issue
    
    ---
     documentation/documentation.gradle.kts | 2 ++
     1 file changed, 2 insertions(+)
    
    diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts
    index bceab93de5c0..a5f806e99b60 100644
    --- a/documentation/documentation.gradle.kts
    +++ b/documentation/documentation.gradle.kts
    @@ -9,6 +9,7 @@ import junitbuild.javadoc.ModuleSpecificJavadocFileOption
     import org.asciidoctor.gradle.base.AsciidoctorAttributeProvider
     import org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask
     import org.gradle.api.tasks.PathSensitivity.RELATIVE
    +import org.ysb33r.grolifant.api.core.jvm.ExecutionMode.JAVA_EXEC
     
     plugins {
     	alias(libs.plugins.asciidoctorConvert)
    @@ -381,6 +382,7 @@ tasks {
     	}
     
     	asciidoctorPdf {
    +		setExecutionMode(JAVA_EXEC) // Avoid classpath conflicts with other Gradle plugins (e.g. JReleaser)
     		sources {
     			include("user-guide/index.adoc")
     		}
    
    From c8c9436b315d649edd548c24ddc841965ee3cb5a Mon Sep 17 00:00:00 2001
    From: Marc Philipp 
    Date: Thu, 17 Apr 2025 10:34:45 +0200
    Subject: [PATCH 22/42] Adjust release build for Maven Central Portal
    
    ---
     .../maven-central-user-token/action.yml       | 17 +++++++
     .github/workflows/release.yml                 | 48 ++++++++++++++-----
     RELEASING.md                                  |  4 +-
     .../VerifyBinaryArtifactsAreIdentical.kt      | 13 ++++-
     4 files changed, 66 insertions(+), 16 deletions(-)
     create mode 100644 .github/actions/maven-central-user-token/action.yml
    
    diff --git a/.github/actions/maven-central-user-token/action.yml b/.github/actions/maven-central-user-token/action.yml
    new file mode 100644
    index 000000000000..37266d5e86a0
    --- /dev/null
    +++ b/.github/actions/maven-central-user-token/action.yml
    @@ -0,0 +1,17 @@
    +name: Prepare Maven Central user token
    +description: Compute the Maven Central user token from username and password
    +inputs:
    +  username:
    +    required: true
    +    description: Maven Central username
    +  password:
    +    required: true
    +    description: Maven Central password
    +runs:
    +  using: "composite"
    +  steps:
    +    - shell: bash
    +      run: |
    +        USER_TOKEN=$(printf "${{ inputs.username }}:${{ inputs.password }}" | base64)
    +        echo "::add-mask::$USER_TOKEN"
    +        echo "MAVEN_CENTRAL_USER_TOKEN=$USER_TOKEN" >> $GITHUB_ENV
    diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
    index 5b363033f4b7..91cc95a45f01 100644
    --- a/.github/workflows/release.yml
    +++ b/.github/workflows/release.yml
    @@ -6,15 +6,15 @@ on:
           releaseVersion:
             description: Version to be released (e.g. "5.12.0-M1")
             required: true
    -      stagingRepoId:
    -        description: ID of the Nexus staging repository (e.g. "orgjunit-1159")
    +      deploymentId:
    +        description: ID of the Maven Central Publish Portal deployment
             required: true
     
     permissions: read-all
     
     env:
       DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
    -  STAGING_REPO_URL: https://oss.sonatype.org/service/local/repositories/${{ github.event.inputs.stagingRepoId }}/content
    +  STAGING_REPO_URL: https://central.sonatype.com/api/v1/publisher/deployment/${{ github.event.inputs.deploymentId }}/download
       RELEASE_TAG: r${{ github.event.inputs.releaseVersion }}
     
     jobs:
    @@ -31,10 +31,16 @@ jobs:
             with:
               fetch-depth: 1
               ref: "refs/tags/${{ env.RELEASE_TAG }}"
    +      - name: Prepare Maven Central user token
    +        uses: ./.github/actions/maven-central-user-token
    +        with:
    +          username: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
    +          password: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
           - name: Download reference JAR from staging repository
             id: referenceJar
             run: |
               curl --silent --fail --location --output /tmp/reference.jar \
    +            --header "Authorization: Bearer $MAVEN_CENTRAL_USER_TOKEN" \
                 "${{ env.STAGING_REPO_URL }}/org/junit/jupiter/junit-jupiter-api/${{ github.event.inputs.releaseVersion }}/junit-jupiter-api-${{ github.event.inputs.releaseVersion }}.jar"
               sudo apt-get update && sudo apt-get install --yes jc
               unzip -c /tmp/reference.jar META-INF/MANIFEST.MF | jc --jar-manifest | jq '.[0]' > /tmp/manifest.json
    @@ -64,12 +70,19 @@ jobs:
         name: Verify consumability
         runs-on: ubuntu-latest
         steps:
    +      - name: Check out repository
    +        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
    +        with:
    +          fetch-depth: 1
    +          ref: "refs/tags/${{ env.RELEASE_TAG }}"
    +          path: junit5
           - name: Check out samples repository
             uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
             with:
               repository: ${{ github.repository_owner }}/junit5-samples
               token: ${{ secrets.GH_TOKEN }}
               fetch-depth: 1
    +          path: junit5-samples
           - name: Set up JDK
             uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
             with:
    @@ -78,10 +91,20 @@ jobs:
           - uses: sbt/setup-sbt@26ab4b0fa1c47fa62fc1f6e51823a658fb6c760c # v1.1.7
           - name: Update JUnit dependencies in samples
             run: java src/Updater.java ${{ github.event.inputs.releaseVersion }}
    +        working-directory: junit5-samples
    +      - name: Prepare Maven Central user token
    +        uses: junit5/.github/actions/maven-central-user-token
    +        with:
    +          username: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
    +          password: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
           - name: Inject staging repository URL
             run: java src/StagingRepoInjector.java ${{ env.STAGING_REPO_URL }}
    +        working-directory: junit5-samples
           - name: Build samples
    -        run: java src/Builder.java
    +        run: java src/Builder.java --exclude=junit5-jupiter-starter-bazel,junit5-jupiter-starter-sbt
    +        working-directory: junit5-samples
    +        env:
    +          MAVEN_ARGS: --settings src/central-staging-maven-settings.xml --activate-profiles central-staging
     
       close_github_milestone:
         name: Close GitHub milestone
    @@ -116,8 +139,8 @@ jobs:
                 console.log(requestBody);
                 await github.rest.issues.updateMilestone(requestBody);
     
    -  release_staging_repo:
    -    name: Release staging repo
    +  publish_deployment:
    +    name: Publish to Maven Central
         needs: [ verify_reproducibility, verify_consumability, close_github_milestone ]
         runs-on: ubuntu-latest
         steps:
    @@ -129,17 +152,18 @@ jobs:
           - name: Release staging repository
             uses: ./.github/actions/run-gradle
             env:
    -          ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.SONATYPE_USERNAME }}
    -          ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.SONATYPE_PASSWORD }}
    +          ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
    +          ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
             with:
               encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
               arguments: |
    -            releaseSonatypeStagingRepository \
    -            --staging-repository-id=${{ github.event.inputs.stagingRepoId }}
    +            jreleaserDeploy \
    +            -Pjreleaser.mavencentral.stage=PUBLISH \
    +            -Pjreleaser.mavencentral.deployment.id=${{ github.event.inputs.deploymentId }}
     
       publish_documentation:
         name: Publish documentation
    -    needs: release_staging_repo
    +    needs: publish_deployment
         runs-on: ubuntu-latest
         steps:
           - name: Check out repository
    @@ -182,7 +206,7 @@ jobs:
     
       wait_for_maven_central:
         name: Wait for Maven Central
    -    needs: release_staging_repo
    +    needs: publish_deployment
         runs-on: ubuntu-latest
         steps:
           - name: Check out repository
    diff --git a/RELEASING.md b/RELEASING.md
    index bdbb753eba27..4616ac429047 100644
    --- a/RELEASING.md
    +++ b/RELEASING.md
    @@ -7,11 +7,11 @@
     - [ ] Change release date in Release Notes
     - [ ] Change release date in `README.MD`
     - [ ] Commit with message "Release ${VERSION}"
    -- [ ] Execute `./gradlew --no-build-cache --no-configuration-cache clean build publish closeSonatypeStagingRepository`
    +- [ ] Execute `./gradlew --no-build-cache --no-configuration-cache clean build jreleaserDeploy`
     - [ ] Tag current commit: `git tag -s -m ${VERSION} r${VERSION}`
     - [ ] Change `version`, `platformVersion`, and `vintageVersion` properties in `gradle.properties` on release branch to new development versions and commit with message "Back to snapshots for further development" or similar
     - [ ] Push release branch and tag to GitHub: `git push --set-upstream --follow-tags origin HEAD`
    -- [ ] Trigger a [release build](https://github.com/junit-team/junit5/actions/workflows/release.yml): `gh workflow run --ref r${VERSION} -f releaseVersion=${VERSION} -f stagingRepoId=orgjunit-1234 release.yml`
    +- [ ] Trigger a [release build](https://github.com/junit-team/junit5/actions/workflows/release.yml): `gh workflow run --ref r${VERSION} -f releaseVersion=${VERSION} -f deploymentId=${DEPLOYMENT_ID} release.yml`
       - Select the release branch
       - Enter the version to be released
       - Enter the staging repository ID from the output of above Gradle build
    diff --git a/gradle/plugins/publishing/src/main/kotlin/junitbuild/release/VerifyBinaryArtifactsAreIdentical.kt b/gradle/plugins/publishing/src/main/kotlin/junitbuild/release/VerifyBinaryArtifactsAreIdentical.kt
    index 9acd67b96c89..ad7c0fddc8c1 100644
    --- a/gradle/plugins/publishing/src/main/kotlin/junitbuild/release/VerifyBinaryArtifactsAreIdentical.kt
    +++ b/gradle/plugins/publishing/src/main/kotlin/junitbuild/release/VerifyBinaryArtifactsAreIdentical.kt
    @@ -3,8 +3,10 @@ package junitbuild.release
     import org.gradle.api.DefaultTask
     import org.gradle.api.file.DirectoryProperty
     import org.gradle.api.provider.Property
    +import org.gradle.api.provider.ProviderFactory
     import org.gradle.api.tasks.Input
     import org.gradle.api.tasks.InputDirectory
    +import org.gradle.api.tasks.Internal
     import org.gradle.api.tasks.PathSensitive
     import org.gradle.api.tasks.PathSensitivity
     import org.gradle.api.tasks.TaskAction
    @@ -15,7 +17,7 @@ import java.net.http.HttpClient
     import java.net.http.HttpRequest
     import java.net.http.HttpResponse.BodyHandlers
     
    -abstract class VerifyBinaryArtifactsAreIdentical : DefaultTask() {
    +abstract class VerifyBinaryArtifactsAreIdentical(providers: ProviderFactory) : DefaultTask() {
     
         @get:InputDirectory
         @get:PathSensitive(PathSensitivity.RELATIVE)
    @@ -24,9 +26,13 @@ abstract class VerifyBinaryArtifactsAreIdentical : DefaultTask() {
         @get:Input
         abstract val remoteRepoUrl: Property
     
    +    @get:Internal
    +    abstract val remoteRepoBearerToken: Property
    +
         init {
             // Depends on contents of remote repository
             outputs.upToDateWhen { false }
    +        remoteRepoBearerToken.convention(providers.environmentVariable("MAVEN_CENTRAL_USER_TOKEN"))
         }
     
         @Suppress("unused")
    @@ -51,7 +57,10 @@ abstract class VerifyBinaryArtifactsAreIdentical : DefaultTask() {
                         val relativeFile = file.relativeTo(localRootDir)
                         val url = URI.create("${baseUrl}/${relativeFile.path}")
                         logger.info("Checking {}...", url)
    -                    val request = HttpRequest.newBuilder().GET().uri(url).build()
    +                    val request = HttpRequest.newBuilder().GET()
    +                        .uri(url)
    +                        .header("Authorization", "Bearer ${remoteRepoBearerToken.get()}")
    +                        .build()
                         val response = httpClient.send(request, BodyHandlers.ofString())
                         val remoteSha512 = if (response.statusCode() == 200) response.body() else "status=${response.statusCode()}"
                         if (localSha512 != remoteSha512) {
    
    From 2e31cde1f0bdf16dbd9d3c8c1558a7c42bb3edfd Mon Sep 17 00:00:00 2001
    From: Marc Philipp 
    Date: Wed, 14 May 2025 17:35:03 +0200
    Subject: [PATCH 23/42] Introduce dry-run workflow input
    
    ---
     .github/workflows/release.yml | 31 ++++++++++++++++++++++++++++---
     1 file changed, 28 insertions(+), 3 deletions(-)
    
    diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
    index 91cc95a45f01..207629cf358e 100644
    --- a/.github/workflows/release.yml
    +++ b/.github/workflows/release.yml
    @@ -9,6 +9,11 @@ on:
           deploymentId:
             description: ID of the Maven Central Publish Portal deployment
             required: true
    +      dryRun:
    +        type: boolean
    +        description: Enable dry-run mode
    +        required: false
    +        default: false
     
     permissions: read-all
     
    @@ -57,6 +62,7 @@ jobs:
                 :verifyArtifactsInStagingRepositoryAreReproducible \
                 --remote-repo-url=${{ env.STAGING_REPO_URL }}
           - name: Generate build provenance attestations
    +        if: ${{ inputs.dryRun == 'false' }}
             uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0
             with:
               subject-path: build/repo/**/*.jar
    @@ -113,6 +119,7 @@ jobs:
           issues: write
         steps:
           - name: Close GitHub milestone
    +        if: ${{ inputs.dryRun == 'false' }}
             uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
             with:
               result-encoding: string
    @@ -150,6 +157,7 @@ jobs:
               fetch-depth: 1
               ref: "refs/tags/${{ env.RELEASE_TAG }}"
           - name: Release staging repository
    +        if: ${{ inputs.dryRun == 'false' }}
             uses: ./.github/actions/run-gradle
             env:
               ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
    @@ -179,7 +187,18 @@ jobs:
             run: |
               git config --global user.name "JUnit Team"
               git config --global user.email "team@junit.org"
    -      - name: Build and publish documentation
    +      - name: Build documentation
    +        uses: ./.github/actions/run-gradle
    +        with:
    +          encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
    +          arguments: |
    +            --no-build-cache \
    +            --no-configuration-cache \
    +            clean \
    +            gitPublishCopy \
    +            -Pdocumentation.replaceCurrentDocs=${{ contains(github.event.inputs.releaseVersion, '-') && 'false' || 'true' }}
    +      - name: Publish documentation
    +        if: ${{ inputs.dryRun == 'false' }}
             uses: ./.github/actions/run-gradle
             env:
               GIT_USERNAME: git
    @@ -189,10 +208,10 @@ jobs:
               arguments: |
                 --no-build-cache \
                 --no-configuration-cache \
    -            clean \
                 gitPublishPush \
                 -Pdocumentation.replaceCurrentDocs=${{ contains(github.event.inputs.releaseVersion, '-') && 'false' || 'true' }}
           - name: Wait for deployment to GitHub Pages
    +        if: ${{ inputs.dryRun == 'false' }}
             id: pagesDeployment
             timeout-minutes: 20
             run: |
    @@ -200,6 +219,7 @@ jobs:
               ./.github/scripts/waitForUrl.sh "$URL"
               echo "pdfUrl=$URL" >> "$GITHUB_OUTPUT"
           - name: Verify integrity of PDF version of User Guide
    +        if: ${{ inputs.dryRun == 'false' }}
             run: |
               curl --silent --fail --location --output /tmp/junit-user-guide.pdf "${{ steps.pagesDeployment.outputs.pdfUrl }}"
               pdfinfo /tmp/junit-user-guide.pdf
    @@ -220,6 +240,7 @@ jobs:
               name: local-maven-repository
               path: build/repo
           - name: Wait for sync to Maven Central
    +        if: ${{ inputs.dryRun == 'false' }}
             timeout-minutes: 30
             run: |
               find build/repo -name '*.pom' -printf './.github/scripts/waitForMavenCentralSync.sh %P\n' | sh
    @@ -252,9 +273,12 @@ jobs:
               git switch -c "${{ env.RELEASE_TAG }}"
               git status
               git commit -a -m "Use ${{ github.event.inputs.releaseVersion }}"
    +      - name: Push release branch
    +        if: ${{ inputs.dryRun == 'false' }}
    +        run: |
               git push origin "${{ env.RELEASE_TAG }}"
           - name: Update main branch (only for GA releases)
    -        if: ${{ !contains(github.event.inputs.releaseVersion, '-') }}
    +        if: ${{ inputs.dryRun == 'false' && !contains(github.event.inputs.releaseVersion, '-') }}
             run: |
               git switch main
               git merge --ff-only "${{ env.RELEASE_TAG }}"
    @@ -262,6 +286,7 @@ jobs:
     
       create_github_release:
         name: Create GitHub release
    +    if: ${{ inputs.dryRun == 'false' }}
         needs: wait_for_maven_central
         runs-on: ubuntu-latest
         permissions:
    
    From 3ce73bc774b5d0e9c190c0aad99836fb0e8211fc Mon Sep 17 00:00:00 2001
    From: Marc Philipp 
    Date: Wed, 14 May 2025 19:59:59 +0200
    Subject: [PATCH 24/42] Inject Maven Central Portal credentials
    
    ---
     .github/workflows/main.yml | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
    index e41fbe61130b..1a381b9ce10f 100644
    --- a/.github/workflows/main.yml
    +++ b/.github/workflows/main.yml
    @@ -85,8 +85,8 @@ jobs:
         - name: Publish
           uses: ./.github/actions/run-gradle
           env:
    -        ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.SONATYPE_USERNAME }}
    -        ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.SONATYPE_PASSWORD }}
    +        ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
    +        ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
           with:
             encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
             arguments: |
    
    From f1f421ec430abd61a91ee4846f9ae30dbf91b612 Mon Sep 17 00:00:00 2001
    From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
    Date: Wed, 14 May 2025 18:04:26 +0000
    Subject: [PATCH 25/42] Update plugin jreleaser to v1.18.0
    
    ---
     gradle/libs.versions.toml | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
    index d8c82070a471..6ab7d672fb00 100644
    --- a/gradle/libs.versions.toml
    +++ b/gradle/libs.versions.toml
    @@ -98,7 +98,7 @@ develocity = { id = "com.gradle.develocity", version = "4.0.1" }
     foojayResolver = { id = "org.gradle.toolchains.foojay-resolver", version = "0.10.0" }
     gitPublish = { id = "org.ajoberstar.git-publish", version = "5.1.1" }
     jmh = { id = "me.champeau.jmh", version = "0.7.3" }
    -jreleaser = { id = "org.jreleaser", version = "1.17.0" }
    +jreleaser = { id = "org.jreleaser", version = "1.18.0" }
     # check if workaround in gradle.properties can be removed when updating
     kotlin = { id = "org.jetbrains.kotlin.jvm", version = "2.1.21" }
     plantuml = { id = "io.freefair.plantuml", version = "8.13.1" }
    
    From 79f30a3502f469df4bdbd7bc285367ab245146be Mon Sep 17 00:00:00 2001
    From: Marc Philipp 
    Date: Thu, 15 May 2025 07:58:18 +0200
    Subject: [PATCH 26/42] Use `./` prefix
    
    ---
     .github/workflows/release.yml | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
    index 207629cf358e..701080b0527c 100644
    --- a/.github/workflows/release.yml
    +++ b/.github/workflows/release.yml
    @@ -99,7 +99,7 @@ jobs:
             run: java src/Updater.java ${{ github.event.inputs.releaseVersion }}
             working-directory: junit5-samples
           - name: Prepare Maven Central user token
    -        uses: junit5/.github/actions/maven-central-user-token
    +        uses: ./junit5/.github/actions/maven-central-user-token
             with:
               username: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
               password: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
    
    From 8ebda0c452ac4d702e88586cf354e2e903f0571e Mon Sep 17 00:00:00 2001
    From: Marc Philipp 
    Date: Thu, 15 May 2025 08:25:23 +0200
    Subject: [PATCH 27/42] Fix path to maven-settings.xml
    
    ---
     .github/workflows/release.yml | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
    index 701080b0527c..23e540e75c69 100644
    --- a/.github/workflows/release.yml
    +++ b/.github/workflows/release.yml
    @@ -110,7 +110,7 @@ jobs:
             run: java src/Builder.java --exclude=junit5-jupiter-starter-bazel,junit5-jupiter-starter-sbt
             working-directory: junit5-samples
             env:
    -          MAVEN_ARGS: --settings src/central-staging-maven-settings.xml --activate-profiles central-staging
    +          MAVEN_ARGS: --settings ${{ github.workspace }}/junit5-samples/src/central-staging-maven-settings.xml --activate-profiles central-staging
     
       close_github_milestone:
         name: Close GitHub milestone
    
    From 33161169b0f1eec2696d99eb27cf42a00ea3a31b Mon Sep 17 00:00:00 2001
    From: Marc Philipp 
    Date: Thu, 15 May 2025 08:25:57 +0200
    Subject: [PATCH 28/42] Add missing `@Inject` annotation to Gradle task
     constructor
    
    ---
     .../junitbuild/release/VerifyBinaryArtifactsAreIdentical.kt    | 3 ++-
     1 file changed, 2 insertions(+), 1 deletion(-)
    
    diff --git a/gradle/plugins/publishing/src/main/kotlin/junitbuild/release/VerifyBinaryArtifactsAreIdentical.kt b/gradle/plugins/publishing/src/main/kotlin/junitbuild/release/VerifyBinaryArtifactsAreIdentical.kt
    index ad7c0fddc8c1..293026023a07 100644
    --- a/gradle/plugins/publishing/src/main/kotlin/junitbuild/release/VerifyBinaryArtifactsAreIdentical.kt
    +++ b/gradle/plugins/publishing/src/main/kotlin/junitbuild/release/VerifyBinaryArtifactsAreIdentical.kt
    @@ -16,8 +16,9 @@ import java.net.URI
     import java.net.http.HttpClient
     import java.net.http.HttpRequest
     import java.net.http.HttpResponse.BodyHandlers
    +import javax.inject.Inject
     
    -abstract class VerifyBinaryArtifactsAreIdentical(providers: ProviderFactory) : DefaultTask() {
    +abstract class VerifyBinaryArtifactsAreIdentical @Inject constructor(providers: ProviderFactory): DefaultTask() {
     
         @get:InputDirectory
         @get:PathSensitive(PathSensitivity.RELATIVE)
    
    From 593313836a24f578b477592dbf064286aac311d8 Mon Sep 17 00:00:00 2001
    From: Marc Philipp 
    Date: Thu, 15 May 2025 08:53:38 +0200
    Subject: [PATCH 29/42] Disable slow pom checking
    
    This is done by the Central Portal anyway during validation of the
    uploaded deployment.
    ---
     .../main/kotlin/junitbuild.maven-central-publishing.gradle.kts   | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/gradle/plugins/publishing/src/main/kotlin/junitbuild.maven-central-publishing.gradle.kts b/gradle/plugins/publishing/src/main/kotlin/junitbuild.maven-central-publishing.gradle.kts
    index bdbd857ea8ae..2b2c08f4ea9a 100644
    --- a/gradle/plugins/publishing/src/main/kotlin/junitbuild.maven-central-publishing.gradle.kts
    +++ b/gradle/plugins/publishing/src/main/kotlin/junitbuild.maven-central-publishing.gradle.kts
    @@ -41,6 +41,7 @@ jreleaser {
     					applyMavenCentralRules = true
     					sign = false
     					checksums = false
    +					verifyPom = false
     					namespace = "org.junit"
     					stage = providers.gradleProperty("jreleaser.mavencentral.stage")
     						.map(Stage::of)
    
    From 0d006ab43faaf4fb48161edbf44842984b2c623c Mon Sep 17 00:00:00 2001
    From: Marc Philipp 
    Date: Thu, 15 May 2025 08:56:16 +0200
    Subject: [PATCH 30/42] Don't build updated samples in dry-run mode
    
    ---
     .github/workflows/release.yml | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
    index 23e540e75c69..6efef3ca75e0 100644
    --- a/.github/workflows/release.yml
    +++ b/.github/workflows/release.yml
    @@ -265,6 +265,7 @@ jobs:
           - name: Update JUnit dependencies in samples
             run: java src/Updater.java ${{ github.event.inputs.releaseVersion }}
           - name: Build samples
    +        if: ${{ inputs.dryRun == 'false' }}
             run: java src/Builder.java
           - name: Create release branch
             run: |
    
    From f78b9b6ba4d44acb819e5c66972dcd3c96234aeb Mon Sep 17 00:00:00 2001
    From: Marc Philipp 
    Date: Thu, 15 May 2025 11:13:14 +0200
    Subject: [PATCH 31/42] Fix Kotlin `Sequence` support for dynamic tests
    
    ---
     .../engine/discovery/predicates/IsTestFactoryMethod.java   | 7 ++-----
     1 file changed, 2 insertions(+), 5 deletions(-)
    
    diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java
    index fcc37c85bf2f..144ec6db621c 100644
    --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java
    +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java
    @@ -11,14 +11,13 @@
     package org.junit.jupiter.engine.discovery.predicates;
     
     import static org.apiguardian.api.API.Status.INTERNAL;
    +import static org.junit.platform.commons.util.CollectionUtils.isConvertibleToStream;
     
     import java.lang.annotation.Annotation;
     import java.lang.reflect.Method;
     import java.lang.reflect.ParameterizedType;
     import java.lang.reflect.Type;
     import java.lang.reflect.WildcardType;
    -import java.util.Iterator;
    -import java.util.stream.Stream;
     
     import org.apiguardian.api.API;
     import org.junit.jupiter.api.DynamicNode;
    @@ -62,9 +61,7 @@ private static boolean isCompatible(Method method, DiscoveryIssueReporter issueR
     			issueReporter.reportIssue(createTooGenericReturnTypeIssue(method));
     			return true;
     		}
    -		boolean validContainerType = Stream.class.isAssignableFrom(returnType) //
    -				|| Iterable.class.isAssignableFrom(returnType) //
    -				|| Iterator.class.isAssignableFrom(returnType);
    +		boolean validContainerType = !returnType.isArray() && isConvertibleToStream(returnType);
     		return validContainerType && isCompatibleContainerType(method, issueReporter);
     	}
     
    
    From 3f79ffd3a9406fa3efafd9c6fbdecfb98d0271a0 Mon Sep 17 00:00:00 2001
    From: Marc Philipp 
    Date: Thu, 15 May 2025 11:13:24 +0200
    Subject: [PATCH 32/42] Update Javadoc
    
    ---
     .../listeners/discovery/LauncherDiscoveryListeners.java  | 9 ---------
     1 file changed, 9 deletions(-)
    
    diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LauncherDiscoveryListeners.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LauncherDiscoveryListeners.java
    index 757d17f6f400..d6923652c749 100644
    --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LauncherDiscoveryListeners.java
    +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LauncherDiscoveryListeners.java
    @@ -21,9 +21,7 @@
     import org.apiguardian.api.API;
     import org.junit.platform.commons.JUnitException;
     import org.junit.platform.commons.util.Preconditions;
    -import org.junit.platform.engine.SelectorResolutionResult.Status;
     import org.junit.platform.engine.TestEngine;
    -import org.junit.platform.engine.discovery.UniqueIdSelector;
     import org.junit.platform.launcher.LauncherDiscoveryListener;
     
     /**
    @@ -46,13 +44,6 @@ private LauncherDiscoveryListeners() {
     	 *
     	 * 
      *
    • - * a {@linkplain Status#FAILED failed} resolution result. - *
    • - *
    • - * an {@linkplain Status#FAILED unresolved} resolution result for a - * {@link UniqueIdSelector} that starts with the engine's unique ID. - *
    • - *
    • * any recoverable {@link Throwable} thrown by * {@link TestEngine#discover}. *
    • From dcd29b8b61ed487603d530b52d8109f99b54e223 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 15 May 2025 11:13:40 +0200 Subject: [PATCH 33/42] Disable test output --- platform-tests/src/test/resources/log4j2-test.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/platform-tests/src/test/resources/log4j2-test.xml b/platform-tests/src/test/resources/log4j2-test.xml index 544a157b1d8f..636f0c345058 100644 --- a/platform-tests/src/test/resources/log4j2-test.xml +++ b/platform-tests/src/test/resources/log4j2-test.xml @@ -11,6 +11,7 @@ + From b816f69ba9b68f1657cea63c93f4af203758e2d7 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 15 May 2025 11:29:45 +0200 Subject: [PATCH 34/42] Disable Predictive Test Selection for now to surface discovery issues --- .github/actions/run-gradle/action.yml | 2 -- gradle/plugins/build-parameters/build.gradle.kts | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/actions/run-gradle/action.yml b/.github/actions/run-gradle/action.yml index 7506f777a7ce..16a1211555af 100644 --- a/.github/actions/run-gradle/action.yml +++ b/.github/actions/run-gradle/action.yml @@ -26,8 +26,6 @@ runs: run: | ./gradlew \ -Porg.gradle.java.installations.auto-download=false \ - -Pjunit.develocity.predictiveTestSelection.enabled=true \ - -Pjunit.develocity.predictiveTestSelection.selectRemainingTests=${{ github.event_name != 'pull_request' }} \ "-Dscan.value.GitHub job=${{ github.job }}" \ javaToolchains \ ${{ inputs.arguments }} diff --git a/gradle/plugins/build-parameters/build.gradle.kts b/gradle/plugins/build-parameters/build.gradle.kts index 8a1276886d14..bed7fe291200 100644 --- a/gradle/plugins/build-parameters/build.gradle.kts +++ b/gradle/plugins/build-parameters/build.gradle.kts @@ -39,7 +39,7 @@ buildParameters { group("predictiveTestSelection") { bool("enabled") { description = "Whether or not to use Predictive Test Selection for selecting tests to execute" - defaultValue = true + defaultValue = false } bool("selectRemainingTests") { // see https://docs.gradle.com/develocity/predictive-test-selection/#gradle-selection-mode From a0e4c93f59105f69ffa4cd048b8ca73ed2cab333 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 22:05:50 +0000 Subject: [PATCH 35/42] Update codecov/codecov-action action to v5.4.3 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1a381b9ce10f..aadc10b78069 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -41,7 +41,7 @@ jobs: jacocoRootReport \ --no-configuration-cache # Disable configuration cache due to https://github.com/diffplug/spotless/issues/2318 - name: Upload to Codecov.io - uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 + uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 with: token: ${{ secrets.CODECOV_TOKEN }} From bf126747b6940d263ac8d13b9b7656f3cf9dc646 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 16 May 2025 11:35:50 +0000 Subject: [PATCH 36/42] Update github/codeql-action action to v3.28.18 --- .github/workflows/codeql-analysis.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e27db6e3b99f..5664eeca4c65 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -34,7 +34,7 @@ jobs: - name: Check out repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Initialize CodeQL - uses: github/codeql-action/init@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 + uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 with: languages: ${{ matrix.language }} tools: linked @@ -47,4 +47,4 @@ jobs: -Dscan.tag.CodeQL \ allMainClasses - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 + uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index ea885c523a17..c24ae2e85e54 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -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@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 + uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 with: sarif_file: results.sarif From aa0e34da5cc3e3fdce861b9028134571c86fdd8a Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 15 May 2025 12:21:48 +0200 Subject: [PATCH 37/42] Make phase that fails due to critical discovery issues configurable Users of the Launcher API that perform a discovery without executing the resulting test plan are prone to accidentally hiding discovery issues from their users unless they implement their own `LauncherDiscoveryListener`. Therefore, the new `junit.platform.discovery.issue.failure.phase` configuration parameter allows configuring the `discovery` phase as the one that should fail in case critical discovery issues are encountered. By default, they will still be reported in the `execution` phase. --- .../platform/launcher/LauncherConstants.java | 21 +++++++- .../launcher/core/DiscoveryIssueNotifier.java | 8 +++ .../core/EngineDiscoveryOrchestrator.java | 24 ++++++++- .../core/EngineExecutionOrchestrator.java | 6 ++- .../AbstractJupiterTestEngineTests.java | 2 +- ...stInstanceLifecycleConfigurationTests.java | 2 + .../DiscoverySelectorResolverTests.java | 2 +- .../engine/discovery/DiscoveryTests.java | 43 ++++++++-------- ...dingDiscoveryListenerIntegrationTests.java | 1 + .../launcher/core/DefaultLauncherTests.java | 49 +++++++++++++++++++ ...LoggingLauncherDiscoveryListenerTests.java | 5 ++ ...ngineDiscoveryResultsIntegrationTests.java | 9 +++- .../testkit/engine/EngineTestKitTests.java | 1 + 13 files changed, 144 insertions(+), 29 deletions(-) diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java index b356e376b54c..842481f0fe90 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java @@ -225,9 +225,10 @@ public class LauncherConstants { * *

      If an engine reports an issue with a severity equal to or higher than * the configured critical severity, its tests will not be executed. - * Instead, the engine will be reported as failed during execution with a + * Depending on {@link #DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME}, a * {@link org.junit.platform.launcher.core.DiscoveryIssueException} listing - * all critical issues. + * all critical issues will be thrown during discovery or be reported as + * engine-level failure during execution. * *

      Supported Values

      * @@ -244,6 +245,22 @@ public class LauncherConstants { @API(status = EXPERIMENTAL, since = "1.13") public static final String CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME = "junit.platform.discovery.issue.severity.critical"; + /** + * Property name used to configure the phase that critical discovery issues + * should cause a failure + * + *

      Supported Values

      + * + *

      Supported values are "discovery" or "execution". + * + *

      If not specified, the default is "execution". + * + * @since 1.13 + * @see #CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME + */ + @API(status = EXPERIMENTAL, since = "1.13") + public static final String DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME = "junit.platform.discovery.issue.failure.phase"; + private LauncherConstants() { /* no-op */ } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueNotifier.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueNotifier.java index 592af7bd866b..215e077c542f 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueNotifier.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueNotifier.java @@ -14,16 +14,19 @@ import static java.util.Comparator.comparing; import static java.util.stream.Collectors.partitioningBy; import static org.junit.platform.commons.util.ExceptionUtils.readStackTrace; +import static org.junit.platform.launcher.LauncherConstants.DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Consumer; import java.util.function.Supplier; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.TestEngine; @@ -140,4 +143,9 @@ else if (source instanceof ClassSource) { private static void appendIdeCompatibleLink(StringBuilder message, String className, String methodName) { message.append("\n at ").append(className).append(".").append(methodName).append("(SourceFile:0)"); } + + static boolean handleDiscoveryIssuesInDiscoveryPhase(ConfigurationParameters configurationParameters) { + Optional phase = configurationParameters.get(DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME); + return "discovery".equalsIgnoreCase(phase.orElse(null)); + } } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java index 00bcdae9ada8..9b23641631bf 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java @@ -13,6 +13,7 @@ import static java.util.stream.Collectors.joining; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.engine.Filter.composeFilters; +import static org.junit.platform.launcher.core.DiscoveryIssueNotifier.handleDiscoveryIssuesInDiscoveryPhase; import java.util.Collection; import java.util.LinkedHashMap; @@ -109,15 +110,36 @@ public LauncherDiscoveryListener getDiscoveryListener() { } }; listener.launcherDiscoveryStarted(request); + LauncherDiscoveryResult discoveryResult; try { Map testEngineResults = discoverSafely(delegatingRequest, phase, issueCollector, uniqueIdCreator); - return new LauncherDiscoveryResult(testEngineResults, request.getConfigurationParameters(), + discoveryResult = new LauncherDiscoveryResult(testEngineResults, request.getConfigurationParameters(), request.getOutputDirectoryProvider()); } finally { listener.launcherDiscoveryFinished(request); } + if (handleDiscoveryIssuesInDiscoveryPhase(request.getConfigurationParameters())) { + handleDiscoveryIssues(discoveryResult); + } + return discoveryResult; + } + + private static void handleDiscoveryIssues(LauncherDiscoveryResult discoveryResult) { + DiscoveryIssueException exception = null; + for (TestEngine testEngine : discoveryResult.getTestEngines()) { + EngineResultInfo engineResult = discoveryResult.getEngineResult(testEngine); + DiscoveryIssueNotifier discoveryIssueNotifier = engineResult.getDiscoveryIssueNotifier(); + discoveryIssueNotifier.logCriticalIssues(testEngine); + discoveryIssueNotifier.logNonCriticalIssues(testEngine); + if (exception == null) { + exception = discoveryIssueNotifier.createExceptionForCriticalIssues(testEngine); + } + } + if (exception != null) { + throw exception; + } } private Map discoverSafely(LauncherDiscoveryRequest request, Phase phase, diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java index eccb29fd82df..b63bea0b4df2 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java @@ -13,6 +13,7 @@ import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.launcher.LauncherConstants.DRY_RUN_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME; +import static org.junit.platform.launcher.core.DiscoveryIssueNotifier.handleDiscoveryIssuesInDiscoveryPhase; import static org.junit.platform.launcher.core.ListenerRegistry.forEngineExecutionListeners; import java.util.Optional; @@ -185,7 +186,10 @@ private static EngineExecutionListener selectExecutionListener(EngineExecutionLi private void failOrExecuteEngine(LauncherDiscoveryResult discoveryResult, EngineExecutionListener listener, TestEngine testEngine, NamespacedHierarchicalStore requestLevelStore) { EngineResultInfo engineDiscoveryResult = discoveryResult.getEngineResult(testEngine); - DiscoveryIssueNotifier discoveryIssueNotifier = engineDiscoveryResult.getDiscoveryIssueNotifier(); + DiscoveryIssueNotifier discoveryIssueNotifier = handleDiscoveryIssuesInDiscoveryPhase( + discoveryResult.getConfigurationParameters()) // + ? DiscoveryIssueNotifier.NO_ISSUES // + : engineDiscoveryResult.getDiscoveryIssueNotifier(); TestDescriptor engineDescriptor = engineDiscoveryResult.getRootDescriptor(); Throwable failure = engineDiscoveryResult.getCause() // .orElseGet(() -> discoveryIssueNotifier.createExceptionForCriticalIssues(testEngine)); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java index 35a2e8941a73..0dcd074d74f8 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java @@ -82,7 +82,7 @@ protected EngineDiscoveryResults discoverTests(LauncherDiscoveryRequest request) return EngineTestKit.discover(this.engine, request); } - private static LauncherDiscoveryRequestBuilder defaultRequest() { + protected static LauncherDiscoveryRequestBuilder defaultRequest() { return request() // .outputDirectoryProvider(dummyOutputDirectoryProvider()) // .configurationParameter(STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME, String.valueOf(false)) // diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleConfigurationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleConfigurationTests.java index 8090690f32f6..e121fbd683aa 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleConfigurationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleConfigurationTests.java @@ -16,6 +16,7 @@ import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.LauncherConstants.DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import java.util.ArrayList; @@ -129,6 +130,7 @@ private void performAssertions(Class testClass, Map configPar request() .selectors(selectClass(testClass)) .configurationParameters(configParams) + .configurationParameter(DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME, "execution") .build() ); // @formatter:on diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java index 697efda838a4..f607f28e15b9 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java @@ -819,7 +819,7 @@ private List uniqueIds() { } private LauncherDiscoveryRequestBuilder request() { - return LauncherDiscoveryRequestBuilder.request() // + return defaultRequest() // .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // .listeners(discoveryListener); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java index ab2d904f3253..26db9306abb9 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java @@ -25,7 +25,6 @@ import static org.junit.platform.engine.discovery.DiscoverySelectors.selectNestedMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -62,21 +61,21 @@ class DiscoveryTests extends AbstractJupiterTestEngineTests { @Test void discoverTestClass() { - LauncherDiscoveryRequest request = request().selectors(selectClass(LocalTestCase.class)).build(); + LauncherDiscoveryRequest request = defaultRequest().selectors(selectClass(LocalTestCase.class)).build(); TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertEquals(7, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @Test void doNotDiscoverAbstractTestClass() { - LauncherDiscoveryRequest request = request().selectors(selectClass(AbstractTestCase.class)).build(); + LauncherDiscoveryRequest request = defaultRequest().selectors(selectClass(AbstractTestCase.class)).build(); TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertEquals(0, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @Test void discoverMethodByUniqueId() { - LauncherDiscoveryRequest request = request().selectors( + LauncherDiscoveryRequest request = defaultRequest().selectors( selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod(LocalTestCase.class, "test1()"))).build(); TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); @@ -84,7 +83,7 @@ void discoverMethodByUniqueId() { @Test void discoverMethodByUniqueIdForOverloadedMethod() { - LauncherDiscoveryRequest request = request().selectors( + LauncherDiscoveryRequest request = defaultRequest().selectors( selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod(LocalTestCase.class, "test4()"))).build(); TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); @@ -92,8 +91,9 @@ void discoverMethodByUniqueIdForOverloadedMethod() { @Test void discoverMethodByUniqueIdForOverloadedMethodVariantThatAcceptsArguments() { - LauncherDiscoveryRequest request = request().selectors(selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod( - LocalTestCase.class, "test4(" + TestInfo.class.getName() + ")"))).build(); + LauncherDiscoveryRequest request = defaultRequest().selectors( + selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod(LocalTestCase.class, + "test4(" + TestInfo.class.getName() + ")"))).build(); TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @@ -102,14 +102,15 @@ void discoverMethodByUniqueIdForOverloadedMethodVariantThatAcceptsArguments() { void discoverMethodByMethodReference() throws NoSuchMethodException { Method testMethod = LocalTestCase.class.getDeclaredMethod("test3"); - LauncherDiscoveryRequest request = request().selectors(selectMethod(LocalTestCase.class, testMethod)).build(); + LauncherDiscoveryRequest request = defaultRequest().selectors( + selectMethod(LocalTestCase.class, testMethod)).build(); TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @Test void discoverMultipleMethodsOfSameClass() { - LauncherDiscoveryRequest request = request().selectors(selectMethod(LocalTestCase.class, "test1"), + LauncherDiscoveryRequest request = defaultRequest().selectors(selectMethod(LocalTestCase.class, "test1"), selectMethod(LocalTestCase.class, "test2")).build(); TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); @@ -121,7 +122,7 @@ void discoverMultipleMethodsOfSameClass() { @Test void discoverCompositeSpec() { - LauncherDiscoveryRequest spec = request().selectors( + LauncherDiscoveryRequest spec = defaultRequest().selectors( selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod(LocalTestCase.class, "test2()")), selectClass(LocalTestCase.class)).build(); @@ -131,7 +132,7 @@ void discoverCompositeSpec() { @Test void discoverTestTemplateMethodByUniqueId() { - LauncherDiscoveryRequest spec = request().selectors( + LauncherDiscoveryRequest spec = defaultRequest().selectors( selectUniqueId(uniqueIdForTestTemplateMethod(TestTemplateClass.class, "testTemplate()"))).build(); TestDescriptor engineDescriptor = discoverTests(spec).getEngineDescriptor(); @@ -140,7 +141,7 @@ void discoverTestTemplateMethodByUniqueId() { @Test void discoverTestTemplateMethodByMethodSelector() { - LauncherDiscoveryRequest spec = request().selectors( + LauncherDiscoveryRequest spec = defaultRequest().selectors( selectMethod(TestTemplateClass.class, "testTemplate")).build(); TestDescriptor engineDescriptor = discoverTests(spec).getEngineDescriptor(); @@ -153,7 +154,7 @@ void discoverDeeplyNestedTestMethodByNestedMethodSelector() throws Exception { List.of(TestCaseWithExtendedNested.class, TestCaseWithExtendedNested.ConcreteInner1.class), AbstractSuperClass.NestedInAbstractClass.class, AbstractSuperClass.NestedInAbstractClass.class.getDeclaredMethod("test")); - LauncherDiscoveryRequest spec = request().selectors(selector).build(); + LauncherDiscoveryRequest spec = defaultRequest().selectors(selector).build(); TestDescriptor engineDescriptor = discoverTests(spec).getEngineDescriptor(); @@ -198,12 +199,12 @@ void reportsWarningForTestClassWithInvalidTestMethod(LauncherDiscoveryRequest re static List> requestsForTestClassWithInvalidTestMethod() { return List.of( // named("directly selected", - request().selectors(selectClass(InvalidTestCases.InvalidTestMethodTestCase.class)).build()), // - named("indirectly selected", request() // + defaultRequest().selectors(selectClass(InvalidTestCases.InvalidTestMethodTestCase.class)).build()), // + named("indirectly selected", defaultRequest() // .selectors(selectPackage(InvalidTestCases.InvalidTestMethodTestCase.class.getPackageName())) // .filters(includeClassNamePatterns( Pattern.quote(InvalidTestCases.InvalidTestMethodTestCase.class.getName()))).build()), // - named("subclasses", request() // + named("subclasses", defaultRequest() // .selectors(selectClass(InvalidTestCases.InvalidTestMethodSubclass1TestCase.class), selectClass(InvalidTestCases.InvalidTestMethodSubclass2TestCase.class)) // .build()) // @@ -229,14 +230,14 @@ void reportsWarningForInvalidStandaloneTestClass(LauncherDiscoveryRequest reques static List requestsForTestClassWithInvalidStandaloneTestClass() { return List.of( // argumentSet("directly selected", - request().selectors(selectClass(InvalidTestCases.InvalidTestClassTestCase.class)).build(), + defaultRequest().selectors(selectClass(InvalidTestCases.InvalidTestClassTestCase.class)).build(), InvalidTestCases.InvalidTestClassTestCase.class), // - argumentSet("indirectly selected", request() // + argumentSet("indirectly selected", defaultRequest() // .selectors(selectPackage(InvalidTestCases.InvalidTestClassTestCase.class.getPackageName())) // .filters(includeClassNamePatterns( Pattern.quote(InvalidTestCases.InvalidTestClassTestCase.class.getName()))).build(), // InvalidTestCases.InvalidTestClassTestCase.class), // - argumentSet("subclass", request() // + argumentSet("subclass", defaultRequest() // .selectors(selectClass(InvalidTestCases.InvalidTestClassSubclassTestCase.class)) // .build(), // InvalidTestCases.InvalidTestClassSubclassTestCase.class) // @@ -262,8 +263,8 @@ void reportsWarningForInvalidNestedTestClass(LauncherDiscoveryRequest request) { static List> requestsForTestClassWithInvalidNestedTestClass() { return List.of( // named("directly selected", - request().selectors(selectClass(InvalidTestCases.InvalidTestClassTestCase.Inner.class)).build()), // - named("subclass", request() // + defaultRequest().selectors(selectClass(InvalidTestCases.InvalidTestClassTestCase.Inner.class)).build()), // + named("subclass", defaultRequest() // .selectors(selectNestedClass(List.of(InvalidTestCases.InvalidTestClassSubclassTestCase.class), InvalidTestCases.InvalidTestClassTestCase.Inner.class)) // .build()) // diff --git a/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingDiscoveryListenerIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingDiscoveryListenerIntegrationTests.java index e893428d0246..c734d70e06f4 100644 --- a/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingDiscoveryListenerIntegrationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingDiscoveryListenerIntegrationTests.java @@ -57,6 +57,7 @@ public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId EngineTestKit.discover(testEngine, request() // .selectors(selectClass(FlightRecordingDiscoveryListenerIntegrationTests.class)) // .listeners(new FlightRecordingDiscoveryListener()) // + .enableImplicitConfigurationParameters(false) // .build()); jfrEvents.awaitEvents(); diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java index 1722bd9bfc42..9dbb53a0c8b7 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java @@ -948,6 +948,55 @@ public void execute(ExecutionRequest request) { LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME); } + @Test + void failsDuringDiscoveryIfConfigurationParameterIsSetAccordingly() { + + var engine = new TestEngineStub("engine-id") { + @Override + public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { + var listener = discoveryRequest.getDiscoveryListener(); + listener.issueEncountered(uniqueId, DiscoveryIssue.create(Severity.ERROR, "error")); + return new EngineDescriptor(uniqueId, "Engine"); + } + }; + + var exception = assertThrows(DiscoveryIssueException.class, () -> execute(engine, request -> request // + .configurationParameter(LauncherConstants.DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME, "discovery"))); + + assertThat(exception) // + .isInstanceOf(DiscoveryIssueException.class) // + .hasMessageStartingWith( + "TestEngine with ID 'engine-id' encountered a critical issue during test discovery") // + .hasMessageContaining("(1) [ERROR] error"); + } + + @ParameterizedTest + @ValueSource(strings = { "discovery", "execution" }) + void logsNonCriticalIssuesOnlyOnce(String phase, @TrackLogRecords LogRecordListener listener) { + + var engine = new TestEngineStub("engine-id") { + @Override + public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { + var listener = discoveryRequest.getDiscoveryListener(); + listener.issueEncountered(uniqueId, DiscoveryIssue.create(Severity.WARNING, "warning")); + return new EngineDescriptor(uniqueId, "Engine"); + } + + @Override + public void execute(ExecutionRequest request) { + var executionListener = request.getEngineExecutionListener(); + var engineDescriptor = request.getRootTestDescriptor(); + executionListener.executionStarted(engineDescriptor); + executionListener.executionFinished(engineDescriptor, successful()); + } + }; + + execute(engine, request -> request // + .configurationParameter(LauncherConstants.DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME, phase)); + + assertThat(listener.stream(DiscoveryIssueNotifier.class, Level.WARNING)).hasSize(1); + } + private static ReportedData execute(TestEngine engine) { return execute(engine, identity()); } diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListenerTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListenerTests.java index 400f4b43dd6a..f9b0344eb9d5 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListenerTests.java @@ -39,6 +39,7 @@ void logsWarningOnUnresolvedUniqueIdSelectorWithEnginePrefix(LogRecordListener l var request = request() // .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // .selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) // + .enableImplicitConfigurationParameters(false) // .build(); var launcher = createLauncher(engine); @@ -56,6 +57,7 @@ void logsDebugMessageOnUnresolvedUniqueIdSelectorWithoutEnginePrefix(LogRecordLi var request = request() // .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // .selectors(selectUniqueId(UniqueId.forEngine("some-other-engine"))) // + .enableImplicitConfigurationParameters(false) // .build(); var launcher = createLauncher(engine); @@ -74,6 +76,7 @@ void logsErrorOnSelectorResolutionFailure(LogRecordListener log) { var request = request() // .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // .selectors(selectClass(Object.class)) // + .enableImplicitConfigurationParameters(false) // .build(); var launcher = createLauncher(engine); @@ -97,6 +100,7 @@ public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId var request = request() // .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // .selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) // + .enableImplicitConfigurationParameters(false) // .build(); var launcher = createLauncher(engine); @@ -113,6 +117,7 @@ void logsTraceMessageOnStartAndEnd(LogRecordListener log) { var request = request() // .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // .selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) // + .enableImplicitConfigurationParameters(false) // .build(); var launcher = createLauncher(engine); diff --git a/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineDiscoveryResultsIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineDiscoveryResultsIntegrationTests.java index 29945442f388..24cb2cc5acd8 100644 --- a/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineDiscoveryResultsIntegrationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineDiscoveryResultsIntegrationTests.java @@ -27,6 +27,7 @@ import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.fakes.TestEngineStub; +import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; @ParameterizedClass @EnumSource @@ -70,12 +71,16 @@ enum TestKitApi { STATIC_METHOD { @Override EngineDiscoveryResults discover(String engineId, DiscoverySelector selector) { - return EngineTestKit.discover(engineId, request().selectors(selector).build()); + return EngineTestKit.discover(engineId, newRequest().selectors(selector).build()); } @Override EngineDiscoveryResults discover(TestEngine testEngine) { - return EngineTestKit.discover(testEngine, request().build()); + return EngineTestKit.discover(testEngine, newRequest().build()); + } + + private static LauncherDiscoveryRequestBuilder newRequest() { + return request().enableImplicitConfigurationParameters(false); } }, diff --git a/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java b/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java index 60776a7b6c4e..dffb0255fe7d 100644 --- a/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java +++ b/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java @@ -68,6 +68,7 @@ void verifyRequestLevelStoreIsUsedInExecution() { when(testEngine.getId()).thenReturn("test-engine"); LauncherDiscoveryRequest request = mock(LauncherDiscoveryRequest.class); + when(request.getConfigurationParameters()).thenReturn(mock()); when(request.getDiscoveryListener()).thenReturn(LauncherDiscoveryListener.NOOP); try (MockedConstruction mockedConstruction = mockConstruction( From a08987c0bca26340d9c272e9eef3421af9e3ca9d Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 16 May 2025 15:30:05 +0200 Subject: [PATCH 38/42] Make phase for reporting discovery issues dependent on `Launcher` method If the new configuration parameter is not specified, the `Launcher` will report discovery issues during the discovery phase if `Launcher#discover(LauncherDiscoveryRequest)` is called, and during the execution phase if `Launcher#execute(LauncherDiscoveryRequest, TestExecutionListener...)` is called. --- .../platform/launcher/LauncherConstants.java | 7 ++- .../launcher/core/DefaultLauncher.java | 7 +-- .../launcher/core/DiscoveryIssueNotifier.java | 8 --- .../core/EngineDiscoveryOrchestrator.java | 58 ++++++++++--------- .../core/EngineExecutionOrchestrator.java | 15 +++-- .../platform/launcher/core/LauncherPhase.java | 51 ++++++++++++++++ .../platform/suite/engine/SuiteLauncher.java | 3 +- .../testkit/engine/EngineTestKit.java | 12 ++-- .../launcher/core/DefaultLauncherTests.java | 2 + ...LoggingLauncherDiscoveryListenerTests.java | 3 + 10 files changed, 111 insertions(+), 55 deletions(-) create mode 100644 junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherPhase.java diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java index 842481f0fe90..a18a71b45c74 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java @@ -253,7 +253,12 @@ public class LauncherConstants { * *

      Supported values are "discovery" or "execution". * - *

      If not specified, the default is "execution". + *

      If not specified, the {@code Launcher} will report discovery issues + * during the discovery phase if + * {@link Launcher#discover(LauncherDiscoveryRequest)} is called, and during + * the execution phase if + * {@link Launcher#execute(LauncherDiscoveryRequest, TestExecutionListener...)} + * is called. * * @since 1.13 * @see #CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java index 2a9fa3bf1ed4..c4691503ba1f 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java @@ -12,8 +12,8 @@ import static java.util.Collections.unmodifiableCollection; import static org.junit.platform.engine.support.store.NamespacedHierarchicalStore.CloseAction.closeAutoCloseables; -import static org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase.DISCOVERY; -import static org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase.EXECUTION; +import static org.junit.platform.launcher.core.LauncherPhase.DISCOVERY; +import static org.junit.platform.launcher.core.LauncherPhase.EXECUTION; import java.util.Collection; @@ -100,8 +100,7 @@ public void execute(TestPlan testPlan, TestExecutionListener... listeners) { execute((InternalTestPlan) testPlan, listeners); } - private LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, - EngineDiscoveryOrchestrator.Phase phase) { + private LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, LauncherPhase phase) { return discoveryOrchestrator.discover(discoveryRequest, phase); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueNotifier.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueNotifier.java index 215e077c542f..592af7bd866b 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueNotifier.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueNotifier.java @@ -14,19 +14,16 @@ import static java.util.Comparator.comparing; import static java.util.stream.Collectors.partitioningBy; import static org.junit.platform.commons.util.ExceptionUtils.readStackTrace; -import static org.junit.platform.launcher.LauncherConstants.DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.function.Consumer; import java.util.function.Supplier; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.TestEngine; @@ -143,9 +140,4 @@ else if (source instanceof ClassSource) { private static void appendIdeCompatibleLink(StringBuilder message, String className, String methodName) { message.append("\n at ").append(className).append(".").append(methodName).append("(SourceFile:0)"); } - - static boolean handleDiscoveryIssuesInDiscoveryPhase(ConfigurationParameters configurationParameters) { - Optional phase = configurationParameters.get(DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME); - return "discovery".equalsIgnoreCase(phase.orElse(null)); - } } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java index 9b23641631bf..5c989b32d79a 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java @@ -13,13 +13,12 @@ import static java.util.stream.Collectors.joining; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.engine.Filter.composeFilters; -import static org.junit.platform.launcher.core.DiscoveryIssueNotifier.handleDiscoveryIssuesInDiscoveryPhase; +import static org.junit.platform.launcher.core.LauncherPhase.getDiscoveryIssueFailurePhase; import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.function.Function; @@ -29,6 +28,7 @@ import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.UnrecoverableExceptions; +import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.Filter; import org.junit.platform.engine.FilterResult; import org.junit.platform.engine.TestDescriptor; @@ -69,15 +69,19 @@ public EngineDiscoveryOrchestrator(Iterable testEngines, } /** - * Discovers tests for the supplied request in the supplied phase using the - * configured test engines. + * Discovers tests for the supplied request using the configured test + * engines. * *

      Applies {@linkplain org.junit.platform.launcher.EngineFilter engine * filters} and {@linkplain PostDiscoveryFilter post-discovery filters} and * {@linkplain TestDescriptor#prune() prunes} the resulting test tree. */ - public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Phase phase) { - return discover(request, phase, UniqueId::forEngine); + public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request) { + return discover(request, Optional.empty(), UniqueId::forEngine); + } + + LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, LauncherPhase phase) { + return discover(request, Optional.of(phase), UniqueId::forEngine); } /** @@ -94,12 +98,12 @@ public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Phase * {@link EngineExecutionOrchestrator} will not emit start or emit events * for engines without tests. */ - public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Phase phase, UniqueId parentId) { - LauncherDiscoveryResult result = discover(request, phase, parentId::appendEngine); + public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, UniqueId parentId) { + LauncherDiscoveryResult result = discover(request, Optional.empty(), parentId::appendEngine); return result.withRetainedEngines(TestDescriptor::containsTests); } - private LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Phase phase, + private LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Optional phase, Function uniqueIdCreator) { DiscoveryIssueCollector issueCollector = new DiscoveryIssueCollector(request.getConfigurationParameters()); LauncherDiscoveryListener listener = getLauncherDiscoveryListener(request, issueCollector); @@ -120,13 +124,20 @@ public LauncherDiscoveryListener getDiscoveryListener() { finally { listener.launcherDiscoveryFinished(request); } - if (handleDiscoveryIssuesInDiscoveryPhase(request.getConfigurationParameters())) { - handleDiscoveryIssues(discoveryResult); + if (shouldReportDiscoveryIssues(request, phase)) { + reportDiscoveryIssues(discoveryResult); } return discoveryResult; } - private static void handleDiscoveryIssues(LauncherDiscoveryResult discoveryResult) { + private static boolean shouldReportDiscoveryIssues(LauncherDiscoveryRequest request, + Optional phase) { + ConfigurationParameters configurationParameters = request.getConfigurationParameters(); + return getDiscoveryIssueFailurePhase(configurationParameters).orElse( + phase.orElse(null)) == LauncherPhase.DISCOVERY; + } + + private static void reportDiscoveryIssues(LauncherDiscoveryResult discoveryResult) { DiscoveryIssueException exception = null; for (TestEngine testEngine : discoveryResult.getTestEngines()) { EngineResultInfo engineResult = discoveryResult.getEngineResult(testEngine); @@ -142,8 +153,9 @@ private static void handleDiscoveryIssues(LauncherDiscoveryResult discoveryResul } } - private Map discoverSafely(LauncherDiscoveryRequest request, Phase phase, - DiscoveryIssueCollector issueCollector, Function uniqueIdCreator) { + private Map discoverSafely(LauncherDiscoveryRequest request, + Optional phase, DiscoveryIssueCollector issueCollector, + Function uniqueIdCreator) { Map testEngineDescriptors = new LinkedHashMap<>(); EngineFilterer engineFilterer = new EngineFilterer(request.getEngineFilters()); @@ -151,14 +163,13 @@ private Map discoverSafely(LauncherDiscoveryReques boolean engineIsExcluded = engineFilterer.isExcluded(testEngine); if (engineIsExcluded) { - logger.debug(() -> String.format( - "Test discovery for engine '%s' was skipped due to an EngineFilter in %s phase.", - testEngine.getId(), phase)); + logger.debug(() -> String.format("Test discovery for engine '%s' was skipped due to an EngineFilter%s.", + testEngine.getId(), phase.map(it -> String.format(" in %s phase", it)).orElse(""))); continue; } - logger.debug(() -> String.format("Discovering tests during Launcher %s phase in engine '%s'.", phase, - testEngine.getId())); + logger.debug(() -> String.format("Discovering tests%s in engine '%s'.", + phase.map(it -> String.format(" during Launcher %s phase", it)).orElse(""), testEngine.getId())); EngineResultInfo engineResult = discoverEngineRoot(testEngine, request, issueCollector, uniqueIdCreator); testEngineDescriptors.put(testEngine, engineResult); @@ -263,13 +274,4 @@ private void acceptInAllTestEngines(Map testEngine testEngineResults.values().forEach(result -> result.getRootDescriptor().accept(visitor)); } - public enum Phase { - DISCOVERY, EXECUTION; - - @Override - public String toString() { - return name().toLowerCase(Locale.ENGLISH); - } - } - } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java index b63bea0b4df2..1cf63758f3aa 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java @@ -13,7 +13,7 @@ import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.launcher.LauncherConstants.DRY_RUN_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME; -import static org.junit.platform.launcher.core.DiscoveryIssueNotifier.handleDiscoveryIssuesInDiscoveryPhase; +import static org.junit.platform.launcher.core.LauncherPhase.getDiscoveryIssueFailurePhase; import static org.junit.platform.launcher.core.ListenerRegistry.forEngineExecutionListeners; import java.util.Optional; @@ -186,10 +186,9 @@ private static EngineExecutionListener selectExecutionListener(EngineExecutionLi private void failOrExecuteEngine(LauncherDiscoveryResult discoveryResult, EngineExecutionListener listener, TestEngine testEngine, NamespacedHierarchicalStore requestLevelStore) { EngineResultInfo engineDiscoveryResult = discoveryResult.getEngineResult(testEngine); - DiscoveryIssueNotifier discoveryIssueNotifier = handleDiscoveryIssuesInDiscoveryPhase( - discoveryResult.getConfigurationParameters()) // - ? DiscoveryIssueNotifier.NO_ISSUES // - : engineDiscoveryResult.getDiscoveryIssueNotifier(); + DiscoveryIssueNotifier discoveryIssueNotifier = shouldReportDiscoveryIssues(discoveryResult) // + ? engineDiscoveryResult.getDiscoveryIssueNotifier() // + : DiscoveryIssueNotifier.NO_ISSUES; TestDescriptor engineDescriptor = engineDiscoveryResult.getRootDescriptor(); Throwable failure = engineDiscoveryResult.getCause() // .orElseGet(() -> discoveryIssueNotifier.createExceptionForCriticalIssues(testEngine)); @@ -207,6 +206,12 @@ private void failOrExecuteEngine(LauncherDiscoveryResult discoveryResult, Engine } } + private static boolean shouldReportDiscoveryIssues(LauncherDiscoveryResult discoveryResult) { + ConfigurationParameters configurationParameters = discoveryResult.getConfigurationParameters(); + return getDiscoveryIssueFailurePhase(configurationParameters).orElse( + LauncherPhase.EXECUTION) == LauncherPhase.EXECUTION; + } + private ListenerRegistry buildListenerRegistryForExecution( TestExecutionListener... listeners) { if (listeners.length == 0) { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherPhase.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherPhase.java new file mode 100644 index 000000000000..107bcf971c94 --- /dev/null +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherPhase.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static org.junit.platform.launcher.LauncherConstants.DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME; + +import java.util.Locale; +import java.util.Optional; + +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.engine.ConfigurationParameters; + +/** + * The phase the {@link org.junit.platform.launcher.Launcher} is in. + * + * @since 1.13 + */ +enum LauncherPhase { + + DISCOVERY, EXECUTION; + + private static final Logger logger = LoggerFactory.getLogger(LauncherPhase.class); + + static Optional getDiscoveryIssueFailurePhase(ConfigurationParameters configurationParameters) { + return configurationParameters.get(DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME, value -> { + try { + return LauncherPhase.valueOf(value.toUpperCase(Locale.ROOT)); + } + catch (Exception e) { + logger.warn( + () -> String.format("Ignoring invalid LauncherPhase '%s' set via the '%s' configuration parameter.", + value, DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME)); + return null; + } + }); + } + + @Override + public String toString() { + return name().toLowerCase(Locale.ENGLISH); + } +} diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java index 6e3bf311ccfb..640d90f984e1 100644 --- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java @@ -23,7 +23,6 @@ import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.core.EngineDiscoveryOrchestrator; -import org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase; import org.junit.platform.launcher.core.EngineExecutionOrchestrator; import org.junit.platform.launcher.core.LauncherDiscoveryResult; import org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry; @@ -56,7 +55,7 @@ private boolean hasTestEngineOtherThanSuiteEngine(Set testEngines) { } LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, UniqueId parentId) { - return discoveryOrchestrator.discover(discoveryRequest, Phase.DISCOVERY, parentId); + return discoveryOrchestrator.discover(discoveryRequest, parentId); } TestExecutionSummary execute(LauncherDiscoveryResult discoveryResult, diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java index 9f7f3e18501a..37398b4efd3e 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java @@ -17,8 +17,6 @@ import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import static org.junit.platform.engine.support.store.NamespacedHierarchicalStore.CloseAction.closeAutoCloseables; -import static org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase.DISCOVERY; -import static org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase.EXECUTION; import java.nio.file.Path; import java.util.List; @@ -190,7 +188,7 @@ public static EngineDiscoveryResults discover(String engineId, LauncherDiscovery public static EngineDiscoveryResults discover(TestEngine testEngine, LauncherDiscoveryRequest discoveryRequest) { Preconditions.notNull(testEngine, "TestEngine must not be null"); Preconditions.notNull(discoveryRequest, "EngineDiscoveryRequest must not be null"); - LauncherDiscoveryResult discoveryResult = discover(testEngine, discoveryRequest, DISCOVERY); + LauncherDiscoveryResult discoveryResult = discoverUsingOrchestrator(testEngine, discoveryRequest); TestDescriptor engineDescriptor = discoveryResult.getEngineTestDescriptor(testEngine); List discoveryIssues = discoveryResult.getDiscoveryIssues(testEngine); return new EngineDiscoveryResults(engineDescriptor, discoveryIssues); @@ -337,7 +335,7 @@ private static void executeDirectly(TestEngine testEngine, EngineDiscoveryReques private static void executeUsingLauncherOrchestration(TestEngine testEngine, LauncherDiscoveryRequest discoveryRequest, EngineExecutionListener listener) { - LauncherDiscoveryResult discoveryResult = discover(testEngine, discoveryRequest, EXECUTION); + LauncherDiscoveryResult discoveryResult = discoverUsingOrchestrator(testEngine, discoveryRequest); TestDescriptor engineTestDescriptor = discoveryResult.getEngineTestDescriptor(testEngine); Preconditions.notNull(engineTestDescriptor, "TestEngine did not yield a TestDescriptor"); withRequestLevelStore(store -> new EngineExecutionOrchestrator().execute(discoveryResult, listener, store)); @@ -354,10 +352,10 @@ private static NamespacedHierarchicalStore newStore(NamespacedHierarc return new NamespacedHierarchicalStore<>(parentStore, closeAutoCloseables()); } - private static LauncherDiscoveryResult discover(TestEngine testEngine, LauncherDiscoveryRequest discoveryRequest, - EngineDiscoveryOrchestrator.Phase phase) { + private static LauncherDiscoveryResult discoverUsingOrchestrator(TestEngine testEngine, + LauncherDiscoveryRequest discoveryRequest) { return new EngineDiscoveryOrchestrator(singleton(testEngine), emptySet()) // - .discover(discoveryRequest, phase); + .discover(discoveryRequest); } @SuppressWarnings("unchecked") diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java index 9dbb53a0c8b7..d03bc43c3a0d 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java @@ -25,6 +25,7 @@ import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.fakes.FaultyTestEngines.createEngineThatCannotResolveAnything; import static org.junit.platform.fakes.FaultyTestEngines.createEngineThatFailsToResolveAnything; +import static org.junit.platform.launcher.LauncherConstants.DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.DRY_RUN_PROPERTY_NAME; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; @@ -1018,6 +1019,7 @@ private static ReportedData execute(TestEngine engine, UnaryOperator Date: Thu, 15 May 2025 12:28:08 +0200 Subject: [PATCH 39/42] Revert "Disable Predictive Test Selection for now to surface discovery issues" This reverts commit b816f69ba9b68f1657cea63c93f4af203758e2d7. --- .github/actions/run-gradle/action.yml | 2 ++ gradle/plugins/build-parameters/build.gradle.kts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/actions/run-gradle/action.yml b/.github/actions/run-gradle/action.yml index 16a1211555af..7506f777a7ce 100644 --- a/.github/actions/run-gradle/action.yml +++ b/.github/actions/run-gradle/action.yml @@ -26,6 +26,8 @@ runs: run: | ./gradlew \ -Porg.gradle.java.installations.auto-download=false \ + -Pjunit.develocity.predictiveTestSelection.enabled=true \ + -Pjunit.develocity.predictiveTestSelection.selectRemainingTests=${{ github.event_name != 'pull_request' }} \ "-Dscan.value.GitHub job=${{ github.job }}" \ javaToolchains \ ${{ inputs.arguments }} diff --git a/gradle/plugins/build-parameters/build.gradle.kts b/gradle/plugins/build-parameters/build.gradle.kts index bed7fe291200..8a1276886d14 100644 --- a/gradle/plugins/build-parameters/build.gradle.kts +++ b/gradle/plugins/build-parameters/build.gradle.kts @@ -39,7 +39,7 @@ buildParameters { group("predictiveTestSelection") { bool("enabled") { description = "Whether or not to use Predictive Test Selection for selecting tests to execute" - defaultValue = false + defaultValue = true } bool("selectRemainingTests") { // see https://docs.gradle.com/develocity/predictive-test-selection/#gradle-selection-mode From ed159562f2728a93667cb20900e7eafc4f74b505 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 16 May 2025 16:55:24 +0200 Subject: [PATCH 40/42] Fix dry-run checks --- .github/workflows/release.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6efef3ca75e0..0e2fbf7803de 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,7 +62,7 @@ jobs: :verifyArtifactsInStagingRepositoryAreReproducible \ --remote-repo-url=${{ env.STAGING_REPO_URL }} - name: Generate build provenance attestations - if: ${{ inputs.dryRun == 'false' }} + if: ${{ inputs.dryRun == false }} uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0 with: subject-path: build/repo/**/*.jar @@ -119,7 +119,7 @@ jobs: issues: write steps: - name: Close GitHub milestone - if: ${{ inputs.dryRun == 'false' }} + if: ${{ inputs.dryRun == false }} uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 with: result-encoding: string @@ -157,7 +157,7 @@ jobs: fetch-depth: 1 ref: "refs/tags/${{ env.RELEASE_TAG }}" - name: Release staging repository - if: ${{ inputs.dryRun == 'false' }} + if: ${{ inputs.dryRun == false }} uses: ./.github/actions/run-gradle env: ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} @@ -198,7 +198,7 @@ jobs: gitPublishCopy \ -Pdocumentation.replaceCurrentDocs=${{ contains(github.event.inputs.releaseVersion, '-') && 'false' || 'true' }} - name: Publish documentation - if: ${{ inputs.dryRun == 'false' }} + if: ${{ inputs.dryRun == false }} uses: ./.github/actions/run-gradle env: GIT_USERNAME: git @@ -211,7 +211,7 @@ jobs: gitPublishPush \ -Pdocumentation.replaceCurrentDocs=${{ contains(github.event.inputs.releaseVersion, '-') && 'false' || 'true' }} - name: Wait for deployment to GitHub Pages - if: ${{ inputs.dryRun == 'false' }} + if: ${{ inputs.dryRun == false }} id: pagesDeployment timeout-minutes: 20 run: | @@ -219,7 +219,7 @@ jobs: ./.github/scripts/waitForUrl.sh "$URL" echo "pdfUrl=$URL" >> "$GITHUB_OUTPUT" - name: Verify integrity of PDF version of User Guide - if: ${{ inputs.dryRun == 'false' }} + if: ${{ inputs.dryRun == false }} run: | curl --silent --fail --location --output /tmp/junit-user-guide.pdf "${{ steps.pagesDeployment.outputs.pdfUrl }}" pdfinfo /tmp/junit-user-guide.pdf @@ -240,7 +240,7 @@ jobs: name: local-maven-repository path: build/repo - name: Wait for sync to Maven Central - if: ${{ inputs.dryRun == 'false' }} + if: ${{ inputs.dryRun == false }} timeout-minutes: 30 run: | find build/repo -name '*.pom' -printf './.github/scripts/waitForMavenCentralSync.sh %P\n' | sh @@ -265,7 +265,7 @@ jobs: - name: Update JUnit dependencies in samples run: java src/Updater.java ${{ github.event.inputs.releaseVersion }} - name: Build samples - if: ${{ inputs.dryRun == 'false' }} + if: ${{ inputs.dryRun == false }} run: java src/Builder.java - name: Create release branch run: | @@ -275,11 +275,11 @@ jobs: git status git commit -a -m "Use ${{ github.event.inputs.releaseVersion }}" - name: Push release branch - if: ${{ inputs.dryRun == 'false' }} + if: ${{ inputs.dryRun == false }} run: | git push origin "${{ env.RELEASE_TAG }}" - name: Update main branch (only for GA releases) - if: ${{ inputs.dryRun == 'false' && !contains(github.event.inputs.releaseVersion, '-') }} + if: ${{ inputs.dryRun == false && !contains(github.event.inputs.releaseVersion, '-') }} run: | git switch main git merge --ff-only "${{ env.RELEASE_TAG }}" @@ -287,7 +287,7 @@ jobs: create_github_release: name: Create GitHub release - if: ${{ inputs.dryRun == 'false' }} + if: ${{ inputs.dryRun == false }} needs: wait_for_maven_central runs-on: ubuntu-latest permissions: From 905cd70782b7cd795cd91db832f1ed8c2c60fe3c Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 16 May 2025 19:17:20 +0200 Subject: [PATCH 41/42] Pass JReleaser config as env vars --- .github/workflows/release.yml | 7 +++---- .../kotlin/junitbuild.maven-central-publishing.gradle.kts | 6 ++++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0e2fbf7803de..6988b6e873b6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -162,12 +162,11 @@ jobs: env: ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + JRELEASER_MAVENCENTRAL_STAGE: PUBLISH + JRELEASER_MAVENCENTRAL_DEPLOYMENT_ID: ${{ inputs.deploymentId }} with: encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - arguments: | - jreleaserDeploy \ - -Pjreleaser.mavencentral.stage=PUBLISH \ - -Pjreleaser.mavencentral.deployment.id=${{ github.event.inputs.deploymentId }} + arguments: jreleaserDeploy publish_documentation: name: Publish documentation diff --git a/gradle/plugins/publishing/src/main/kotlin/junitbuild.maven-central-publishing.gradle.kts b/gradle/plugins/publishing/src/main/kotlin/junitbuild.maven-central-publishing.gradle.kts index 2b2c08f4ea9a..0ebec70fa49b 100644 --- a/gradle/plugins/publishing/src/main/kotlin/junitbuild.maven-central-publishing.gradle.kts +++ b/gradle/plugins/publishing/src/main/kotlin/junitbuild.maven-central-publishing.gradle.kts @@ -38,12 +38,14 @@ jreleaser { username = mavenCentralUsername password = mavenCentralPassword stagingRepository(tempRepoDir.absolutePath) - applyMavenCentralRules = true + applyMavenCentralRules = false + sourceJar = false + javadocJar = false sign = false checksums = false verifyPom = false namespace = "org.junit" - stage = providers.gradleProperty("jreleaser.mavencentral.stage") + stage = providers.environmentVariable("JRELEASER_MAVENCENTRAL_STAGE") .map(Stage::of) .orElse(Stage.UPLOAD) } From 2c24bc349312a293dcecc23805ce6605a3a20c54 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 16 May 2025 16:35:18 +0200 Subject: [PATCH 42/42] Release 5.13.0-RC1 --- README.md | 2 +- .../release-notes-5.13.0-RC1.adoc | 37 ++----------------- gradle.properties | 6 +-- 3 files changed, 7 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index d2378544982e..20f691ec93f5 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ This repository is the home of _JUnit 5_. ## Latest Releases - General Availability (GA): [JUnit 5.12.2](https://github.com/junit-team/junit5/releases/tag/r5.12.2) (April 11, 2025) -- Preview (Milestone/Release Candidate): [JUnit 5.13.0-M3](https://github.com/junit-team/junit5/releases/tag/r5.13.0-M3) (May 2, 2025) +- Preview (Milestone/Release Candidate): [JUnit 5.13.0-RC1](https://github.com/junit-team/junit5/releases/tag/r5.13.0-RC1) (May 16, 2025) ## Documentation diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-RC1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-RC1.adoc index 5be59b89bdbe..2cac539083b9 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-RC1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-RC1.adoc @@ -3,7 +3,7 @@ *Date of Release:* May 16, 2025 -*Scope:* ❓ +*Scope:* Minor bug fixes and enhancements since 5.13.0-M1 For a complete list of all _closed_ issues and pull requests for this release, consult the link:{junit5-repo}+/milestone/96?closed=1+[5.13.0-RC1] milestone page in the JUnit @@ -13,20 +13,7 @@ repository on GitHub. [[release-notes-5.13.0-RC1-junit-platform]] === JUnit Platform -[[release-notes-5.13.0-RC1-junit-platform-bug-fixes]] -==== Bug Fixes - -* ❓ - -[[release-notes-5.13.0-RC1-junit-platform-deprecations-and-breaking-changes]] -==== Deprecations and Breaking Changes - -* ❓ - -[[release-notes-5.13.0-RC1-junit-platform-new-features-and-improvements]] -==== New Features and Improvements - -* ❓ +No changes. [[release-notes-5.13.0-RC1-junit-jupiter]] @@ -37,11 +24,6 @@ repository on GitHub. * Validate _all_ versions specified in `@EnabledOnJre` and `@DisabledOnJre` annotations. -[[release-notes-5.13.0-RC1-junit-jupiter-deprecations-and-breaking-changes]] -==== Deprecations and Breaking Changes - -* ❓ - [[release-notes-5.13.0-RC1-junit-jupiter-new-features-and-improvements]] ==== New Features and Improvements @@ -52,17 +34,4 @@ repository on GitHub. [[release-notes-5.13.0-RC1-junit-vintage]] === JUnit Vintage -[[release-notes-5.13.0-RC1-junit-vintage-bug-fixes]] -==== Bug Fixes - -* ❓ - -[[release-notes-5.13.0-RC1-junit-vintage-deprecations-and-breaking-changes]] -==== Deprecations and Breaking Changes - -* ❓ - -[[release-notes-5.13.0-RC1-junit-vintage-new-features-and-improvements]] -==== New Features and Improvements - -* ❓ +No changes. diff --git a/gradle.properties b/gradle.properties index 1f57336a6a6a..6a96f24ce567 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,13 +1,13 @@ group = org.junit -version = 5.13.0-SNAPSHOT +version = 5.13.0-RC1 jupiterGroup = org.junit.jupiter platformGroup = org.junit.platform -platformVersion = 1.13.0-SNAPSHOT +platformVersion = 1.13.0-RC1 vintageGroup = org.junit.vintage -vintageVersion = 5.13.0-SNAPSHOT +vintageVersion = 5.13.0-RC1 # We need more metaspace due to apparent memory leak in Asciidoctor/JRuby org.gradle.jvmargs=-Xmx1g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError