From 827df2bb3765863ad2c7619a35d1483cf0927f35 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 30 Jul 2026 22:54:55 +0200 Subject: [PATCH 001/117] Setup project skeleton --- build.gradle.kts | 2 ++ .../junit-jupiter-api.gradle.kts | 2 ++ .../src/main/java/module-info.java | 1 + .../java/org/junit/jupiter/api/Constants.java | 2 ++ ...unit-platform-configuration-api.gradle.kts | 12 +++++++++ .../src/main/java/module-info.java | 16 +++++++++++ .../api/ConfigurationProperty.java | 26 ++++++++++++++++++ .../configuration/api/package-info.java | 14 ++++++++++ ...latform-configuration-processor.gradle.kts | 13 +++++++++ .../src/main/java/module-info.java | 19 +++++++++++++ ...figurationMetadataAnnotationProcessor.java | 27 +++++++++++++++++++ .../configuration/processor/package-info.java | 14 ++++++++++ .../gradle/incremental.annotation.processors | 1 + .../javax.annotation.processing.Processor | 1 + .../core/ClasspathAlignmentChecker.java | 2 ++ .../junit-jupiter-api.expected.txt | 1 + ...it-platform-configuration-api.expected.txt | 5 ++++ ...tform-configuration-processor.expected.txt | 8 ++++++ .../platform/tooling/support/HelperTests.java | 2 ++ 19 files changed, 168 insertions(+) create mode 100644 junit-platform-configuration-api/junit-platform-configuration-api.gradle.kts create mode 100644 junit-platform-configuration-api/src/main/java/module-info.java create mode 100644 junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationProperty.java create mode 100644 junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/package-info.java create mode 100644 junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts create mode 100644 junit-platform-configuration-processor/src/main/java/module-info.java create mode 100644 junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java create mode 100644 junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/package-info.java create mode 100644 junit-platform-configuration-processor/src/main/resources/META-INF/gradle/incremental.annotation.processors create mode 100644 junit-platform-configuration-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor create mode 100644 platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-api.expected.txt create mode 100644 platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt diff --git a/build.gradle.kts b/build.gradle.kts index 70998a0407fc..6bd224f787eb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,6 +18,8 @@ extra["license"] = License( val mavenizedProjects = listOf( projects.junitStart, projects.junitPlatformCommons, + projects.junitPlatformConfigurationApi, + projects.junitPlatformConfigurationProcessor, projects.junitPlatformConsole, projects.junitPlatformConsoleStandalone, projects.junitPlatformEngine, diff --git a/junit-jupiter-api/junit-jupiter-api.gradle.kts b/junit-jupiter-api/junit-jupiter-api.gradle.kts index 4fe91a3dcf42..a561d6ad9d40 100644 --- a/junit-jupiter-api/junit-jupiter-api.gradle.kts +++ b/junit-jupiter-api/junit-jupiter-api.gradle.kts @@ -15,6 +15,8 @@ dependencies { compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) + // TODO: Marc, why does compileOnlyApi break the Javadoc task? + api(projects.junitPlatformConfigurationApi) compileOnly(kotlin("stdlib")) diff --git a/junit-jupiter-api/src/main/java/module-info.java b/junit-jupiter-api/src/main/java/module-info.java index eb0df11c066b..e59a765b49f6 100644 --- a/junit-jupiter-api/src/main/java/module-info.java +++ b/junit-jupiter-api/src/main/java/module-info.java @@ -17,6 +17,7 @@ requires static transitive org.apiguardian.api; requires static transitive org.jspecify; + requires static transitive org.junit.platform.configuration.api; requires transitive org.junit.platform.commons; requires transitive org.opentest4j; diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java index f1c38975ed51..54ac6abffeef 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.parallel.Execution; +import org.junit.platform.configuration.api.ConfigurationProperty; /** * Collection of configuration constants for the Jupiter test engine. @@ -64,6 +65,7 @@ public final class Constants { * *

Note: A class that matches both an inclusion and exclusion pattern will be excluded. */ + @ConfigurationProperty public static final String EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.include"; /** diff --git a/junit-platform-configuration-api/junit-platform-configuration-api.gradle.kts b/junit-platform-configuration-api/junit-platform-configuration-api.gradle.kts new file mode 100644 index 000000000000..498e82524ec2 --- /dev/null +++ b/junit-platform-configuration-api/junit-platform-configuration-api.gradle.kts @@ -0,0 +1,12 @@ +plugins { + id("junitbuild.java-library-conventions") +} + +description = "JUnit Platform Configuration API" + +dependencies { + api(platform(projects.junitBom)) + + compileOnlyApi(libs.apiguardian) + compileOnlyApi(libs.jspecify) +} diff --git a/junit-platform-configuration-api/src/main/java/module-info.java b/junit-platform-configuration-api/src/main/java/module-info.java new file mode 100644 index 000000000000..f1b0d54fd84f --- /dev/null +++ b/junit-platform-configuration-api/src/main/java/module-info.java @@ -0,0 +1,16 @@ +/* + * Copyright 2015-2026 the original author or 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 + */ + +module org.junit.platform.configuration.api { + requires static transitive org.apiguardian.api; + requires static transitive org.jspecify; + + exports org.junit.platform.configuration.api; +} diff --git a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationProperty.java b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationProperty.java new file mode 100644 index 000000000000..d4472aacdf6e --- /dev/null +++ b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationProperty.java @@ -0,0 +1,26 @@ +/* + * Copyright 2026 the original author or 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.configuration.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.FIELD) +@API(status = STABLE, since = "1.10") +public @interface ConfigurationProperty { +} diff --git a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/package-info.java b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/package-info.java new file mode 100644 index 000000000000..4c97e5c75569 --- /dev/null +++ b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/package-info.java @@ -0,0 +1,14 @@ +/* + * Copyright 2012 the original author or 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 + */ + +@NullMarked +package org.junit.platform.configuration.api; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts b/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts new file mode 100644 index 000000000000..a492849abeea --- /dev/null +++ b/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id("junitbuild.java-library-conventions") +} + +description = "JUnit Platform Configuration Processor" + +dependencies { + api(platform(projects.junitBom)) + api(projects.junitPlatformConfigurationApi) + + compileOnlyApi(libs.apiguardian) + compileOnlyApi(libs.jspecify) +} diff --git a/junit-platform-configuration-processor/src/main/java/module-info.java b/junit-platform-configuration-processor/src/main/java/module-info.java new file mode 100644 index 000000000000..8832b385bbb9 --- /dev/null +++ b/junit-platform-configuration-processor/src/main/java/module-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2015-2026 the original author or 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 + */ + +module org.junit.platform.configuration.processor { + requires static transitive org.apiguardian.api; + requires static transitive org.jspecify; + + requires java.compiler; + requires org.junit.platform.configuration.api; + + provides javax.annotation.processing.Processor with org.junit.platform.configuration.processor.ConfigurationMetadataAnnotationProcessor; +} diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java new file mode 100644 index 000000000000..75dc8db1901e --- /dev/null +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -0,0 +1,27 @@ +/* + * Copyright 2026 the original author or 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.configuration.processor; + +import java.util.Set; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.TypeElement; + +import org.apiguardian.api.API; + +@API(status = API.Status.EXPERIMENTAL) +public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor { + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + return false; + } +} diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/package-info.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/package-info.java new file mode 100644 index 000000000000..77966dcca659 --- /dev/null +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/package-info.java @@ -0,0 +1,14 @@ +/* + * Copyright 2012 the original author or 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 + */ + +@NullMarked +package org.junit.platform.configuration.processor; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-configuration-processor/src/main/resources/META-INF/gradle/incremental.annotation.processors b/junit-platform-configuration-processor/src/main/resources/META-INF/gradle/incremental.annotation.processors new file mode 100644 index 000000000000..3019ff07037d --- /dev/null +++ b/junit-platform-configuration-processor/src/main/resources/META-INF/gradle/incremental.annotation.processors @@ -0,0 +1 @@ +org.junit.platform.configuration.processor.ConfigurationMetadataAnnotationProcessor,aggregating diff --git a/junit-platform-configuration-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/junit-platform-configuration-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 000000000000..c034bf8213af --- /dev/null +++ b/junit-platform-configuration-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +org.junit.platform.configuration.processor.ConfigurationMetadataAnnotationProcessor diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ClasspathAlignmentChecker.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ClasspathAlignmentChecker.java index 4ddc74c8128c..14efcc0ada2f 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ClasspathAlignmentChecker.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ClasspathAlignmentChecker.java @@ -37,6 +37,8 @@ class ClasspathAlignmentChecker { "org.junit.jupiter.params", // "org.junit.platform.commons", // "org.junit.platform.console", // + "org.junit.platform.configuration.api", // + "org.junit.platform.configuration.processor", // "org.junit.platform.engine", // "org.junit.platform.launcher", // "org.junit.platform.reporting", // diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt index 50e4a7c4f920..01714c5c4512 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt @@ -12,6 +12,7 @@ requires kotlin.stdlib static requires org.apiguardian.api static transitive requires org.jspecify static transitive requires org.junit.platform.commons transitive +requires org.junit.platform.configuration.api static transitive requires org.opentest4j transitive qualified exports org.junit.jupiter.api.timeout to org.junit.jupiter.engine qualified opens org.junit.jupiter.api.condition to org.junit.platform.commons diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-api.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-api.expected.txt new file mode 100644 index 000000000000..4ccd09ba45ce --- /dev/null +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-api.expected.txt @@ -0,0 +1,5 @@ +org.junit.platform.configuration.api@6.2.0-SNAPSHOT jar:file:///home/mpkorstanje/Projects/github/junit-team/junit-framework/platform-tooling-support-tests/build/normalized-repo/org/junit/platform/junit-platform-configuration-api/6.2.0-SNAPSHOT/junit-platform-configuration-api-6.2.0-SNAPSHOT.jar!/module-info.class +exports org.junit.platform.configuration.api +requires java.base mandated +requires org.apiguardian.api static transitive +requires org.jspecify static transitive diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt new file mode 100644 index 000000000000..3b6bbe89054d --- /dev/null +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt @@ -0,0 +1,8 @@ +org.junit.platform.configuration.processor@6.2.0-SNAPSHOT jar:file:///home/mpkorstanje/Projects/github/junit-team/junit-framework/platform-tooling-support-tests/build/normalized-repo/org/junit/platform/junit-platform-configuration-processor/6.2.0-SNAPSHOT/junit-platform-configuration-processor-6.2.0-SNAPSHOT.jar!/module-info.class +requires java.base mandated +requires java.compiler +requires org.apiguardian.api static transitive +requires org.jspecify static transitive +requires org.junit.platform.configuration.api +provides javax.annotation.processing.Processor with org.junit.platform.configuration.processor.ConfigurationMetadataAnnotationProcessor +contains org.junit.platform.configuration.processor \ No newline at end of file diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/HelperTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/HelperTests.java index 54ad44834707..9a6ffdb0bb0c 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/HelperTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/HelperTests.java @@ -38,6 +38,8 @@ void loadModuleDirectoryNames() { "junit-jupiter-params", // "junit-start", // "junit-platform-commons", // + "junit-platform-configuration-api", // + "junit-platform-configuration-processor", // "junit-platform-console", // "junit-platform-engine", // "junit-platform-launcher", // From ed2e1eedcc99ff4dca1685b9693b7b8dded968ce Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 30 Jul 2026 23:07:47 +0200 Subject: [PATCH 002/117] Enable annotation processor for ConfigurationParameter --- junit-jupiter-api/junit-jupiter-api.gradle.kts | 4 +++- .../processor/ConfigurationMetadataAnnotationProcessor.java | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/junit-jupiter-api/junit-jupiter-api.gradle.kts b/junit-jupiter-api/junit-jupiter-api.gradle.kts index a561d6ad9d40..5dce4bbc02fa 100644 --- a/junit-jupiter-api/junit-jupiter-api.gradle.kts +++ b/junit-jupiter-api/junit-jupiter-api.gradle.kts @@ -9,6 +9,8 @@ plugins { description = "JUnit Jupiter API" dependencies { + annotationProcessor(projects.junitPlatformConfigurationProcessor) + api(platform(projects.junitBom)) api(libs.opentest4j) api(projects.junitPlatformCommons) @@ -37,7 +39,7 @@ eclipseConventions { tasks { compileJava { - options.compilerArgs.add("-Xlint:-module") // due to qualified exports + options.compilerArgs.add("-Xlint:-module,-processing") // due to qualified exports, not all annotations need to be processed } jar { bundle { diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index 75dc8db1901e..690c84c49351 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -14,11 +14,16 @@ import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; import javax.lang.model.element.TypeElement; import org.apiguardian.api.API; @API(status = API.Status.EXPERIMENTAL) +@SupportedSourceVersion(SourceVersion.RELEASE_17) +@SupportedAnnotationTypes("org.junit.platform.suite.api.ConfigurationParameter") public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor { @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { From 14ffed5c1c500cd0cd2aa879e3ce8c88c37dec8f Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 30 Jul 2026 23:37:49 +0200 Subject: [PATCH 003/117] Output minimal junit-platform-configuration-metadata.json --- ...nitbuild.backward-compatibility.gradle.kts | 7 +++-- .../junitbuild.osgi-conventions.gradle.kts | 7 +++-- ...unit-platform-configuration-api.gradle.kts | 1 - .../src/main/java/module-info.java | 1 - .../api/ConfigurationProperty.java | 5 --- ...latform-configuration-processor.gradle.kts | 1 - .../src/main/java/module-info.java | 1 - ...figurationMetadataAnnotationProcessor.java | 31 +++++++++++++++++-- ...it-platform-configuration-api.expected.txt | 1 - ...tform-configuration-processor.expected.txt | 3 +- .../tooling/support/tests/ArchUnitTests.java | 30 +++++++----------- 11 files changed, 49 insertions(+), 39 deletions(-) diff --git a/gradle/plugins/backward-compatibility/src/main/kotlin/junitbuild.backward-compatibility.gradle.kts b/gradle/plugins/backward-compatibility/src/main/kotlin/junitbuild.backward-compatibility.gradle.kts index cc14ed07d664..21a084afc970 100644 --- a/gradle/plugins/backward-compatibility/src/main/kotlin/junitbuild.backward-compatibility.gradle.kts +++ b/gradle/plugins/backward-compatibility/src/main/kotlin/junitbuild.backward-compatibility.gradle.kts @@ -89,6 +89,7 @@ val checkBackwardCompatibility = tasks.register("checkBackwardCompatibility") { dependsOn(roseau) } -tasks.check { - dependsOn(checkBackwardCompatibility) -} +// TODO: Breaks because the configuration-api hasn't been released yet. +//tasks.check { +// dependsOn(checkBackwardCompatibility) +//} diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts index c3441bb5df1d..2e0c1947c9be 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts @@ -125,6 +125,7 @@ val verifyOSGi = tasks.register("verifyOSGi", Resolve::class) { properties.empty() } -tasks.check { - dependsOn(verifyOSGi) -} +// TODO: Fails, for some reason +//tasks.check { +// dependsOn(verifyOSGi) +//} diff --git a/junit-platform-configuration-api/junit-platform-configuration-api.gradle.kts b/junit-platform-configuration-api/junit-platform-configuration-api.gradle.kts index 498e82524ec2..3b76d6fc66fa 100644 --- a/junit-platform-configuration-api/junit-platform-configuration-api.gradle.kts +++ b/junit-platform-configuration-api/junit-platform-configuration-api.gradle.kts @@ -7,6 +7,5 @@ description = "JUnit Platform Configuration API" dependencies { api(platform(projects.junitBom)) - compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) } diff --git a/junit-platform-configuration-api/src/main/java/module-info.java b/junit-platform-configuration-api/src/main/java/module-info.java index f1b0d54fd84f..6692bc571702 100644 --- a/junit-platform-configuration-api/src/main/java/module-info.java +++ b/junit-platform-configuration-api/src/main/java/module-info.java @@ -9,7 +9,6 @@ */ module org.junit.platform.configuration.api { - requires static transitive org.apiguardian.api; requires static transitive org.jspecify; exports org.junit.platform.configuration.api; diff --git a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationProperty.java b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationProperty.java index d4472aacdf6e..ea5a0c827fd7 100644 --- a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationProperty.java +++ b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationProperty.java @@ -10,17 +10,12 @@ package org.junit.platform.configuration.api; -import static org.apiguardian.api.API.Status.STABLE; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.apiguardian.api.API; - @Retention(RetentionPolicy.SOURCE) @Target(ElementType.FIELD) -@API(status = STABLE, since = "1.10") public @interface ConfigurationProperty { } diff --git a/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts b/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts index a492849abeea..066c0bd9d90d 100644 --- a/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts +++ b/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts @@ -8,6 +8,5 @@ dependencies { api(platform(projects.junitBom)) api(projects.junitPlatformConfigurationApi) - compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) } diff --git a/junit-platform-configuration-processor/src/main/java/module-info.java b/junit-platform-configuration-processor/src/main/java/module-info.java index 8832b385bbb9..f211842906c3 100644 --- a/junit-platform-configuration-processor/src/main/java/module-info.java +++ b/junit-platform-configuration-processor/src/main/java/module-info.java @@ -9,7 +9,6 @@ */ module org.junit.platform.configuration.processor { - requires static transitive org.apiguardian.api; requires static transitive org.jspecify; requires java.compiler; diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index 690c84c49351..3dba8fc1fb04 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -10,23 +10,48 @@ package org.junit.platform.configuration.processor; +import static java.util.Objects.requireNonNull; + +import java.nio.charset.StandardCharsets; import java.util.Set; import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.TypeElement; +import javax.tools.StandardLocation; -import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; -@API(status = API.Status.EXPERIMENTAL) @SupportedSourceVersion(SourceVersion.RELEASE_17) -@SupportedAnnotationTypes("org.junit.platform.suite.api.ConfigurationParameter") +@SupportedAnnotationTypes("org.junit.platform.configuration.api.ConfigurationProperty") public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor { + private static final String METADATA_PATH = "META-INF/junit-platform-configuration-metadata.json"; + private @Nullable ProcessingEnvironment environment; + + @Override + public synchronized void init(ProcessingEnvironment environment) { + super.init(environment); + this.environment = environment; + } + @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { + if (roundEnv.processingOver()) { + try { + var resource = requireNonNull(environment).getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", + METADATA_PATH); + try (var out = resource.openOutputStream()) { + out.write("{}".getBytes(StandardCharsets.UTF_8)); + } + } + catch (Exception ex) { + throw new IllegalStateException("Failed to write metadata", ex); + } + } return false; } } diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-api.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-api.expected.txt index 4ccd09ba45ce..f0c9ea97e404 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-api.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-api.expected.txt @@ -1,5 +1,4 @@ org.junit.platform.configuration.api@6.2.0-SNAPSHOT jar:file:///home/mpkorstanje/Projects/github/junit-team/junit-framework/platform-tooling-support-tests/build/normalized-repo/org/junit/platform/junit-platform-configuration-api/6.2.0-SNAPSHOT/junit-platform-configuration-api-6.2.0-SNAPSHOT.jar!/module-info.class exports org.junit.platform.configuration.api requires java.base mandated -requires org.apiguardian.api static transitive requires org.jspecify static transitive diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt index 3b6bbe89054d..571f2bbe632a 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt @@ -1,8 +1,7 @@ org.junit.platform.configuration.processor@6.2.0-SNAPSHOT jar:file:///home/mpkorstanje/Projects/github/junit-team/junit-framework/platform-tooling-support-tests/build/normalized-repo/org/junit/platform/junit-platform-configuration-processor/6.2.0-SNAPSHOT/junit-platform-configuration-processor-6.2.0-SNAPSHOT.jar!/module-info.class requires java.base mandated requires java.compiler -requires org.apiguardian.api static transitive requires org.jspecify static transitive requires org.junit.platform.configuration.api provides javax.annotation.processing.Processor with org.junit.platform.configuration.processor.ConfigurationMetadataAnnotationProcessor -contains org.junit.platform.configuration.processor \ No newline at end of file +contains org.junit.platform.configuration.processor diff --git a/platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java b/platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java index 026711669ebc..b9617eac46ae 100644 --- a/platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java +++ b/platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java @@ -13,21 +13,14 @@ import static com.tngtech.archunit.base.DescribedPredicate.and; import static com.tngtech.archunit.base.DescribedPredicate.describe; import static com.tngtech.archunit.base.DescribedPredicate.not; -import static com.tngtech.archunit.core.domain.JavaClass.Predicates.ANONYMOUS_CLASSES; -import static com.tngtech.archunit.core.domain.JavaClass.Predicates.TOP_LEVEL_CLASSES; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage; -import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleName; -import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleNameEndingWith; import static com.tngtech.archunit.core.domain.JavaMember.Predicates.declaredIn; -import static com.tngtech.archunit.core.domain.JavaModifier.PUBLIC; import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Predicates.annotatedWith; -import static com.tngtech.archunit.core.domain.properties.HasModifiers.Predicates.modifier; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameContaining; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameStartingWith; import static com.tngtech.archunit.lang.conditions.ArchConditions.onlyBeAccessedByClassesThat; import static com.tngtech.archunit.lang.conditions.ArchPredicates.are; -import static com.tngtech.archunit.lang.conditions.ArchPredicates.have; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.members; import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; @@ -74,17 +67,18 @@ class ArchUnitTests { private final ArchRule allClassesAreInJUnitPackage = classes() // .should().haveNameMatching("org\\.junit\\..+"); - @SuppressWarnings("unused") - @ArchTest - private final ArchRule allPublicTopLevelTypesHaveApiAnnotations = classes() // - .that(have(modifier(PUBLIC))) // - .and(TOP_LEVEL_CLASSES) // - .and(not(ANONYMOUS_CLASSES)) // - .and(not(describe("are Kotlin SAM type implementations", simpleName("")))) // - .and(not(describe("are Kotlin-generated classes that contain only top-level functions", - simpleNameEndingWith("Kt")))) // - .and(notShadowed()) // - .should().beAnnotatedWith(API.class); + // TODO: ConfigurationMetadataAnnotationProcessor isn't an API for OSGI purposes? + // @SuppressWarnings("unused") + // @ArchTest + // private final ArchRule allPublicTopLevelTypesHaveApiAnnotations = classes() // + // .that(have(modifier(PUBLIC))) // + // .and(TOP_LEVEL_CLASSES) // + // .and(not(ANONYMOUS_CLASSES)) // + // .and(not(describe("are Kotlin SAM type implementations", simpleName("")))) // + // .and(not(describe("are Kotlin-generated classes that contain only top-level functions", + // simpleNameEndingWith("Kt")))) // + // .and(notShadowed()) // + // .should().beAnnotatedWith(API.class); @SuppressWarnings("unused") @ArchTest // Consistency of @Documented and @Inherited is checked by the compiler but not for @Retention and @Target From 63dd21ba4f135baee54c5906def70730a5705817 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 30 Jul 2026 23:49:34 +0200 Subject: [PATCH 004/117] Fix workaround for Javadoc --- junit-jupiter-api/junit-jupiter-api.gradle.kts | 5 ++--- junit-jupiter-engine/junit-jupiter-engine.gradle.kts | 1 + .../junit-jupiter-migrationsupport.gradle.kts | 1 + junit-jupiter-params/junit-jupiter-params.gradle.kts | 1 + junit-jupiter/junit-jupiter.gradle.kts | 1 + junit-start/junit-start.gradle.kts | 1 + 6 files changed, 7 insertions(+), 3 deletions(-) diff --git a/junit-jupiter-api/junit-jupiter-api.gradle.kts b/junit-jupiter-api/junit-jupiter-api.gradle.kts index 5dce4bbc02fa..068076117718 100644 --- a/junit-jupiter-api/junit-jupiter-api.gradle.kts +++ b/junit-jupiter-api/junit-jupiter-api.gradle.kts @@ -17,8 +17,7 @@ dependencies { compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) - // TODO: Marc, why does compileOnlyApi break the Javadoc task? - api(projects.junitPlatformConfigurationApi) + compileOnlyApi(projects.junitPlatformConfigurationApi) compileOnly(kotlin("stdlib")) @@ -30,7 +29,7 @@ dependencies { } javadocConventions { - addExtraModuleReferences(projects.junitPlatformEngine, projects.junitPlatformLauncher, projects.junitJupiterParams) + addExtraModuleReferences(projects.junitPlatformEngine, projects.junitPlatformConfigurationApi, projects.junitPlatformLauncher, projects.junitJupiterParams) } eclipseConventions { diff --git a/junit-jupiter-engine/junit-jupiter-engine.gradle.kts b/junit-jupiter-engine/junit-jupiter-engine.gradle.kts index 0624868ad2b0..62a125526ac1 100644 --- a/junit-jupiter-engine/junit-jupiter-engine.gradle.kts +++ b/junit-jupiter-engine/junit-jupiter-engine.gradle.kts @@ -12,6 +12,7 @@ dependencies { compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) + compileOnlyApi(projects.junitPlatformConfigurationApi) osgiVerification(projects.junitPlatformLauncher) } diff --git a/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts b/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts index 7be8ee70ee38..fb9b0c7268cf 100644 --- a/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts +++ b/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts @@ -12,6 +12,7 @@ dependencies { compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) + compileOnlyApi(projects.junitPlatformConfigurationApi) osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) diff --git a/junit-jupiter-params/junit-jupiter-params.gradle.kts b/junit-jupiter-params/junit-jupiter-params.gradle.kts index 8973d77ae629..3fb8e710b0e1 100644 --- a/junit-jupiter-params/junit-jupiter-params.gradle.kts +++ b/junit-jupiter-params/junit-jupiter-params.gradle.kts @@ -17,6 +17,7 @@ dependencies { compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) + compileOnlyApi(projects.junitPlatformConfigurationApi) shadowed(libs.fastcsv) diff --git a/junit-jupiter/junit-jupiter.gradle.kts b/junit-jupiter/junit-jupiter.gradle.kts index bc93d030e8d8..35bda5e79aec 100644 --- a/junit-jupiter/junit-jupiter.gradle.kts +++ b/junit-jupiter/junit-jupiter.gradle.kts @@ -13,6 +13,7 @@ dependencies { compileOnly(libs.apiguardian) compileOnly(libs.jspecify) + compileOnly(projects.junitPlatformConfigurationApi) osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) diff --git a/junit-start/junit-start.gradle.kts b/junit-start/junit-start.gradle.kts index 8b03e5986e21..7fed90461a17 100644 --- a/junit-start/junit-start.gradle.kts +++ b/junit-start/junit-start.gradle.kts @@ -11,6 +11,7 @@ dependencies { compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) compileOnlyApi(projects.junitJupiterEngine) + compileOnlyApi(projects.junitPlatformConfigurationApi) implementation(projects.junitPlatformLauncher) implementation(projects.junitPlatformConsole) From 578bdca8f4dad1bbd986179ed4f0872f79b4f0da Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 30 Jul 2026 23:51:22 +0200 Subject: [PATCH 005/117] Fix verifyOSGi --- .../src/main/kotlin/junitbuild.osgi-conventions.gradle.kts | 7 +++---- .../junit-platform-configuration-api.gradle.kts | 1 + .../junit-platform-configuration-processor.gradle.kts | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts index 2e0c1947c9be..c3441bb5df1d 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts @@ -125,7 +125,6 @@ val verifyOSGi = tasks.register("verifyOSGi", Resolve::class) { properties.empty() } -// TODO: Fails, for some reason -//tasks.check { -// dependsOn(verifyOSGi) -//} +tasks.check { + dependsOn(verifyOSGi) +} diff --git a/junit-platform-configuration-api/junit-platform-configuration-api.gradle.kts b/junit-platform-configuration-api/junit-platform-configuration-api.gradle.kts index 3b76d6fc66fa..6f98a4843a93 100644 --- a/junit-platform-configuration-api/junit-platform-configuration-api.gradle.kts +++ b/junit-platform-configuration-api/junit-platform-configuration-api.gradle.kts @@ -6,6 +6,7 @@ description = "JUnit Platform Configuration API" dependencies { api(platform(projects.junitBom)) + api(projects.junitPlatformCommons) compileOnlyApi(libs.jspecify) } diff --git a/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts b/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts index 066c0bd9d90d..1c30a25b1eef 100644 --- a/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts +++ b/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts @@ -7,6 +7,8 @@ description = "JUnit Platform Configuration Processor" dependencies { api(platform(projects.junitBom)) api(projects.junitPlatformConfigurationApi) + api(projects.junitPlatformCommons) + compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) } From 2d7547d263415559ca6e3a1d03b73b7de7a5f773 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 31 Jul 2026 00:05:28 +0200 Subject: [PATCH 006/117] Fix describe module tests --- .../junit-platform-configuration-api.expected.txt | 2 +- .../junit-platform-configuration-processor.expected.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-api.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-api.expected.txt index f0c9ea97e404..5106f577f6ba 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-api.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-api.expected.txt @@ -1,4 +1,4 @@ -org.junit.platform.configuration.api@6.2.0-SNAPSHOT jar:file:///home/mpkorstanje/Projects/github/junit-team/junit-framework/platform-tooling-support-tests/build/normalized-repo/org/junit/platform/junit-platform-configuration-api/6.2.0-SNAPSHOT/junit-platform-configuration-api-6.2.0-SNAPSHOT.jar!/module-info.class +org.junit.platform.configuration.api@${version} jar:file:.+/junit-platform-configuration-api-\d.+\.jar..module-info\.class exports org.junit.platform.configuration.api requires java.base mandated requires org.jspecify static transitive diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt index 571f2bbe632a..27986b3eedec 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt @@ -1,4 +1,4 @@ -org.junit.platform.configuration.processor@6.2.0-SNAPSHOT jar:file:///home/mpkorstanje/Projects/github/junit-team/junit-framework/platform-tooling-support-tests/build/normalized-repo/org/junit/platform/junit-platform-configuration-processor/6.2.0-SNAPSHOT/junit-platform-configuration-processor-6.2.0-SNAPSHOT.jar!/module-info.class +org.junit.platform.configuration.processor@${version} jar:file:.+/junit-platform-configuration-processor-\d.+\.jar..module-info\.class requires java.base mandated requires java.compiler requires org.jspecify static transitive From 6bc410bc501fceb217db22b80d0a38cb6951c8eb Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 31 Jul 2026 00:12:56 +0200 Subject: [PATCH 007/117] Fix allPublicTopLevelTypesHaveApiAnnotations test --- ...unit-platform-configuration-api.gradle.kts | 1 + .../src/main/java/module-info.java | 1 + .../api/ConfigurationProperty.java | 3 ++ .../src/main/java/module-info.java | 1 + ...figurationMetadataAnnotationProcessor.java | 2 ++ ...it-platform-configuration-api.expected.txt | 5 ++-- ...tform-configuration-processor.expected.txt | 5 ++-- .../tooling/support/tests/ArchUnitTests.java | 30 +++++++++++-------- 8 files changed, 32 insertions(+), 16 deletions(-) diff --git a/junit-platform-configuration-api/junit-platform-configuration-api.gradle.kts b/junit-platform-configuration-api/junit-platform-configuration-api.gradle.kts index 6f98a4843a93..3e2782ed16b2 100644 --- a/junit-platform-configuration-api/junit-platform-configuration-api.gradle.kts +++ b/junit-platform-configuration-api/junit-platform-configuration-api.gradle.kts @@ -8,5 +8,6 @@ dependencies { api(platform(projects.junitBom)) api(projects.junitPlatformCommons) + compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) } diff --git a/junit-platform-configuration-api/src/main/java/module-info.java b/junit-platform-configuration-api/src/main/java/module-info.java index 6692bc571702..dfa9067462b7 100644 --- a/junit-platform-configuration-api/src/main/java/module-info.java +++ b/junit-platform-configuration-api/src/main/java/module-info.java @@ -10,6 +10,7 @@ module org.junit.platform.configuration.api { requires static transitive org.jspecify; + requires static transitive org.apiguardian.api; exports org.junit.platform.configuration.api; } diff --git a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationProperty.java b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationProperty.java index ea5a0c827fd7..bb0aed569f76 100644 --- a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationProperty.java +++ b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationProperty.java @@ -15,6 +15,9 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.apiguardian.api.API; + +@API(status = API.Status.EXPERIMENTAL) @Retention(RetentionPolicy.SOURCE) @Target(ElementType.FIELD) public @interface ConfigurationProperty { diff --git a/junit-platform-configuration-processor/src/main/java/module-info.java b/junit-platform-configuration-processor/src/main/java/module-info.java index f211842906c3..1c134963564b 100644 --- a/junit-platform-configuration-processor/src/main/java/module-info.java +++ b/junit-platform-configuration-processor/src/main/java/module-info.java @@ -10,6 +10,7 @@ module org.junit.platform.configuration.processor { requires static transitive org.jspecify; + requires static transitive org.apiguardian.api; requires java.compiler; requires org.junit.platform.configuration.api; diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index 3dba8fc1fb04..3725a709d25d 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -24,8 +24,10 @@ import javax.lang.model.element.TypeElement; import javax.tools.StandardLocation; +import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; +@API(status = API.Status.EXPERIMENTAL) @SupportedSourceVersion(SourceVersion.RELEASE_17) @SupportedAnnotationTypes("org.junit.platform.configuration.api.ConfigurationProperty") public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor { diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-api.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-api.expected.txt index 5106f577f6ba..4f1c5bb07d74 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-api.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-api.expected.txt @@ -1,4 +1,5 @@ -org.junit.platform.configuration.api@${version} jar:file:.+/junit-platform-configuration-api-\d.+\.jar..module-info\.class +org.junit.platform.configuration.api@6.2.0-SNAPSHOT jar:file:///home/mpkorstanje/Projects/github/junit-team/junit-framework/platform-tooling-support-tests/build/normalized-repo/org/junit/platform/junit-platform-configuration-api/6.2.0-SNAPSHOT/junit-platform-configuration-api-6.2.0-SNAPSHOT.jar!/module-info.class exports org.junit.platform.configuration.api requires java.base mandated -requires org.jspecify static transitive +requires org.apiguardian.api static transitive +requires org.jspecify static transitive \ No newline at end of file diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt index 27986b3eedec..3b6bbe89054d 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt @@ -1,7 +1,8 @@ -org.junit.platform.configuration.processor@${version} jar:file:.+/junit-platform-configuration-processor-\d.+\.jar..module-info\.class +org.junit.platform.configuration.processor@6.2.0-SNAPSHOT jar:file:///home/mpkorstanje/Projects/github/junit-team/junit-framework/platform-tooling-support-tests/build/normalized-repo/org/junit/platform/junit-platform-configuration-processor/6.2.0-SNAPSHOT/junit-platform-configuration-processor-6.2.0-SNAPSHOT.jar!/module-info.class requires java.base mandated requires java.compiler +requires org.apiguardian.api static transitive requires org.jspecify static transitive requires org.junit.platform.configuration.api provides javax.annotation.processing.Processor with org.junit.platform.configuration.processor.ConfigurationMetadataAnnotationProcessor -contains org.junit.platform.configuration.processor +contains org.junit.platform.configuration.processor \ No newline at end of file diff --git a/platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java b/platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java index b9617eac46ae..026711669ebc 100644 --- a/platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java +++ b/platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java @@ -13,14 +13,21 @@ import static com.tngtech.archunit.base.DescribedPredicate.and; import static com.tngtech.archunit.base.DescribedPredicate.describe; import static com.tngtech.archunit.base.DescribedPredicate.not; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.ANONYMOUS_CLASSES; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.TOP_LEVEL_CLASSES; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleName; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleNameEndingWith; import static com.tngtech.archunit.core.domain.JavaMember.Predicates.declaredIn; +import static com.tngtech.archunit.core.domain.JavaModifier.PUBLIC; import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Predicates.annotatedWith; +import static com.tngtech.archunit.core.domain.properties.HasModifiers.Predicates.modifier; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameContaining; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameStartingWith; import static com.tngtech.archunit.lang.conditions.ArchConditions.onlyBeAccessedByClassesThat; import static com.tngtech.archunit.lang.conditions.ArchPredicates.are; +import static com.tngtech.archunit.lang.conditions.ArchPredicates.have; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.members; import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; @@ -67,18 +74,17 @@ class ArchUnitTests { private final ArchRule allClassesAreInJUnitPackage = classes() // .should().haveNameMatching("org\\.junit\\..+"); - // TODO: ConfigurationMetadataAnnotationProcessor isn't an API for OSGI purposes? - // @SuppressWarnings("unused") - // @ArchTest - // private final ArchRule allPublicTopLevelTypesHaveApiAnnotations = classes() // - // .that(have(modifier(PUBLIC))) // - // .and(TOP_LEVEL_CLASSES) // - // .and(not(ANONYMOUS_CLASSES)) // - // .and(not(describe("are Kotlin SAM type implementations", simpleName("")))) // - // .and(not(describe("are Kotlin-generated classes that contain only top-level functions", - // simpleNameEndingWith("Kt")))) // - // .and(notShadowed()) // - // .should().beAnnotatedWith(API.class); + @SuppressWarnings("unused") + @ArchTest + private final ArchRule allPublicTopLevelTypesHaveApiAnnotations = classes() // + .that(have(modifier(PUBLIC))) // + .and(TOP_LEVEL_CLASSES) // + .and(not(ANONYMOUS_CLASSES)) // + .and(not(describe("are Kotlin SAM type implementations", simpleName("")))) // + .and(not(describe("are Kotlin-generated classes that contain only top-level functions", + simpleNameEndingWith("Kt")))) // + .and(notShadowed()) // + .should().beAnnotatedWith(API.class); @SuppressWarnings("unused") @ArchTest // Consistency of @Documented and @Inherited is checked by the compiler but not for @Retention and @Target From e06d4c19e51d9ad3bfc3f19f1548dd8354120b02 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 31 Jul 2026 00:13:10 +0200 Subject: [PATCH 008/117] Explain why processing linting is supressed --- junit-jupiter-api/junit-jupiter-api.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junit-jupiter-api/junit-jupiter-api.gradle.kts b/junit-jupiter-api/junit-jupiter-api.gradle.kts index 068076117718..82eb68146c0e 100644 --- a/junit-jupiter-api/junit-jupiter-api.gradle.kts +++ b/junit-jupiter-api/junit-jupiter-api.gradle.kts @@ -38,7 +38,7 @@ eclipseConventions { tasks { compileJava { - options.compilerArgs.add("-Xlint:-module,-processing") // due to qualified exports, not all annotations need to be processed + options.compilerArgs.add("-Xlint:-module,-processing") // -module: due to qualified exports, -processing: not all annotations need to be processed } jar { bundle { From ccea87e25f021fcfec7ff707ed4bf6e038aaba00 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 31 Jul 2026 00:35:33 +0200 Subject: [PATCH 009/117] Output junit-platform-configuration-metadata for platform-launcher --- .../junit-platform-console-standalone.gradle.kts | 6 ++---- junit-platform-console/junit-platform-console.gradle.kts | 4 ++++ .../junit-platform-launcher.gradle.kts | 8 +++++++- junit-platform-launcher/src/main/java/module-info.java | 2 ++ .../org/junit/platform/launcher/LauncherConstants.java | 2 ++ .../junit-platform-reporting.gradle.kts | 4 ++++ .../junit-platform-suite-engine.gradle.kts | 4 ++++ junit-platform-testkit/junit-platform-testkit.gradle.kts | 4 ++++ .../junit-platform-launcher.expected.txt | 5 +++-- 9 files changed, 32 insertions(+), 7 deletions(-) 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 c0bfd2391dff..794284d8b8a4 100644 --- a/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts +++ b/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts @@ -22,10 +22,6 @@ dependencies { osgiVerification(libs.openTestReporting.tooling.spi) } -backwardCompatibilityChecks { - enabled = false // already checked by individual projects -} - tasks { jar { manifest { @@ -58,6 +54,8 @@ tasks { // https://github.com/junit-team/junit-framework/issues/2557 // exclude compiled module declarations from any source (e.g. /*, /META-INF/versions/N/*) exclude("**/module-info.class") + // TODO: merge meta data files? + exclude("META-INF/junit-platform-configuration-metadata.json") // https://github.com/junit-team/junit-framework/issues/761 // prevent duplicates, add 3rd-party licenses explicitly exclude("**/COPYRIGHT*") diff --git a/junit-platform-console/junit-platform-console.gradle.kts b/junit-platform-console/junit-platform-console.gradle.kts index c4553d6df7a5..9c522cb699f8 100644 --- a/junit-platform-console/junit-platform-console.gradle.kts +++ b/junit-platform-console/junit-platform-console.gradle.kts @@ -25,6 +25,10 @@ dependencies { osgiVerification(libs.openTestReporting.tooling.spi) } +javadocConventions { + addExtraModuleReferences(projects.junitPlatformConfigurationApi) +} + tasks { compileJava { options.compilerArgs.addAll(listOf( diff --git a/junit-platform-launcher/junit-platform-launcher.gradle.kts b/junit-platform-launcher/junit-platform-launcher.gradle.kts index 00e431dead15..c3852e1d3472 100644 --- a/junit-platform-launcher/junit-platform-launcher.gradle.kts +++ b/junit-platform-launcher/junit-platform-launcher.gradle.kts @@ -6,20 +6,26 @@ plugins { description = "JUnit Platform Launcher" dependencies { + annotationProcessor(projects.junitPlatformConfigurationProcessor) + api(platform(projects.junitBom)) api(projects.junitPlatformEngine) compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) + compileOnlyApi(projects.junitPlatformConfigurationApi) osgiVerification(projects.junitJupiterEngine) } javadocConventions { - addExtraModuleReferences(projects.junitPlatformReporting) + addExtraModuleReferences(projects.junitPlatformConfigurationApi, projects.junitPlatformReporting) } tasks { + compileJava { + options.compilerArgs.add("-Xlint:-processing") // -processing: not all annotations need to be processed + } jar { bundle { bnd(""" diff --git a/junit-platform-launcher/src/main/java/module-info.java b/junit-platform-launcher/src/main/java/module-info.java index 466aa808fc1c..958a0f8acff1 100644 --- a/junit-platform-launcher/src/main/java/module-info.java +++ b/junit-platform-launcher/src/main/java/module-info.java @@ -25,6 +25,8 @@ requires static transitive org.apiguardian.api; requires static transitive org.jspecify; + requires static transitive org.junit.platform.configuration.api; + requires static jdk.jfr; requires transitive java.logging; 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 de1cfc719f78..c7aec3a8e2a1 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 @@ -16,6 +16,7 @@ import org.apiguardian.api.API; import org.junit.platform.commons.util.ClassNamePatternFilterUtils; +import org.junit.platform.configuration.api.ConfigurationProperty; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.reporting.ReportEntry; @@ -44,6 +45,7 @@ public class LauncherConstants { * @see ReportEntry * @see TestExecutionListener#reportingEntryPublished(TestIdentifier, ReportEntry) */ + @ConfigurationProperty public static final String CAPTURE_STDOUT_PROPERTY_NAME = "junit.platform.output.capture.stdout"; /** diff --git a/junit-platform-reporting/junit-platform-reporting.gradle.kts b/junit-platform-reporting/junit-platform-reporting.gradle.kts index 922b57a757e9..8f45f6f4f330 100644 --- a/junit-platform-reporting/junit-platform-reporting.gradle.kts +++ b/junit-platform-reporting/junit-platform-reporting.gradle.kts @@ -26,6 +26,10 @@ dependencies { testFixturesApi(projects.junitJupiterApi) } +javadocConventions { + addExtraModuleReferences(projects.junitPlatformConfigurationApi) +} + tasks { shadowJar { listOf("events", "schema").forEach { name -> diff --git a/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts b/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts index d7ec38cba45e..b2fbe8be79a2 100644 --- a/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts +++ b/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts @@ -17,3 +17,7 @@ dependencies { osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) } + +javadocConventions { + addExtraModuleReferences(projects.junitPlatformConfigurationApi) +} diff --git a/junit-platform-testkit/junit-platform-testkit.gradle.kts b/junit-platform-testkit/junit-platform-testkit.gradle.kts index 13ca2af224b6..cc74e0d4c24d 100644 --- a/junit-platform-testkit/junit-platform-testkit.gradle.kts +++ b/junit-platform-testkit/junit-platform-testkit.gradle.kts @@ -16,3 +16,7 @@ dependencies { osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) } + +javadocConventions { + addExtraModuleReferences(projects.junitPlatformConfigurationApi) +} diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt index 3bf55cd46f61..b96533ee0cac 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt @@ -1,4 +1,4 @@ -org.junit.platform.launcher@${version} jar:file:.+/junit-platform-launcher-\d.+\.jar..module-info\.class +org.junit.platform.launcher@6.2.0-SNAPSHOT jar:file:///home/mpkorstanje/Projects/github/junit-team/junit-framework/platform-tooling-support-tests/build/normalized-repo/org/junit/platform/junit-platform-launcher/6.2.0-SNAPSHOT/junit-platform-launcher-6.2.0-SNAPSHOT.jar!/module-info.class exports org.junit.platform.launcher exports org.junit.platform.launcher.core exports org.junit.platform.launcher.listeners @@ -9,6 +9,7 @@ requires jdk.jfr static requires org.apiguardian.api static transitive requires org.jspecify static transitive requires org.junit.platform.commons transitive +requires org.junit.platform.configuration.api static transitive requires org.junit.platform.engine transitive uses org.junit.platform.engine.TestEngine uses org.junit.platform.launcher.LauncherDiscoveryListener @@ -16,4 +17,4 @@ uses org.junit.platform.launcher.LauncherInterceptor uses org.junit.platform.launcher.LauncherSessionListener uses org.junit.platform.launcher.PostDiscoveryFilter uses org.junit.platform.launcher.TestExecutionListener -provides org.junit.platform.launcher.TestExecutionListener with org.junit.platform.launcher.listeners.UniqueIdTrackingListener +provides org.junit.platform.launcher.TestExecutionListener with org.junit.platform.launcher.listeners.UniqueIdTrackingListener \ No newline at end of file From fd1baf1c40ae34f36bfb6d919e3b886ea49808c7 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 31 Jul 2026 00:46:17 +0200 Subject: [PATCH 010/117] Fix checkBackwardCompatibility --- .../kotlin/junitbuild.backward-compatibility.gradle.kts | 7 +++---- .../junit-platform-configuration-api.gradle.kts | 4 ++++ .../junit-platform-configuration-processor.gradle.kts | 4 ++++ .../junit-platform-console-standalone.gradle.kts | 4 ++++ 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/gradle/plugins/backward-compatibility/src/main/kotlin/junitbuild.backward-compatibility.gradle.kts b/gradle/plugins/backward-compatibility/src/main/kotlin/junitbuild.backward-compatibility.gradle.kts index 21a084afc970..cc14ed07d664 100644 --- a/gradle/plugins/backward-compatibility/src/main/kotlin/junitbuild.backward-compatibility.gradle.kts +++ b/gradle/plugins/backward-compatibility/src/main/kotlin/junitbuild.backward-compatibility.gradle.kts @@ -89,7 +89,6 @@ val checkBackwardCompatibility = tasks.register("checkBackwardCompatibility") { dependsOn(roseau) } -// TODO: Breaks because the configuration-api hasn't been released yet. -//tasks.check { -// dependsOn(checkBackwardCompatibility) -//} +tasks.check { + dependsOn(checkBackwardCompatibility) +} diff --git a/junit-platform-configuration-api/junit-platform-configuration-api.gradle.kts b/junit-platform-configuration-api/junit-platform-configuration-api.gradle.kts index 3e2782ed16b2..fc03f296f01c 100644 --- a/junit-platform-configuration-api/junit-platform-configuration-api.gradle.kts +++ b/junit-platform-configuration-api/junit-platform-configuration-api.gradle.kts @@ -11,3 +11,7 @@ dependencies { compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) } + +backwardCompatibilityChecks { + enabled = false // not yet released +} diff --git a/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts b/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts index 1c30a25b1eef..8bbc6735b57d 100644 --- a/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts +++ b/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts @@ -12,3 +12,7 @@ dependencies { compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) } + +backwardCompatibilityChecks { + enabled = false // not yet released +} 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 794284d8b8a4..05d127a64834 100644 --- a/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts +++ b/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts @@ -22,6 +22,10 @@ dependencies { osgiVerification(libs.openTestReporting.tooling.spi) } +backwardCompatibilityChecks { + enabled = false // already checked by individual projects +} + tasks { jar { manifest { From 3295a99fe7d6092a96e6d21cce4be559e2c1013d Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 31 Jul 2026 00:48:44 +0200 Subject: [PATCH 011/117] Fix JarDescribeModuleTests --- .../junit-platform-configuration-api.expected.txt | 2 +- .../junit-platform-configuration-processor.expected.txt | 2 +- .../jar-describe-module/junit-platform-launcher.expected.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-api.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-api.expected.txt index 4f1c5bb07d74..34741f806073 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-api.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-api.expected.txt @@ -1,4 +1,4 @@ -org.junit.platform.configuration.api@6.2.0-SNAPSHOT jar:file:///home/mpkorstanje/Projects/github/junit-team/junit-framework/platform-tooling-support-tests/build/normalized-repo/org/junit/platform/junit-platform-configuration-api/6.2.0-SNAPSHOT/junit-platform-configuration-api-6.2.0-SNAPSHOT.jar!/module-info.class +org.junit.platform.configuration.api@${version} jar:file:.+/junit-platform-configuration-api-\d.+\.jar..module-info\.class exports org.junit.platform.configuration.api requires java.base mandated requires org.apiguardian.api static transitive diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt index 3b6bbe89054d..c5d5087cbe64 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt @@ -1,4 +1,4 @@ -org.junit.platform.configuration.processor@6.2.0-SNAPSHOT jar:file:///home/mpkorstanje/Projects/github/junit-team/junit-framework/platform-tooling-support-tests/build/normalized-repo/org/junit/platform/junit-platform-configuration-processor/6.2.0-SNAPSHOT/junit-platform-configuration-processor-6.2.0-SNAPSHOT.jar!/module-info.class +org.junit.platform.configuration.processor@${version} jar:file:.+/junit-platform-configuration-processor-\d.+\.jar..module-info\.class requires java.base mandated requires java.compiler requires org.apiguardian.api static transitive diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt index b96533ee0cac..059daea06286 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt @@ -1,4 +1,4 @@ -org.junit.platform.launcher@6.2.0-SNAPSHOT jar:file:///home/mpkorstanje/Projects/github/junit-team/junit-framework/platform-tooling-support-tests/build/normalized-repo/org/junit/platform/junit-platform-launcher/6.2.0-SNAPSHOT/junit-platform-launcher-6.2.0-SNAPSHOT.jar!/module-info.class +org.junit.platform.launcher@${version} jar:file:.+/junit-platform-launcher-\d.+\.jar..module-info\.class exports org.junit.platform.launcher exports org.junit.platform.launcher.core exports org.junit.platform.launcher.listeners From 3b5624f1379abb80889372cd30aca1f7ff25d1ae Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 31 Jul 2026 10:41:22 +0200 Subject: [PATCH 012/117] Reduce scope to compileOnly --- junit-jupiter-api/junit-jupiter-api.gradle.kts | 4 ++-- junit-jupiter-api/src/main/java/module-info.java | 2 +- junit-jupiter-engine/junit-jupiter-engine.gradle.kts | 1 - .../junit-jupiter-migrationsupport.gradle.kts | 1 - junit-jupiter-params/junit-jupiter-params.gradle.kts | 1 - junit-jupiter/junit-jupiter.gradle.kts | 1 - junit-platform-console/junit-platform-console.gradle.kts | 4 ---- junit-platform-launcher/junit-platform-launcher.gradle.kts | 5 +++-- junit-platform-launcher/src/main/java/module-info.java | 2 +- junit-platform-reporting/junit-platform-reporting.gradle.kts | 4 ---- .../junit-platform-suite-engine.gradle.kts | 4 ---- junit-platform-testkit/junit-platform-testkit.gradle.kts | 4 ---- junit-start/junit-start.gradle.kts | 1 - .../jar-describe-module/junit-jupiter-api.expected.txt | 2 +- .../jar-describe-module/junit-platform-launcher.expected.txt | 2 +- 15 files changed, 9 insertions(+), 29 deletions(-) diff --git a/junit-jupiter-api/junit-jupiter-api.gradle.kts b/junit-jupiter-api/junit-jupiter-api.gradle.kts index 82eb68146c0e..c52c9cf784e6 100644 --- a/junit-jupiter-api/junit-jupiter-api.gradle.kts +++ b/junit-jupiter-api/junit-jupiter-api.gradle.kts @@ -17,9 +17,9 @@ dependencies { compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) - compileOnlyApi(projects.junitPlatformConfigurationApi) compileOnly(kotlin("stdlib")) + compileOnly(projects.junitPlatformConfigurationApi) testFixturesImplementation(libs.assertj) testFixturesImplementation(testFixtures(projects.junitPlatformCommons)) @@ -29,7 +29,7 @@ dependencies { } javadocConventions { - addExtraModuleReferences(projects.junitPlatformEngine, projects.junitPlatformConfigurationApi, projects.junitPlatformLauncher, projects.junitJupiterParams) + addExtraModuleReferences(projects.junitPlatformEngine, projects.junitPlatformLauncher, projects.junitJupiterParams) } eclipseConventions { diff --git a/junit-jupiter-api/src/main/java/module-info.java b/junit-jupiter-api/src/main/java/module-info.java index e59a765b49f6..583a9701aa62 100644 --- a/junit-jupiter-api/src/main/java/module-info.java +++ b/junit-jupiter-api/src/main/java/module-info.java @@ -17,11 +17,11 @@ requires static transitive org.apiguardian.api; requires static transitive org.jspecify; - requires static transitive org.junit.platform.configuration.api; requires transitive org.junit.platform.commons; requires transitive org.opentest4j; + requires static org.junit.platform.configuration.api; requires static kotlin.stdlib; exports org.junit.jupiter.api; diff --git a/junit-jupiter-engine/junit-jupiter-engine.gradle.kts b/junit-jupiter-engine/junit-jupiter-engine.gradle.kts index 62a125526ac1..0624868ad2b0 100644 --- a/junit-jupiter-engine/junit-jupiter-engine.gradle.kts +++ b/junit-jupiter-engine/junit-jupiter-engine.gradle.kts @@ -12,7 +12,6 @@ dependencies { compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) - compileOnlyApi(projects.junitPlatformConfigurationApi) osgiVerification(projects.junitPlatformLauncher) } diff --git a/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts b/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts index fb9b0c7268cf..7be8ee70ee38 100644 --- a/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts +++ b/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts @@ -12,7 +12,6 @@ dependencies { compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) - compileOnlyApi(projects.junitPlatformConfigurationApi) osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) diff --git a/junit-jupiter-params/junit-jupiter-params.gradle.kts b/junit-jupiter-params/junit-jupiter-params.gradle.kts index 3fb8e710b0e1..8973d77ae629 100644 --- a/junit-jupiter-params/junit-jupiter-params.gradle.kts +++ b/junit-jupiter-params/junit-jupiter-params.gradle.kts @@ -17,7 +17,6 @@ dependencies { compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) - compileOnlyApi(projects.junitPlatformConfigurationApi) shadowed(libs.fastcsv) diff --git a/junit-jupiter/junit-jupiter.gradle.kts b/junit-jupiter/junit-jupiter.gradle.kts index 35bda5e79aec..bc93d030e8d8 100644 --- a/junit-jupiter/junit-jupiter.gradle.kts +++ b/junit-jupiter/junit-jupiter.gradle.kts @@ -13,7 +13,6 @@ dependencies { compileOnly(libs.apiguardian) compileOnly(libs.jspecify) - compileOnly(projects.junitPlatformConfigurationApi) osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) diff --git a/junit-platform-console/junit-platform-console.gradle.kts b/junit-platform-console/junit-platform-console.gradle.kts index 9c522cb699f8..c4553d6df7a5 100644 --- a/junit-platform-console/junit-platform-console.gradle.kts +++ b/junit-platform-console/junit-platform-console.gradle.kts @@ -25,10 +25,6 @@ dependencies { osgiVerification(libs.openTestReporting.tooling.spi) } -javadocConventions { - addExtraModuleReferences(projects.junitPlatformConfigurationApi) -} - tasks { compileJava { options.compilerArgs.addAll(listOf( diff --git a/junit-platform-launcher/junit-platform-launcher.gradle.kts b/junit-platform-launcher/junit-platform-launcher.gradle.kts index c3852e1d3472..046bd921df5a 100644 --- a/junit-platform-launcher/junit-platform-launcher.gradle.kts +++ b/junit-platform-launcher/junit-platform-launcher.gradle.kts @@ -13,13 +13,14 @@ dependencies { compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) - compileOnlyApi(projects.junitPlatformConfigurationApi) + + compileOnly(projects.junitPlatformConfigurationApi) osgiVerification(projects.junitJupiterEngine) } javadocConventions { - addExtraModuleReferences(projects.junitPlatformConfigurationApi, projects.junitPlatformReporting) + addExtraModuleReferences(projects.junitPlatformReporting) } tasks { diff --git a/junit-platform-launcher/src/main/java/module-info.java b/junit-platform-launcher/src/main/java/module-info.java index 958a0f8acff1..93c1672cfbf1 100644 --- a/junit-platform-launcher/src/main/java/module-info.java +++ b/junit-platform-launcher/src/main/java/module-info.java @@ -25,8 +25,8 @@ requires static transitive org.apiguardian.api; requires static transitive org.jspecify; - requires static transitive org.junit.platform.configuration.api; + requires static org.junit.platform.configuration.api; requires static jdk.jfr; requires transitive java.logging; diff --git a/junit-platform-reporting/junit-platform-reporting.gradle.kts b/junit-platform-reporting/junit-platform-reporting.gradle.kts index 8f45f6f4f330..922b57a757e9 100644 --- a/junit-platform-reporting/junit-platform-reporting.gradle.kts +++ b/junit-platform-reporting/junit-platform-reporting.gradle.kts @@ -26,10 +26,6 @@ dependencies { testFixturesApi(projects.junitJupiterApi) } -javadocConventions { - addExtraModuleReferences(projects.junitPlatformConfigurationApi) -} - tasks { shadowJar { listOf("events", "schema").forEach { name -> diff --git a/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts b/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts index b2fbe8be79a2..d7ec38cba45e 100644 --- a/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts +++ b/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts @@ -17,7 +17,3 @@ dependencies { osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) } - -javadocConventions { - addExtraModuleReferences(projects.junitPlatformConfigurationApi) -} diff --git a/junit-platform-testkit/junit-platform-testkit.gradle.kts b/junit-platform-testkit/junit-platform-testkit.gradle.kts index cc74e0d4c24d..13ca2af224b6 100644 --- a/junit-platform-testkit/junit-platform-testkit.gradle.kts +++ b/junit-platform-testkit/junit-platform-testkit.gradle.kts @@ -16,7 +16,3 @@ dependencies { osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) } - -javadocConventions { - addExtraModuleReferences(projects.junitPlatformConfigurationApi) -} diff --git a/junit-start/junit-start.gradle.kts b/junit-start/junit-start.gradle.kts index 7fed90461a17..8b03e5986e21 100644 --- a/junit-start/junit-start.gradle.kts +++ b/junit-start/junit-start.gradle.kts @@ -11,7 +11,6 @@ dependencies { compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) compileOnlyApi(projects.junitJupiterEngine) - compileOnlyApi(projects.junitPlatformConfigurationApi) implementation(projects.junitPlatformLauncher) implementation(projects.junitPlatformConsole) diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt index 01714c5c4512..856a65ab6108 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt @@ -12,7 +12,7 @@ requires kotlin.stdlib static requires org.apiguardian.api static transitive requires org.jspecify static transitive requires org.junit.platform.commons transitive -requires org.junit.platform.configuration.api static transitive +requires org.junit.platform.configuration.api static requires org.opentest4j transitive qualified exports org.junit.jupiter.api.timeout to org.junit.jupiter.engine qualified opens org.junit.jupiter.api.condition to org.junit.platform.commons diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt index 059daea06286..1253a982bf66 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt @@ -9,7 +9,7 @@ requires jdk.jfr static requires org.apiguardian.api static transitive requires org.jspecify static transitive requires org.junit.platform.commons transitive -requires org.junit.platform.configuration.api static transitive +requires org.junit.platform.configuration.api static requires org.junit.platform.engine transitive uses org.junit.platform.engine.TestEngine uses org.junit.platform.launcher.LauncherDiscoveryListener From 314e8fdcadc54509da2d651a67b68f884e7d4565 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 31 Jul 2026 11:57:49 +0200 Subject: [PATCH 013/117] Process annotations for Vintage --- junit-vintage-engine/junit-vintage-engine.gradle.kts | 6 +++++- junit-vintage-engine/src/main/java/module-info.java | 1 + .../src/main/java/org/junit/vintage/engine/Constants.java | 2 ++ .../jar-describe-module/junit-vintage-engine.expected.txt | 1 + 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/junit-vintage-engine/junit-vintage-engine.gradle.kts b/junit-vintage-engine/junit-vintage-engine.gradle.kts index d8b3f7cc3b22..315dbd526f42 100644 --- a/junit-vintage-engine/junit-vintage-engine.gradle.kts +++ b/junit-vintage-engine/junit-vintage-engine.gradle.kts @@ -9,6 +9,8 @@ plugins { description = "JUnit Vintage Engine" dependencies { + annotationProcessor(projects.junitPlatformConfigurationProcessor) + api(platform(projects.junitBom)) api(projects.junitPlatformEngine) api(libs.junit4) @@ -16,6 +18,8 @@ dependencies { compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) + compileOnly(projects.junitPlatformConfigurationApi) + testFixturesApi(platform(libs.groovy2.bom)) testFixturesApi(libs.spock1) testFixturesImplementation(projects.junitPlatformSuiteApi) @@ -33,7 +37,7 @@ dependencies { tasks { compileJava { - options.compilerArgs.add("-Xlint:-requires-automatic") // JUnit 4 + options.compilerArgs.add("-Xlint:-requires-automatic,-processing") // -requires-automatic: JUnit 4, -module: due to qualified exports, -processing: not all annotations need to be processed } compileTestFixturesGroovy { javaLauncher = project.javaToolchains.launcherFor { diff --git a/junit-vintage-engine/src/main/java/module-info.java b/junit-vintage-engine/src/main/java/module-info.java index 336418b4cf0a..eecadbee0850 100644 --- a/junit-vintage-engine/src/main/java/module-info.java +++ b/junit-vintage-engine/src/main/java/module-info.java @@ -20,6 +20,7 @@ module org.junit.vintage.engine { requires static org.apiguardian.api; + requires static org.junit.platform.configuration.api; requires static transitive org.jspecify; requires junit; // 4 diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java index 2c7da30e392a..a6e5c44bf01c 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java @@ -14,6 +14,7 @@ import static org.apiguardian.api.API.Status.MAINTAINED; import org.apiguardian.api.API; +import org.junit.platform.configuration.api.ConfigurationProperty; /** * Collection of constants related to the {@link VintageTestEngine}. @@ -34,6 +35,7 @@ public final class Constants { * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") + @ConfigurationProperty public static final String PARALLEL_EXECUTION_ENABLED = "junit.vintage.execution.parallel.enabled"; /** diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-vintage-engine.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-vintage-engine.expected.txt index 8d3bf8d6650f..ce57bcbfc2e2 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-vintage-engine.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-vintage-engine.expected.txt @@ -3,6 +3,7 @@ requires java.base mandated requires junit requires org.apiguardian.api static requires org.jspecify static transitive +requires org.junit.platform.configuration.api static requires org.junit.platform.engine provides org.junit.platform.engine.TestEngine with org.junit.vintage.engine.VintageTestEngine contains org.junit.vintage.engine From a3aa4e5861dbe5aede764696d8461a93a6a7232c Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 4 Aug 2026 00:45:31 +0200 Subject: [PATCH 014/117] Setup minimal unit test --- ...figurationMetadataAnnotationProcessor.java | 15 +++- platform-tests/platform-tests.gradle.kts | 1 + ...ationMetadataAnnotationProcessorTests.java | 79 +++++++++++++++++++ .../processor/OneConfigurationProperty.java | 20 +++++ 4 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/processor/OneConfigurationProperty.java diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index 3725a709d25d..f8ed396c4661 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -12,6 +12,9 @@ import static java.util.Objects.requireNonNull; +import java.io.BufferedWriter; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.Set; @@ -19,7 +22,6 @@ import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; -import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.TypeElement; import javax.tools.StandardLocation; @@ -28,7 +30,6 @@ import org.jspecify.annotations.Nullable; @API(status = API.Status.EXPERIMENTAL) -@SupportedSourceVersion(SourceVersion.RELEASE_17) @SupportedAnnotationTypes("org.junit.platform.configuration.api.ConfigurationProperty") public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor { private static final String METADATA_PATH = "META-INF/junit-platform-configuration-metadata.json"; @@ -40,14 +41,20 @@ public synchronized void init(ProcessingEnvironment environment) { this.environment = environment; } + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { if (roundEnv.processingOver()) { try { var resource = requireNonNull(environment).getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", METADATA_PATH); - try (var out = resource.openOutputStream()) { - out.write("{}".getBytes(StandardCharsets.UTF_8)); + try (var out = new PrintWriter( + new BufferedWriter(new OutputStreamWriter(resource.openOutputStream(), StandardCharsets.UTF_8)))) { + out.println("{}"); } } catch (Exception ex) { diff --git a/platform-tests/platform-tests.gradle.kts b/platform-tests/platform-tests.gradle.kts index 520ad6b9a3e1..643c43519994 100644 --- a/platform-tests/platform-tests.gradle.kts +++ b/platform-tests/platform-tests.gradle.kts @@ -32,6 +32,7 @@ val woodstoxRuntimeClasspath = configurations.resolvable("woodstoxRuntimeClasspa dependencies { // --- Things we are testing -------------------------------------------------- testImplementation(projects.junitPlatformCommons) + testImplementation(projects.junitPlatformConfigurationProcessor) testImplementation(projects.junitPlatformConsole) testImplementation(projects.junitPlatformEngine) testImplementation(projects.junitPlatformLauncher) diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java new file mode 100644 index 000000000000..2fa9e5e5d6e5 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -0,0 +1,79 @@ +/* + * Copyright 2026 the original author or 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.configuration.processor; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.io.IOException; +import java.io.StringWriter; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +import javax.tools.DiagnosticCollector; +import javax.tools.SimpleJavaFileObject; +import javax.tools.ToolProvider; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class ConfigurationMetadataAnnotationProcessorTests { + + // TODO: Use an in memory solution, faster + @TempDir + Path tempDir; + + @Test + void test() { + var sourceDirectory = Path.of("src/test/java"); + var type = OneConfigurationProperty.class; + var sourceFile = sourceDirectory.resolve(type.getTypeName().replace('.', '/') + ".java"); + var processor = new ConfigurationMetadataAnnotationProcessor(); + var compiler = ToolProvider.getSystemJavaCompiler(); + var out = new StringWriter(); + var listener = new DiagnosticCollector<>(); + var fileManager = compiler.getStandardFileManager(listener, Locale.ROOT, Charset.defaultCharset()); + var options = List.of("-d", tempDir.toString()); + var classes = Set.of(type.getTypeName()); + var compilationUnits = Set.of(new JavaSourceFileObject(sourceFile)); + var task = compiler.getTask(out, fileManager, listener, options, classes, compilationUnits); + task.setProcessors(Set.of(processor)); + var result = task.call(); + if (!result || !listener.getDiagnostics().isEmpty()) { + System.out.println(out); + listener.getDiagnostics().forEach(System.out::println); + fail(); + } + var expectedMetadataPath = "META-INF/junit-platform-configuration-metadata.json"; + assertThat(tempDir.resolve(expectedMetadataPath)).exists().content().isEqualTo(""" + {} + """); + } + + private static class JavaSourceFileObject extends SimpleJavaFileObject { + + private final Path sourceFile; + + JavaSourceFileObject(Path sourceFile) { + super(sourceFile.toUri(), Kind.SOURCE); + this.sourceFile = sourceFile; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + return Files.readString(sourceFile); + } + } +} diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/OneConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/OneConfigurationProperty.java new file mode 100644 index 000000000000..f0abc6c64dd3 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/OneConfigurationProperty.java @@ -0,0 +1,20 @@ +/* + * Copyright 2026 the original author or 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.configuration.processor; + +import org.junit.platform.configuration.api.ConfigurationProperty; + +final class OneConfigurationProperty { + + @ConfigurationProperty + public static String EXAMPLE_PROPERTY_NAME = "org.example.property"; + +} From b1558360348db62a9335d7e985de2e7b9bf1cabe Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 4 Aug 2026 12:14:06 +0200 Subject: [PATCH 015/117] Add Eclipse IDE compatibility note --- junit-platform-configuration-api/src/test/README.md | 1 + junit-platform-configuration-processor/src/test/README.md | 1 + 2 files changed, 2 insertions(+) create mode 100644 junit-platform-configuration-api/src/test/README.md create mode 100644 junit-platform-configuration-processor/src/test/README.md diff --git a/junit-platform-configuration-api/src/test/README.md b/junit-platform-configuration-api/src/test/README.md new file mode 100644 index 000000000000..6e2fd0b363f0 --- /dev/null +++ b/junit-platform-configuration-api/src/test/README.md @@ -0,0 +1 @@ +For compatibility with the Eclipse IDE, the test for this module are in the `platform-tests` project. diff --git a/junit-platform-configuration-processor/src/test/README.md b/junit-platform-configuration-processor/src/test/README.md new file mode 100644 index 000000000000..6e2fd0b363f0 --- /dev/null +++ b/junit-platform-configuration-processor/src/test/README.md @@ -0,0 +1 @@ +For compatibility with the Eclipse IDE, the test for this module are in the `platform-tests` project. From 149556b9f39b46d61662b56da8eff4504d9d7fab Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 4 Aug 2026 12:55:29 +0200 Subject: [PATCH 016/117] Extract TestCompiler --- ...ationMetadataAnnotationProcessorTests.java | 67 ++++------- .../configuration/processor/TestCompiler.java | 108 ++++++++++++++++++ 2 files changed, 128 insertions(+), 47 deletions(-) create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/processor/TestCompiler.java diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index 2fa9e5e5d6e5..a3159e6bf370 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -11,69 +11,42 @@ package org.junit.platform.configuration.processor; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; import java.io.IOException; -import java.io.StringWriter; -import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; -import java.util.Locale; -import java.util.Set; - -import javax.tools.DiagnosticCollector; -import javax.tools.SimpleJavaFileObject; -import javax.tools.ToolProvider; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; class ConfigurationMetadataAnnotationProcessorTests { - // TODO: Use an in memory solution, faster + final String expectedMetadataPath = "META-INF/junit-platform-configuration-metadata.json"; + + final Path sourceDirectory = Path.of("src/test/java"); + @TempDir - Path tempDir; + Path outputDirectory; - @Test - void test() { - var sourceDirectory = Path.of("src/test/java"); - var type = OneConfigurationProperty.class; - var sourceFile = sourceDirectory.resolve(type.getTypeName().replace('.', '/') + ".java"); + TestCompiler compiler; + + @BeforeEach + void setup() { var processor = new ConfigurationMetadataAnnotationProcessor(); - var compiler = ToolProvider.getSystemJavaCompiler(); - var out = new StringWriter(); - var listener = new DiagnosticCollector<>(); - var fileManager = compiler.getStandardFileManager(listener, Locale.ROOT, Charset.defaultCharset()); - var options = List.of("-d", tempDir.toString()); - var classes = Set.of(type.getTypeName()); - var compilationUnits = Set.of(new JavaSourceFileObject(sourceFile)); - var task = compiler.getTask(out, fileManager, listener, options, classes, compilationUnits); - task.setProcessors(Set.of(processor)); - var result = task.call(); - if (!result || !listener.getDiagnostics().isEmpty()) { - System.out.println(out); - listener.getDiagnostics().forEach(System.out::println); - fail(); - } - var expectedMetadataPath = "META-INF/junit-platform-configuration-metadata.json"; - assertThat(tempDir.resolve(expectedMetadataPath)).exists().content().isEqualTo(""" + compiler = new TestCompiler(sourceDirectory, outputDirectory, processor); + } + + @Test + void test() throws IOException { + compiler.compile(OneConfigurationProperty.class); + assertThat(metaData()).isEqualTo(""" {} """); } - private static class JavaSourceFileObject extends SimpleJavaFileObject { - - private final Path sourceFile; - - JavaSourceFileObject(Path sourceFile) { - super(sourceFile.toUri(), Kind.SOURCE); - this.sourceFile = sourceFile; - } - - @Override - public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { - return Files.readString(sourceFile); - } + private String metaData() throws IOException { + return Files.readString(outputDirectory.resolve(expectedMetadataPath)); } + } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/TestCompiler.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/TestCompiler.java new file mode 100644 index 000000000000..b56f33c52888 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/TestCompiler.java @@ -0,0 +1,108 @@ +/* + * Copyright 2026 the original author or 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.configuration.processor; + +import static org.assertj.core.api.Assertions.fail; + +import java.io.IOException; +import java.io.StringWriter; +import java.lang.reflect.Type; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.annotation.processing.Processor; +import javax.tools.DiagnosticCollector; +import javax.tools.JavaCompiler; +import javax.tools.SimpleJavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; + +class TestCompiler { + + private final Path sourceDirectory; + // TODO: Use an in memory solution, faster + private final Path outputDirectory; + private final Processor processor; + + TestCompiler(Path sourceDirectory, Path outputDirectory, Processor processor) { + this.sourceDirectory = sourceDirectory; + this.outputDirectory = outputDirectory; + this.processor = processor; + } + + void compile(Type type) { + var options = List.of("-d", outputDirectory.toString()); + var listener = new DiagnosticCollector<>(); + var additionalOutput = new StringWriter(); + var task = ToolProvider.getSystemJavaCompiler().getTask( // + additionalOutput, // + fileManagerOf(ToolProvider.getSystemJavaCompiler(), listener), // + listener, // + options, // + classesOf(type), // + compilationUnitOf(type) // + ); + task.setProcessors(Set.of(processor)); + var result = task.call(); + if (!result || !listener.getDiagnostics().isEmpty()) { + var diagnostics = listener.getDiagnostics().stream() // + .map(Objects::toString) // + .collect(Collectors.joining("\n")); + fail(""" + Compilation of %s was not successful. + + Javac output: + + %s + + Diagnostics: + + %s + """.formatted(type, additionalOutput, diagnostics)); + } + } + + private static StandardJavaFileManager fileManagerOf(JavaCompiler compiler, DiagnosticCollector listener) { + return compiler.getStandardFileManager(listener, Locale.ROOT, Charset.defaultCharset()); + } + + private static Set classesOf(Type type) { + return Set.of(type.getTypeName()); + } + + private Set compilationUnitOf(Type type) { + var resourceName = type.getTypeName().replace('.', '/'); + var javaFileName = resourceName + ".java"; + var resolvedJavaFileName = sourceDirectory.resolve(javaFileName); + return Set.of(new JavaSourceFileObject(resolvedJavaFileName)); + } + + private static class JavaSourceFileObject extends SimpleJavaFileObject { + + private final Path sourceFile; + + JavaSourceFileObject(Path sourceFile) { + super(sourceFile.toUri(), Kind.SOURCE); + this.sourceFile = sourceFile; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + return Files.readString(sourceFile); + } + } +} From 72d715b98ea48fdda48d7f12c11cd2717a021eb7 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 4 Aug 2026 12:57:37 +0200 Subject: [PATCH 017/117] Write output with platform independent newline --- .../ConfigurationMetadataAnnotationProcessor.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index f8ed396c4661..6111b41b7399 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -14,7 +14,6 @@ import java.io.BufferedWriter; import java.io.OutputStreamWriter; -import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.Set; @@ -52,9 +51,9 @@ public boolean process(Set annotations, RoundEnvironment try { var resource = requireNonNull(environment).getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", METADATA_PATH); - try (var out = new PrintWriter( - new BufferedWriter(new OutputStreamWriter(resource.openOutputStream(), StandardCharsets.UTF_8)))) { - out.println("{}"); + try (var out = new BufferedWriter(new OutputStreamWriter(resource.openOutputStream(), StandardCharsets.UTF_8))) { + out.write("{}"); + out.write("\n"); } } catch (Exception ex) { From c1789d6f41e16e33e3125fbc0e4e596d22a5e40e Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 4 Aug 2026 15:07:54 +0200 Subject: [PATCH 018/117] Require annotated fields to be static with a constant value string --- .../src/main/java/module-info.java | 1 + .../src/main/java/module-info.java | 1 + .../processor/ConfigurationMetaData.java | 80 +++++++++++++++++++ ...figurationMetadataAnnotationProcessor.java | 72 ++++++++++++++--- ...ationMetadataAnnotationProcessorTests.java | 37 ++++++++- ...ava => NonFinalConfigurationProperty.java} | 2 +- .../NonStaticConfigurationProperty.java | 20 +++++ .../SimpleConfigurationProperty.java | 20 +++++ .../junit-platform-commons.expected.txt | 2 +- ...tform-configuration-processor.expected.txt | 3 +- 10 files changed, 219 insertions(+), 19 deletions(-) create mode 100644 junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java rename platform-tests/src/test/java/org/junit/platform/configuration/processor/{OneConfigurationProperty.java => NonFinalConfigurationProperty.java} (92%) create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/processor/NonStaticConfigurationProperty.java create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/processor/SimpleConfigurationProperty.java diff --git a/junit-platform-commons/src/main/java/module-info.java b/junit-platform-commons/src/main/java/module-info.java index d066d025a055..e47524dd9ebb 100644 --- a/junit-platform-commons/src/main/java/module-info.java +++ b/junit-platform-commons/src/main/java/module-info.java @@ -50,6 +50,7 @@ org.junit.jupiter.migrationsupport, org.junit.jupiter.params, org.junit.platform.console, + org.junit.platform.configuration.processor, org.junit.platform.engine, org.junit.platform.launcher, org.junit.platform.reporting, diff --git a/junit-platform-configuration-processor/src/main/java/module-info.java b/junit-platform-configuration-processor/src/main/java/module-info.java index 1c134963564b..7aed42f9559f 100644 --- a/junit-platform-configuration-processor/src/main/java/module-info.java +++ b/junit-platform-configuration-processor/src/main/java/module-info.java @@ -14,6 +14,7 @@ requires java.compiler; requires org.junit.platform.configuration.api; + requires org.junit.platform.commons; provides javax.annotation.processing.Processor with org.junit.platform.configuration.processor.ConfigurationMetadataAnnotationProcessor; } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java new file mode 100644 index 000000000000..a74606b4ef44 --- /dev/null +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java @@ -0,0 +1,80 @@ +/* + * Copyright 2026 the original author or 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.configuration.processor; + +import java.util.ArrayList; +import java.util.List; + +import org.jspecify.annotations.Nullable; + +class ConfigurationMetaData { + +// private final List groups = new ArrayList<>(); + private final List properties = new ArrayList<>(); +// private final List hints = new ArrayList<>(); + + + void addProperty(Property property){ + properties.add(property); + } + +// +// record Group( // +// String name, // +// @Nullable String type, // +// @Nullable String description, // +// @Nullable String sourceType, // +// @Nullable String sourceMethod // +// ) { +// +// } + + record Property( // + String name, // + @Nullable String type, // + @Nullable String description, // + @Nullable String sourceType, // + @Nullable Object defaultValue, // + @Nullable Deprecation deprecation // + ) { + + } + + record Deprecation( // + String level, // + @Nullable String reason, // + @Nullable String replacement, // + @Nullable String since // + ) { + + } + +// record Hint( // +// String name, // +// List values, // +// List providers // +// ) { +// } +// +// record ValueHint( // +// Object value, // +// @Nullable String description // +// ) { +// +// } +// +// record ValueProvider( // +// String name, // +// Object parameters // +// ) { +// +// } +} diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index 6111b41b7399..f8d7273f7e4d 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -10,11 +10,11 @@ package org.junit.platform.configuration.processor; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; import java.io.BufferedWriter; import java.io.OutputStreamWriter; -import java.nio.charset.StandardCharsets; import java.util.Set; import javax.annotation.processing.AbstractProcessor; @@ -22,22 +22,28 @@ import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.SourceVersion; +import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; import javax.tools.StandardLocation; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.configuration.api.ConfigurationProperty; @API(status = API.Status.EXPERIMENTAL) @SupportedAnnotationTypes("org.junit.platform.configuration.api.ConfigurationProperty") public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor { private static final String METADATA_PATH = "META-INF/junit-platform-configuration-metadata.json"; private @Nullable ProcessingEnvironment environment; + private ConfigurationMetaData metaData; @Override public synchronized void init(ProcessingEnvironment environment) { super.init(environment); this.environment = environment; + this.metaData = new ConfigurationMetaData(); } @Override @@ -45,21 +51,63 @@ public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } + private ConfigurationMetaData metaData(){ + return requireNonNull(metaData); + } + @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { + process(roundEnv); if (roundEnv.processingOver()) { - try { - var resource = requireNonNull(environment).getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", - METADATA_PATH); - try (var out = new BufferedWriter(new OutputStreamWriter(resource.openOutputStream(), StandardCharsets.UTF_8))) { - out.write("{}"); - out.write("\n"); - } - } - catch (Exception ex) { - throw new IllegalStateException("Failed to write metadata", ex); - } + writeMetaData(); } return false; } + + private void writeMetaData() { + try { + var resource = requireNonNull(environment).getFiler() // + .createResource(StandardLocation.CLASS_OUTPUT, "", METADATA_PATH); + try (var out = new BufferedWriter(new OutputStreamWriter(resource.openOutputStream(), UTF_8))) { + out.write("{}"); + out.write("\n"); + } + } + catch (Exception ex) { + throw new IllegalStateException("Failed to write metadata", ex); + } + } + + private void process(RoundEnvironment roundEnv) { + roundEnv.getElementsAnnotatedWith(ConfigurationProperty.class).stream() + .filter(VariableElement.class::isInstance) + .map(VariableElement.class::cast) + .map(ConfigurationMetadataAnnotationProcessor::createProperty) + .forEach(element -> metaData().addProperty(element)); + } + + + private static ConfigurationMetaData.Property createProperty(VariableElement element) { + var propertyName = requireStaticConstantValue(element); + return new ConfigurationMetaData.Property(propertyName, null, null, null, + null, null); + } + + + private static boolean isStatic(VariableElement variableElement) { + return variableElement.getModifiers().contains(Modifier.STATIC); + } + + private static String requireStaticConstantValue(VariableElement element) { + Preconditions.condition(isStatic(element), () -> { + var enclosingElement = requireNonNull(element.getEnclosingElement()); + return "Field [%s.%s] must be declared static".formatted(enclosingElement.getSimpleName(), element.getSimpleName()); + }); + var constantValue = element.getConstantValue(); + Preconditions.condition(constantValue instanceof String, () -> { + var enclosingElement = requireNonNull(element.getEnclosingElement()); + return "Field [%s.%s] must have a constant value string".formatted(enclosingElement.getSimpleName(), element.getSimpleName()); + }); + return (String) constantValue; + } } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index a3159e6bf370..d569c263ac3d 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -11,14 +11,18 @@ package org.junit.platform.configuration.processor; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; +import org.assertj.core.api.ThrowableAssert; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.junit.platform.commons.PreconditionViolationException; class ConfigurationMetadataAnnotationProcessorTests { @@ -38,15 +42,40 @@ void setup() { } @Test - void test() throws IOException { - compiler.compile(OneConfigurationProperty.class); + void simpleConfigurationProperty() { + compiler.compile(SimpleConfigurationProperty.class); assertThat(metaData()).isEqualTo(""" {} """); } - private String metaData() throws IOException { - return Files.readString(outputDirectory.resolve(expectedMetadataPath)); + @Test + void configurationPropertyMustBeFinal() { + asserPreconditionViolation(() -> compiler.compile(NonFinalConfigurationProperty.class), + "Field [NonFinalConfigurationProperty.EXAMPLE_PROPERTY_NAME] must have a constant value string"); + } + + @Test + void configurationPropertyMustBeStatic() { + compiler.compile(SimpleConfigurationProperty.class); + assertThat(metaData()).isEqualTo(""" + {} + """); + } + + private static void asserPreconditionViolation(ThrowableAssert.ThrowingCallable throwingCallable, String message) { + assertThatThrownBy(throwingCallable) + .hasRootCauseExactlyInstanceOf(PreconditionViolationException.class) + .hasRootCauseMessage(message); + } + + private String metaData() throws UncheckedIOException { + try { + return Files.readString(outputDirectory.resolve(expectedMetadataPath)); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } } } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/OneConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/NonFinalConfigurationProperty.java similarity index 92% rename from platform-tests/src/test/java/org/junit/platform/configuration/processor/OneConfigurationProperty.java rename to platform-tests/src/test/java/org/junit/platform/configuration/processor/NonFinalConfigurationProperty.java index f0abc6c64dd3..4b4cb136df97 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/OneConfigurationProperty.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/NonFinalConfigurationProperty.java @@ -12,7 +12,7 @@ import org.junit.platform.configuration.api.ConfigurationProperty; -final class OneConfigurationProperty { +final class NonFinalConfigurationProperty { @ConfigurationProperty public static String EXAMPLE_PROPERTY_NAME = "org.example.property"; diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/NonStaticConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/NonStaticConfigurationProperty.java new file mode 100644 index 000000000000..332dfd5d929e --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/NonStaticConfigurationProperty.java @@ -0,0 +1,20 @@ +/* + * Copyright 2026 the original author or 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.configuration.processor; + +import org.junit.platform.configuration.api.ConfigurationProperty; + +final class NonStaticConfigurationProperty { + + @ConfigurationProperty + public final String EXAMPLE_PROPERTY_NAME = "org.example.property"; + +} diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/SimpleConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/SimpleConfigurationProperty.java new file mode 100644 index 000000000000..83b31651b0aa --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/SimpleConfigurationProperty.java @@ -0,0 +1,20 @@ +/* + * Copyright 2026 the original author or 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.configuration.processor; + +import org.junit.platform.configuration.api.ConfigurationProperty; + +final class SimpleConfigurationProperty { + + @ConfigurationProperty + public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; + +} diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt index 65f6236bc4fb..e33205f83333 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt @@ -16,4 +16,4 @@ requires org.apiguardian.api static transitive requires org.jspecify static transitive uses org.junit.platform.commons.support.scanning.ClasspathScanner qualified exports org.junit.platform.commons.logging to org.junit.jupiter.api org.junit.jupiter.engine org.junit.jupiter.migrationsupport org.junit.jupiter.params org.junit.platform.console org.junit.platform.engine org.junit.platform.launcher org.junit.platform.reporting org.junit.platform.suite.api org.junit.platform.suite.engine org.junit.platform.testkit org.junit.vintage.engine -qualified exports org.junit.platform.commons.util to org.junit.jupiter.api org.junit.jupiter.engine org.junit.jupiter.migrationsupport org.junit.jupiter.params org.junit.platform.console org.junit.platform.engine org.junit.platform.launcher org.junit.platform.reporting org.junit.platform.suite.api org.junit.platform.suite.engine org.junit.platform.testkit org.junit.vintage.engine +qualified exports org.junit.platform.commons.util to org.junit.jupiter.api org.junit.jupiter.engine org.junit.jupiter.migrationsupport org.junit.jupiter.params org.junit.platform.configuration.processor org.junit.platform.console org.junit.platform.engine org.junit.platform.launcher org.junit.platform.reporting org.junit.platform.suite.api org.junit.platform.suite.engine org.junit.platform.testkit org.junit.vintage.engine diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt index c5d5087cbe64..158a6774ceea 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt @@ -3,6 +3,7 @@ requires java.base mandated requires java.compiler requires org.apiguardian.api static transitive requires org.jspecify static transitive +requires org.junit.platform.commons requires org.junit.platform.configuration.api provides javax.annotation.processing.Processor with org.junit.platform.configuration.processor.ConfigurationMetadataAnnotationProcessor -contains org.junit.platform.configuration.processor \ No newline at end of file +contains org.junit.platform.configuration.processor From 9a1f8974d3d3c7fe75210a12f4996561796b2be7 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 4 Aug 2026 15:48:50 +0200 Subject: [PATCH 019/117] Add Property.sourceType --- .../processor/ConfigurationMetaData.java | 67 +++++++++---------- ...figurationMetadataAnnotationProcessor.java | 54 +++++++++------ ...ationMetadataAnnotationProcessorTests.java | 38 +++++++---- .../NonStringConfigurationProperty.java | 20 ++++++ 4 files changed, 112 insertions(+), 67 deletions(-) create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/processor/NonStringConfigurationProperty.java diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java index a74606b4ef44..e460345daa7b 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java @@ -17,25 +17,24 @@ class ConfigurationMetaData { -// private final List groups = new ArrayList<>(); + // private final List groups = new ArrayList<>(); private final List properties = new ArrayList<>(); -// private final List hints = new ArrayList<>(); + // private final List hints = new ArrayList<>(); - - void addProperty(Property property){ + void addProperty(Property property) { properties.add(property); } -// -// record Group( // -// String name, // -// @Nullable String type, // -// @Nullable String description, // -// @Nullable String sourceType, // -// @Nullable String sourceMethod // -// ) { -// -// } + // + // record Group( // + // String name, // + // @Nullable String type, // + // @Nullable String description, // + // @Nullable String sourceType, // + // @Nullable String sourceMethod // + // ) { + // + // } record Property( // String name, // @@ -57,24 +56,24 @@ record Deprecation( // } -// record Hint( // -// String name, // -// List values, // -// List providers // -// ) { -// } -// -// record ValueHint( // -// Object value, // -// @Nullable String description // -// ) { -// -// } -// -// record ValueProvider( // -// String name, // -// Object parameters // -// ) { -// -// } + // record Hint( // + // String name, // + // List values, // + // List providers // + // ) { + // } + // + // record ValueHint( // + // Object value, // + // @Nullable String description // + // ) { + // + // } + // + // record ValueProvider( // + // String name, // + // Object parameters // + // ) { + // + // } } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index f8d7273f7e4d..2aa5100f3faa 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -51,7 +51,7 @@ public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } - private ConfigurationMetaData metaData(){ + private ConfigurationMetaData metaData() { return requireNonNull(metaData); } @@ -79,35 +79,49 @@ private void writeMetaData() { } private void process(RoundEnvironment roundEnv) { - roundEnv.getElementsAnnotatedWith(ConfigurationProperty.class).stream() - .filter(VariableElement.class::isInstance) - .map(VariableElement.class::cast) - .map(ConfigurationMetadataAnnotationProcessor::createProperty) + roundEnv.getElementsAnnotatedWith(ConfigurationProperty.class).stream() // + .filter(VariableElement.class::isInstance) // + .map(VariableElement.class::cast) // + .map(ConfigurationMetadataAnnotationProcessor::createProperty) // .forEach(element -> metaData().addProperty(element)); } - private static ConfigurationMetaData.Property createProperty(VariableElement element) { - var propertyName = requireStaticConstantValue(element); - return new ConfigurationMetaData.Property(propertyName, null, null, null, - null, null); + var propertyName = requireConstantStringValue(element); + var enclosingTypeElement = getEnclosingTypeElement(element); + return new ConfigurationMetaData.Property(propertyName, null, null, + enclosingTypeElement.getQualifiedName().toString(), null, null); } + private static String requireConstantStringValue(VariableElement element) { + var enclosingTypeElement = getEnclosingTypeElement(element); + Preconditions.condition(isStatic(element), // + () -> "Field [%s.%s] must be declared static" // + .formatted(enclosingTypeElement.getQualifiedName(), element.getSimpleName())); + Preconditions.condition(isFinal(element), // + () -> "Field [%s.%s] must be declared final" // + .formatted(enclosingTypeElement.getQualifiedName(), element.getSimpleName())); + var constantValue = element.getConstantValue(); + Preconditions.condition(constantValue instanceof String, // + () -> "Field [%s.%s] must have a constant string value" // + .formatted(enclosingTypeElement.getQualifiedName(), element.getSimpleName())); + return (String) constantValue; + } + + private static TypeElement getEnclosingTypeElement(VariableElement element) { + var enclosingElement = element.getEnclosingElement(); + Preconditions.condition(enclosingElement instanceof TypeElement, // + () -> "[%s] did not have an enclosing type element" // + .formatted(element.getSimpleName())); + return (TypeElement) enclosingElement; + } private static boolean isStatic(VariableElement variableElement) { return variableElement.getModifiers().contains(Modifier.STATIC); } - private static String requireStaticConstantValue(VariableElement element) { - Preconditions.condition(isStatic(element), () -> { - var enclosingElement = requireNonNull(element.getEnclosingElement()); - return "Field [%s.%s] must be declared static".formatted(enclosingElement.getSimpleName(), element.getSimpleName()); - }); - var constantValue = element.getConstantValue(); - Preconditions.condition(constantValue instanceof String, () -> { - var enclosingElement = requireNonNull(element.getEnclosingElement()); - return "Field [%s.%s] must have a constant value string".formatted(enclosingElement.getSimpleName(), element.getSimpleName()); - }); - return (String) constantValue; + private static boolean isFinal(VariableElement variableElement) { + return variableElement.getModifiers().contains(Modifier.FINAL); } + } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index d569c263ac3d..06bcdfecb52c 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -20,6 +20,7 @@ import org.assertj.core.api.ThrowableAssert; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.commons.PreconditionViolationException; @@ -49,23 +50,34 @@ void simpleConfigurationProperty() { """); } - @Test - void configurationPropertyMustBeFinal() { - asserPreconditionViolation(() -> compiler.compile(NonFinalConfigurationProperty.class), - "Field [NonFinalConfigurationProperty.EXAMPLE_PROPERTY_NAME] must have a constant value string"); - } + @Nested + class ConfigurationProperty { - @Test - void configurationPropertyMustBeStatic() { - compiler.compile(SimpleConfigurationProperty.class); - assertThat(metaData()).isEqualTo(""" - {} - """); + @Test + void mustBeFinal() { + asserPreconditionViolation(() -> compiler.compile(NonFinalConfigurationProperty.class), + "Field [%s.EXAMPLE_PROPERTY_NAME] must be declared final".formatted( + NonFinalConfigurationProperty.class.getName())); + } + + @Test + void mustBeStatic() { + asserPreconditionViolation(() -> compiler.compile(NonStaticConfigurationProperty.class), + "Field [%s.EXAMPLE_PROPERTY_NAME] must be declared static".formatted( + NonStaticConfigurationProperty.class.getName())); + } + + @Test + void mustBeString() { + asserPreconditionViolation(() -> compiler.compile(NonStringConfigurationProperty.class), + "Field [%s.EXAMPLE_PROPERTY_NAME] must have a constant string value".formatted( + NonStringConfigurationProperty.class.getName())); + } } private static void asserPreconditionViolation(ThrowableAssert.ThrowingCallable throwingCallable, String message) { - assertThatThrownBy(throwingCallable) - .hasRootCauseExactlyInstanceOf(PreconditionViolationException.class) + assertThatThrownBy(throwingCallable) // + .hasRootCauseExactlyInstanceOf(PreconditionViolationException.class) // .hasRootCauseMessage(message); } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/NonStringConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/NonStringConfigurationProperty.java new file mode 100644 index 000000000000..fc941ff635d3 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/NonStringConfigurationProperty.java @@ -0,0 +1,20 @@ +/* + * Copyright 2026 the original author or 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.configuration.processor; + +import org.junit.platform.configuration.api.ConfigurationProperty; + +final class NonStringConfigurationProperty { + + @ConfigurationProperty + public static final Boolean EXAMPLE_PROPERTY_NAME = true; + +} From 7c3df1f2a224969172e64ac049010e7b2868febe Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 4 Aug 2026 16:25:22 +0200 Subject: [PATCH 020/117] Add deprecation and refactor --- ...figurationMetadataAnnotationProcessor.java | 53 +++++++++++++---- .../ClassDeprecatedConfigurationProperty.java | 21 +++++++ ...ationMetadataAnnotationProcessorTests.java | 59 +++++++++++++++---- .../DeprecatedConfigurationProperty.java | 21 +++++++ .../DocumentedConfigurationProperty.java | 23 ++++++++ ...java => MinimalConfigurationProperty.java} | 2 +- .../WithoutConfigurationProperty.java | 15 +++++ 7 files changed, 171 insertions(+), 23 deletions(-) create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/processor/ClassDeprecatedConfigurationProperty.java create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/processor/DeprecatedConfigurationProperty.java create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedConfigurationProperty.java rename platform-tests/src/test/java/org/junit/platform/configuration/processor/{SimpleConfigurationProperty.java => MinimalConfigurationProperty.java} (92%) create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/processor/WithoutConfigurationProperty.java diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index 2aa5100f3faa..df4bb1017aa1 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -37,7 +37,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor { private static final String METADATA_PATH = "META-INF/junit-platform-configuration-metadata.json"; private @Nullable ProcessingEnvironment environment; - private ConfigurationMetaData metaData; + private @Nullable ConfigurationMetaData metaData; @Override public synchronized void init(ProcessingEnvironment environment) { @@ -51,10 +51,6 @@ public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } - private ConfigurationMetaData metaData() { - return requireNonNull(metaData); - } - @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { process(roundEnv); @@ -66,7 +62,7 @@ public boolean process(Set annotations, RoundEnvironment private void writeMetaData() { try { - var resource = requireNonNull(environment).getFiler() // + var resource = environment().getFiler() // .createResource(StandardLocation.CLASS_OUTPUT, "", METADATA_PATH); try (var out = new BufferedWriter(new OutputStreamWriter(resource.openOutputStream(), UTF_8))) { out.write("{}"); @@ -82,18 +78,44 @@ private void process(RoundEnvironment roundEnv) { roundEnv.getElementsAnnotatedWith(ConfigurationProperty.class).stream() // .filter(VariableElement.class::isInstance) // .map(VariableElement.class::cast) // - .map(ConfigurationMetadataAnnotationProcessor::createProperty) // + .map(this::createProperty) // .forEach(element -> metaData().addProperty(element)); } - private static ConfigurationMetaData.Property createProperty(VariableElement element) { - var propertyName = requireConstantStringValue(element); + private ConfigurationMetaData.Property createProperty(VariableElement element) { + return new ConfigurationMetaData.Property( // + processPropertyName(element), // + null, // TODO: + processDescription(element), // + processSourceType(element), // + null, // TODO: + processDeprecation(element) // + ); + } + + private ConfigurationMetaData.@Nullable Deprecation processDeprecation(VariableElement element) { + var deprecated = element.getAnnotation(Deprecated.class); + if (deprecated == null) { + return null; + } + return new ConfigurationMetaData.Deprecation("warn", // + null, // TODO: + null, // TODO: + null // TODO: + ); + } + + private @Nullable String processDescription(VariableElement element) { + // TODO: Clean up doc comment + return environment().getElementUtils().getDocComment(element); + } + + private static String processSourceType(VariableElement element) { var enclosingTypeElement = getEnclosingTypeElement(element); - return new ConfigurationMetaData.Property(propertyName, null, null, - enclosingTypeElement.getQualifiedName().toString(), null, null); + return enclosingTypeElement.getQualifiedName().toString(); } - private static String requireConstantStringValue(VariableElement element) { + private static String processPropertyName(VariableElement element) { var enclosingTypeElement = getEnclosingTypeElement(element); Preconditions.condition(isStatic(element), // () -> "Field [%s.%s] must be declared static" // @@ -124,4 +146,11 @@ private static boolean isFinal(VariableElement variableElement) { return variableElement.getModifiers().contains(Modifier.FINAL); } + private ProcessingEnvironment environment() { + return requireNonNull(environment); + } + + private ConfigurationMetaData metaData() { + return requireNonNull(metaData); + } } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ClassDeprecatedConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ClassDeprecatedConfigurationProperty.java new file mode 100644 index 000000000000..34139695831d --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ClassDeprecatedConfigurationProperty.java @@ -0,0 +1,21 @@ +/* + * Copyright 2026 the original author or 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.configuration.processor; + +import org.junit.platform.configuration.api.ConfigurationProperty; + +@Deprecated +final class ClassDeprecatedConfigurationProperty { + + @ConfigurationProperty + public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; + +} diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index 06bcdfecb52c..1906b0e93ce2 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -17,6 +17,7 @@ import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Optional; import org.assertj.core.api.ThrowableAssert; import org.junit.jupiter.api.BeforeEach; @@ -42,17 +43,51 @@ void setup() { compiler = new TestCompiler(sourceDirectory, outputDirectory, processor); } - @Test - void simpleConfigurationProperty() { - compiler.compile(SimpleConfigurationProperty.class); - assertThat(metaData()).isEqualTo(""" - {} - """); - } - @Nested class ConfigurationProperty { + @Test + void none() { + compiler.compile(WithoutConfigurationProperty.class); + assertThat(metaData()).isEmpty(); + } + + @Test + void minimal() { + compiler.compile(MinimalConfigurationProperty.class); + assertThat(metaData()).contains(""" + {} + """); + } + + @Test + void documented() { + // TODO: Add more complex documentation samples + compiler.compile(DocumentedConfigurationProperty.class); + assertThat(metaData()).contains(""" + {} + """); + } + + @Test + void deprecated() { + // TODO: Inheritance? Meta? + compiler.compile(DeprecatedConfigurationProperty.class); + assertThat(metaData()).contains(""" + {} + """); + } + + @SuppressWarnings("deprecation") + @Test + void classDeprecated() { + // TODO: Inheritance? Meta? + compiler.compile(ClassDeprecatedConfigurationProperty.class); + assertThat(metaData()).contains(""" + {} + """); + } + @Test void mustBeFinal() { asserPreconditionViolation(() -> compiler.compile(NonFinalConfigurationProperty.class), @@ -81,9 +116,13 @@ private static void asserPreconditionViolation(ThrowableAssert.ThrowingCallable .hasRootCauseMessage(message); } - private String metaData() throws UncheckedIOException { + private Optional metaData() throws UncheckedIOException { try { - return Files.readString(outputDirectory.resolve(expectedMetadataPath)); + var metaDataPath = outputDirectory.resolve(expectedMetadataPath); + if (!Files.exists(metaDataPath)) { + return Optional.empty(); + } + return Optional.of(Files.readString(metaDataPath)); } catch (IOException e) { throw new UncheckedIOException(e); diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/DeprecatedConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/DeprecatedConfigurationProperty.java new file mode 100644 index 000000000000..fef6f80ccdab --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/DeprecatedConfigurationProperty.java @@ -0,0 +1,21 @@ +/* + * Copyright 2026 the original author or 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.configuration.processor; + +import org.junit.platform.configuration.api.ConfigurationProperty; + +final class DeprecatedConfigurationProperty { + + @Deprecated + @ConfigurationProperty + public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; + +} diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedConfigurationProperty.java new file mode 100644 index 000000000000..27f0b3fec428 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedConfigurationProperty.java @@ -0,0 +1,23 @@ +/* + * Copyright 2026 the original author or 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.configuration.processor; + +import org.junit.platform.configuration.api.ConfigurationProperty; + +final class DocumentedConfigurationProperty { + + /** + * A brief description of this property. + */ + @ConfigurationProperty + public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; + +} diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/SimpleConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/MinimalConfigurationProperty.java similarity index 92% rename from platform-tests/src/test/java/org/junit/platform/configuration/processor/SimpleConfigurationProperty.java rename to platform-tests/src/test/java/org/junit/platform/configuration/processor/MinimalConfigurationProperty.java index 83b31651b0aa..dab610bad93d 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/SimpleConfigurationProperty.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/MinimalConfigurationProperty.java @@ -12,7 +12,7 @@ import org.junit.platform.configuration.api.ConfigurationProperty; -final class SimpleConfigurationProperty { +final class MinimalConfigurationProperty { @ConfigurationProperty public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/WithoutConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/WithoutConfigurationProperty.java new file mode 100644 index 000000000000..183ae141a3fc --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/WithoutConfigurationProperty.java @@ -0,0 +1,15 @@ +/* + * Copyright 2026 the original author or 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.configuration.processor; + +final class WithoutConfigurationProperty { + +} From d764e3f2f6da864fe1a9f694d421de903497f06f Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Wed, 5 Aug 2026 15:01:47 +0200 Subject: [PATCH 021/117] Remove redundant local variable --- .../ConfigurationMetadataAnnotationProcessor.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index df4bb1017aa1..d2fb3c35ac39 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -36,13 +36,11 @@ @SupportedAnnotationTypes("org.junit.platform.configuration.api.ConfigurationProperty") public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor { private static final String METADATA_PATH = "META-INF/junit-platform-configuration-metadata.json"; - private @Nullable ProcessingEnvironment environment; private @Nullable ConfigurationMetaData metaData; @Override public synchronized void init(ProcessingEnvironment environment) { super.init(environment); - this.environment = environment; this.metaData = new ConfigurationMetaData(); } @@ -62,7 +60,7 @@ public boolean process(Set annotations, RoundEnvironment private void writeMetaData() { try { - var resource = environment().getFiler() // + var resource = processingEnvironment().getFiler() // .createResource(StandardLocation.CLASS_OUTPUT, "", METADATA_PATH); try (var out = new BufferedWriter(new OutputStreamWriter(resource.openOutputStream(), UTF_8))) { out.write("{}"); @@ -107,7 +105,7 @@ private ConfigurationMetaData.Property createProperty(VariableElement element) { private @Nullable String processDescription(VariableElement element) { // TODO: Clean up doc comment - return environment().getElementUtils().getDocComment(element); + return processingEnvironment().getElementUtils().getDocComment(element); } private static String processSourceType(VariableElement element) { @@ -146,8 +144,8 @@ private static boolean isFinal(VariableElement variableElement) { return variableElement.getModifiers().contains(Modifier.FINAL); } - private ProcessingEnvironment environment() { - return requireNonNull(environment); + private ProcessingEnvironment processingEnvironment() { + return requireNonNull(processingEnv); } private ConfigurationMetaData metaData() { From 3b011f7dbb7eda26996aadb6fd1b6f08e471f717 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Wed, 5 Aug 2026 16:15:40 +0200 Subject: [PATCH 022/117] Generate some json output --- gradle/libs.versions.toml | 1 + ...latform-configuration-processor.gradle.kts | 4 + .../src/main/java/module-info.java | 1 + .../processor/ConfigurationMetaData.java | 4 + ...figurationMetadataAnnotationProcessor.java | 22 +++- ...ationMetadataAnnotationProcessorTests.java | 105 ++++++++++++------ ...tform-configuration-processor.expected.txt | 3 +- 7 files changed, 97 insertions(+), 43 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c6bbbc2d0dc3..8a36945c6abf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -36,6 +36,7 @@ fastcsv = { module = "de.siegmar:fastcsv", version = "4.4.0" } groovy = { module = "org.apache.groovy:groovy", version = "5.0.7" } groovy2-bom = { module = "org.codehaus.groovy:groovy-bom", version = "2.5.23" } hamcrest = { module = "org.hamcrest:hamcrest", version = "3.0" } +jackson-databind = { module = "tools.jackson.core:jackson-databind", version.ref = "jackson" } jackson-dataformat-yaml = { module = "tools.jackson.dataformat:jackson-dataformat-yaml", version.ref = "jackson" } jackson-module-kotlin = { module = "tools.jackson.module:jackson-module-kotlin", version.ref = "jackson" } jaxb-api = { module = "jakarta.xml.bind:jakarta.xml.bind-api", version = "4.0.5" } diff --git a/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts b/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts index 8bbc6735b57d..13ae6897a66f 100644 --- a/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts +++ b/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts @@ -1,5 +1,6 @@ plugins { id("junitbuild.java-library-conventions") + id("junitbuild.shadow-conventions") } description = "JUnit Platform Configuration Processor" @@ -11,6 +12,9 @@ dependencies { compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) + + // TODO: Shade, but it's non trivial. + implementation(libs.jackson.databind) } backwardCompatibilityChecks { diff --git a/junit-platform-configuration-processor/src/main/java/module-info.java b/junit-platform-configuration-processor/src/main/java/module-info.java index 7aed42f9559f..63c3344a1b62 100644 --- a/junit-platform-configuration-processor/src/main/java/module-info.java +++ b/junit-platform-configuration-processor/src/main/java/module-info.java @@ -15,6 +15,7 @@ requires java.compiler; requires org.junit.platform.configuration.api; requires org.junit.platform.commons; + requires tools.jackson.databind; provides javax.annotation.processing.Processor with org.junit.platform.configuration.processor.ConfigurationMetadataAnnotationProcessor; } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java index e460345daa7b..f4bbb3ce066f 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java @@ -21,6 +21,10 @@ class ConfigurationMetaData { private final List properties = new ArrayList<>(); // private final List hints = new ArrayList<>(); + public List getProperties() { + return properties; + } + void addProperty(Property property) { properties.add(property); } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index d2fb3c35ac39..bc5da4fd0c87 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -10,6 +10,7 @@ package org.junit.platform.configuration.processor; +import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_ABSENT; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; @@ -32,6 +33,9 @@ import org.junit.platform.commons.util.Preconditions; import org.junit.platform.configuration.api.ConfigurationProperty; +import tools.jackson.core.StreamWriteFeature; +import tools.jackson.databind.json.JsonMapper; + @API(status = API.Status.EXPERIMENTAL) @SupportedAnnotationTypes("org.junit.platform.configuration.api.ConfigurationProperty") public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor { @@ -60,15 +64,21 @@ public boolean process(Set annotations, RoundEnvironment private void writeMetaData() { try { + var mapper = JsonMapper.builder() // + .changeDefaultPropertyInclusion(value -> value // + .withContentInclusion(NON_ABSENT) // + .withValueInclusion(NON_ABSENT) // + ) // + .disable(StreamWriteFeature.AUTO_CLOSE_TARGET) // + .build(); var resource = processingEnvironment().getFiler() // .createResource(StandardLocation.CLASS_OUTPUT, "", METADATA_PATH); try (var out = new BufferedWriter(new OutputStreamWriter(resource.openOutputStream(), UTF_8))) { - out.write("{}"); - out.write("\n"); + mapper.writeValue(out, metaData()); } } catch (Exception ex) { - throw new IllegalStateException("Failed to write metadata", ex); + throw new IllegalStateException("Failed to write metadata to [%s]".formatted(METADATA_PATH), ex); } } @@ -104,8 +114,10 @@ private ConfigurationMetaData.Property createProperty(VariableElement element) { } private @Nullable String processDescription(VariableElement element) { - // TODO: Clean up doc comment - return processingEnvironment().getElementUtils().getDocComment(element); + // TODO: Clean up doc comment more + String docComment = processingEnvironment().getElementUtils().getDocComment(element); + return docComment == null ? null : docComment.trim(); + } private static String processSourceType(VariableElement element) { diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index 1906b0e93ce2..fa51aa72a0af 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -17,10 +17,10 @@ import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Optional; import org.assertj.core.api.ThrowableAssert; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -32,60 +32,94 @@ class ConfigurationMetadataAnnotationProcessorTests { final Path sourceDirectory = Path.of("src/test/java"); - @TempDir - Path outputDirectory; + @Nested + class ConfigurationProperty { - TestCompiler compiler; + @TempDir + Path outputDirectory; - @BeforeEach - void setup() { - var processor = new ConfigurationMetadataAnnotationProcessor(); - compiler = new TestCompiler(sourceDirectory, outputDirectory, processor); - } + TestCompiler compiler; - @Nested - class ConfigurationProperty { + @BeforeEach + void setup() { + var processor = new ConfigurationMetadataAnnotationProcessor(); + compiler = new TestCompiler(sourceDirectory, outputDirectory, processor); + } @Test void none() { compiler.compile(WithoutConfigurationProperty.class); - assertThat(metaData()).isEmpty(); + var metaDataPath = outputDirectory.resolve(expectedMetadataPath); + assertThat(metaDataPath).doesNotExist(); } @Test void minimal() { compiler.compile(MinimalConfigurationProperty.class); - assertThat(metaData()).contains(""" - {} - """); + assertThat(metaData()).isEqualToIgnoringWhitespace(""" + { + "properties": [ + { + "name": "org.example.property", + "sourceType": "org.junit.platform.configuration.processor.MinimalConfigurationProperty" + } + ] + }"""); } @Test void documented() { // TODO: Add more complex documentation samples compiler.compile(DocumentedConfigurationProperty.class); - assertThat(metaData()).contains(""" - {} - """); + assertThat(metaData()).isEqualToIgnoringWhitespace(""" + { + "properties": [ + { + "name": "org.example.property", + "description": "A brief description of this property.", + "sourceType": "org.junit.platform.configuration.processor.DocumentedConfigurationProperty" + } + ] + }"""); } @Test void deprecated() { // TODO: Inheritance? Meta? compiler.compile(DeprecatedConfigurationProperty.class); - assertThat(metaData()).contains(""" - {} - """); + assertThat(metaData()).isEqualToIgnoringWhitespace(""" + { + "properties": [ + { + "name": "org.example.property", + "sourceType": "org.junit.platform.configuration.processor.DeprecatedConfigurationProperty", + "deprecation": { + "level": "warn" + } + } + ] + }"""); } @SuppressWarnings("deprecation") @Test + @Disabled("Not yet implemented") void classDeprecated() { // TODO: Inheritance? Meta? compiler.compile(ClassDeprecatedConfigurationProperty.class); - assertThat(metaData()).contains(""" - {} - """); + assertThat(metaData()).isEqualToIgnoringWhitespace( + """ + { + "properties": [ + { + "name": "org.example.property", + "sourceType": "org.junit.platform.configuration.processor.ClassDeprecatedConfigurationProperty", + "deprecation": { + "level": "warn" + } + } + ] + }"""); } @Test @@ -108,6 +142,16 @@ void mustBeString() { "Field [%s.EXAMPLE_PROPERTY_NAME] must have a constant string value".formatted( NonStringConfigurationProperty.class.getName())); } + + private String metaData() throws UncheckedIOException { + try { + var metaDataPath = outputDirectory.resolve(expectedMetadataPath); + return Files.readString(metaDataPath); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } } private static void asserPreconditionViolation(ThrowableAssert.ThrowingCallable throwingCallable, String message) { @@ -116,17 +160,4 @@ private static void asserPreconditionViolation(ThrowableAssert.ThrowingCallable .hasRootCauseMessage(message); } - private Optional metaData() throws UncheckedIOException { - try { - var metaDataPath = outputDirectory.resolve(expectedMetadataPath); - if (!Files.exists(metaDataPath)) { - return Optional.empty(); - } - return Optional.of(Files.readString(metaDataPath)); - } - catch (IOException e) { - throw new UncheckedIOException(e); - } - } - } diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt index 158a6774ceea..c733374d955f 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt @@ -5,5 +5,6 @@ requires org.apiguardian.api static transitive requires org.jspecify static transitive requires org.junit.platform.commons requires org.junit.platform.configuration.api +requires tools.jackson.databind provides javax.annotation.processing.Processor with org.junit.platform.configuration.processor.ConfigurationMetadataAnnotationProcessor -contains org.junit.platform.configuration.processor +contains org.junit.platform.configuration.processor \ No newline at end of file From dd5fc8c95a903bcb209e7bd50bed152bec6d26a8 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 6 Aug 2026 15:19:36 +0200 Subject: [PATCH 023/117] Use jakarta.json and serialize by hand --- gradle/libs.versions.toml | 3 +- ...latform-configuration-processor.gradle.kts | 5 +- .../src/main/java/module-info.java | 2 +- .../processor/ConfigurationMetaData.java | 80 ++++--- ...figurationMetadataAnnotationProcessor.java | 13 +- .../configuration/processor/JsonWriter.java | 195 ++++++++++++++++++ ...tform-configuration-processor.expected.txt | 2 +- 7 files changed, 250 insertions(+), 50 deletions(-) create mode 100644 junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonWriter.java diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8a36945c6abf..2681feb7cb01 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -36,9 +36,10 @@ fastcsv = { module = "de.siegmar:fastcsv", version = "4.4.0" } groovy = { module = "org.apache.groovy:groovy", version = "5.0.7" } groovy2-bom = { module = "org.codehaus.groovy:groovy-bom", version = "2.5.23" } hamcrest = { module = "org.hamcrest:hamcrest", version = "3.0" } -jackson-databind = { module = "tools.jackson.core:jackson-databind", version.ref = "jackson" } jackson-dataformat-yaml = { module = "tools.jackson.dataformat:jackson-dataformat-yaml", version.ref = "jackson" } jackson-module-kotlin = { module = "tools.jackson.module:jackson-module-kotlin", version.ref = "jackson" } +jakarta-json-api = { module = "jakarta.json:jakarta.json-api", version = "2.1.3" } +jakarta-json-implementation = { module = "org.eclipse.parsson:parsson", version = "1.1.9" } jaxb-api = { module = "jakarta.xml.bind:jakarta.xml.bind-api", version = "4.0.5" } jaxb-runtime = { module = "org.glassfish.jaxb:jaxb-runtime", version = "4.0.9" } jfrunit = { module = "org.moditect.jfrunit:jfrunit-core", version = "1.0.0.Alpha2" } diff --git a/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts b/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts index 13ae6897a66f..cf75f4dfc7a9 100644 --- a/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts +++ b/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts @@ -9,12 +9,15 @@ dependencies { api(platform(projects.junitBom)) api(projects.junitPlatformConfigurationApi) api(projects.junitPlatformCommons) + // TODO: Shade? + api(libs.jakarta.json.api) compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) + // TODO: Check if shadowed artifact is standalone // TODO: Shade, but it's non trivial. - implementation(libs.jackson.databind) + implementation(libs.jakarta.json.implementation) } backwardCompatibilityChecks { diff --git a/junit-platform-configuration-processor/src/main/java/module-info.java b/junit-platform-configuration-processor/src/main/java/module-info.java index 63c3344a1b62..a6cf4fc3f33f 100644 --- a/junit-platform-configuration-processor/src/main/java/module-info.java +++ b/junit-platform-configuration-processor/src/main/java/module-info.java @@ -15,7 +15,7 @@ requires java.compiler; requires org.junit.platform.configuration.api; requires org.junit.platform.commons; - requires tools.jackson.databind; + requires jakarta.json; provides javax.annotation.processing.Processor with org.junit.platform.configuration.processor.ConfigurationMetadataAnnotationProcessor; } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java index f4bbb3ce066f..e26cc7a3da4d 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java @@ -12,45 +12,57 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; import org.jspecify.annotations.Nullable; class ConfigurationMetaData { - // private final List groups = new ArrayList<>(); + private final List groups = new ArrayList<>(); private final List properties = new ArrayList<>(); - // private final List hints = new ArrayList<>(); + private final List hints = new ArrayList<>(); - public List getProperties() { + List properties() { return properties; } + List groups() { + return groups; + } + + List hints() { + return hints; + } + void addProperty(Property property) { properties.add(property); } - // - // record Group( // - // String name, // - // @Nullable String type, // - // @Nullable String description, // - // @Nullable String sourceType, // - // @Nullable String sourceMethod // - // ) { - // - // } + record Group( // + String name, // + @Nullable String type, // + @Nullable String description, // + @Nullable String sourceType, // + @Nullable String sourceMethod // + ) { + + } record Property( // String name, // @Nullable String type, // @Nullable String description, // @Nullable String sourceType, // - @Nullable Object defaultValue, // + @Nullable OneOrManyString defaultValue, // @Nullable Deprecation deprecation // ) { } + interface OneOrManyString { + + } + record Deprecation( // String level, // @Nullable String reason, // @@ -60,24 +72,24 @@ record Deprecation( // } - // record Hint( // - // String name, // - // List values, // - // List providers // - // ) { - // } - // - // record ValueHint( // - // Object value, // - // @Nullable String description // - // ) { - // - // } - // - // record ValueProvider( // - // String name, // - // Object parameters // - // ) { - // - // } + record Hint( // + String name, // + List values, // + List providers // + ) { + } + + record ValueHint( // + String value, // + @Nullable String description // + ) { + + } + + record ValueProvider( // + String name, // + Map parameters // + ) { + + } } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index bc5da4fd0c87..ebe920e8c672 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -10,7 +10,6 @@ package org.junit.platform.configuration.processor; -import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_ABSENT; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; @@ -33,9 +32,6 @@ import org.junit.platform.commons.util.Preconditions; import org.junit.platform.configuration.api.ConfigurationProperty; -import tools.jackson.core.StreamWriteFeature; -import tools.jackson.databind.json.JsonMapper; - @API(status = API.Status.EXPERIMENTAL) @SupportedAnnotationTypes("org.junit.platform.configuration.api.ConfigurationProperty") public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor { @@ -64,17 +60,10 @@ public boolean process(Set annotations, RoundEnvironment private void writeMetaData() { try { - var mapper = JsonMapper.builder() // - .changeDefaultPropertyInclusion(value -> value // - .withContentInclusion(NON_ABSENT) // - .withValueInclusion(NON_ABSENT) // - ) // - .disable(StreamWriteFeature.AUTO_CLOSE_TARGET) // - .build(); var resource = processingEnvironment().getFiler() // .createResource(StandardLocation.CLASS_OUTPUT, "", METADATA_PATH); try (var out = new BufferedWriter(new OutputStreamWriter(resource.openOutputStream(), UTF_8))) { - mapper.writeValue(out, metaData()); + new JsonWriter().writeValue(out, metaData()); } } catch (Exception ex) { diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonWriter.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonWriter.java new file mode 100644 index 000000000000..4b98c6308039 --- /dev/null +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonWriter.java @@ -0,0 +1,195 @@ +/* + * Copyright 2026 the original author or 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.configuration.processor; + +import java.io.Writer; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import org.jspecify.annotations.Nullable; +import org.junit.platform.configuration.processor.ConfigurationMetaData.Deprecation; +import org.junit.platform.configuration.processor.ConfigurationMetaData.Group; +import org.junit.platform.configuration.processor.ConfigurationMetaData.Hint; +import org.junit.platform.configuration.processor.ConfigurationMetaData.Property; +import org.junit.platform.configuration.processor.ConfigurationMetaData.ValueHint; +import org.junit.platform.configuration.processor.ConfigurationMetaData.ValueProvider; + +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonBuilderFactory; +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; + +class JsonWriter { + private final Map config = Map.of(); + private final JsonBuilderFactory factory = Json.createBuilderFactory(config); + + void writeValue(Writer out, ConfigurationMetaData metaData) { + var value = toJsonObject(metaData); + Json.createWriter(out).write(value); + } + + private @Nullable JsonObject toJsonObject(ConfigurationMetaData metaData) { + var builder = factory.createObjectBuilder(); + + var groups = metaData.groups(); + if (!groups.isEmpty()) { + builder.add("groups", toJsonArray(groups, this::toJsonObject)); + } + + var properties = metaData.properties(); + if (!properties.isEmpty()) { + builder.add("properties", toJsonArray(properties, this::toJsonObject)); + } + + var hints = metaData.hints(); + if (!hints.isEmpty()) { + builder.add("hints", toJsonArray(hints, this::toJsonObject)); + } + + return builder.build(); + } + + private JsonObject toJsonObject(Group group) { + var builder = factory.createObjectBuilder(); + + builder.add("name", group.name()); + + var type = group.type(); + if (type != null) { + builder.add("type", type); + } + + var description = group.description(); + if (description != null) { + builder.add("description", description); + } + + var sourceType = group.sourceType(); + if (sourceType != null) { + builder.add("sourceType", sourceType); + } + + var sourceMethod = group.sourceMethod(); + if (sourceMethod != null) { + builder.add("sourceMethod", sourceMethod); + } + return builder.build(); + } + + private JsonObject toJsonObject(Property property) { + var builder = factory.createObjectBuilder(); + builder.add("name", property.name()); + + var type = property.type(); + if (type != null) { + builder.add("type", type); + } + + var description = property.description(); + if (description != null) { + builder.add("description", description); + } + + var sourceType = property.sourceType(); + if (sourceType != null) { + builder.add("sourceType", sourceType); + } + + // TODO: + // var defaultValue = property.defaultValue(); + // if(defaultValue != null){ + // builder.add("defaultValue", toJsonObject(defaultValue)); + // } + + var deprecation = property.deprecation(); + if (deprecation != null) { + builder.add("deprecation", toJsonObject(deprecation)); + } + + return builder.build(); + } + + private JsonObject toJsonObject(Deprecation deprecation) { + var builder = factory.createObjectBuilder(); + + builder.add("level", deprecation.level()); + + var reason = deprecation.reason(); + if (reason != null) { + builder.add("reason", reason); + } + + var replacement = deprecation.replacement(); + if (replacement != null) { + builder.add("replacement", replacement); + } + + var since = deprecation.since(); + if (since != null) { + builder.add("since", since); + } + + return builder.build(); + } + + private JsonObject toJsonObject(Hint hint) { + var builder = factory.createObjectBuilder(); + + builder.add("name", hint.name()); + + var values = hint.values(); + if (!values.isEmpty()) { + builder.add("values", toJsonArray(values, this::toJsonObject)); + } + + var providers = hint.providers(); + if (!providers.isEmpty()) { + builder.add("providers", toJsonArray(providers, this::toJsonObject)); + } + + return builder.build(); + } + + private JsonObject toJsonObject(ValueHint valueHint) { + var builder = factory.createObjectBuilder(); + + builder.add("type", valueHint.value()); + + var description = valueHint.description(); + if (description != null) { + builder.add("description", description); + } + + return builder.build(); + } + + private JsonObject toJsonObject(ValueProvider valueProvider) { + var builder = factory.createObjectBuilder(); + + builder.add("name", valueProvider.name()); + + // TODO: + // var parameters = valueProvider.parameters(); + // if (!parameters.isEmpty()) { + // builder.add("parameters", toJsonObject(parameters)); + // } + + return builder.build(); + } + + private JsonArray toJsonArray(List properties, Function converter) { + var builder = factory.createArrayBuilder(); + properties.forEach(element -> builder.add(converter.apply(element))); + return builder.build(); + } +} diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt index c733374d955f..b9c09021cb23 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt @@ -1,10 +1,10 @@ org.junit.platform.configuration.processor@${version} jar:file:.+/junit-platform-configuration-processor-\d.+\.jar..module-info\.class +requires jakarta.json requires java.base mandated requires java.compiler requires org.apiguardian.api static transitive requires org.jspecify static transitive requires org.junit.platform.commons requires org.junit.platform.configuration.api -requires tools.jackson.databind provides javax.annotation.processing.Processor with org.junit.platform.configuration.processor.ConfigurationMetadataAnnotationProcessor contains org.junit.platform.configuration.processor \ No newline at end of file From 7e7476c43fb43b272cb33196c50a6502d9d1633b Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 6 Aug 2026 15:26:54 +0200 Subject: [PATCH 024/117] Use JSON syntax highlighting in tests --- ...figurationMetadataAnnotationProcessorTests.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index fa51aa72a0af..7ce4c00d85d8 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -18,7 +18,9 @@ import java.nio.file.Files; import java.nio.file.Path; +import org.assertj.core.api.AbstractStringAssert; import org.assertj.core.api.ThrowableAssert; +import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; @@ -56,7 +58,7 @@ void none() { @Test void minimal() { compiler.compile(MinimalConfigurationProperty.class); - assertThat(metaData()).isEqualToIgnoringWhitespace(""" + assertMetaDataIsEqualTo(""" { "properties": [ { @@ -71,7 +73,7 @@ void minimal() { void documented() { // TODO: Add more complex documentation samples compiler.compile(DocumentedConfigurationProperty.class); - assertThat(metaData()).isEqualToIgnoringWhitespace(""" + assertMetaDataIsEqualTo(""" { "properties": [ { @@ -87,7 +89,7 @@ void documented() { void deprecated() { // TODO: Inheritance? Meta? compiler.compile(DeprecatedConfigurationProperty.class); - assertThat(metaData()).isEqualToIgnoringWhitespace(""" + assertMetaDataIsEqualTo(""" { "properties": [ { @@ -107,7 +109,7 @@ void deprecated() { void classDeprecated() { // TODO: Inheritance? Meta? compiler.compile(ClassDeprecatedConfigurationProperty.class); - assertThat(metaData()).isEqualToIgnoringWhitespace( + assertMetaDataIsEqualTo( """ { "properties": [ @@ -143,6 +145,10 @@ void mustBeString() { NonStringConfigurationProperty.class.getName())); } + private AbstractStringAssert assertMetaDataIsEqualTo(@Language("JSON") String json) { + return assertThat(metaData()).isEqualToIgnoringWhitespace(json); + } + private String metaData() throws UncheckedIOException { try { var metaDataPath = outputDirectory.resolve(expectedMetadataPath); From 9381ee15968a8c9b04e41b35279612b829824fa7 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 8 Aug 2026 11:42:44 +0200 Subject: [PATCH 025/117] Shadow jakarta.json and parsson into processor jar --- documentation/documentation.gradle.kts | 3 +- ...latform-configuration-processor.gradle.kts | 42 +++++++++++++++++-- .../src/main/java/module-info.java | 1 - .../platform-tooling-support-tests.gradle.kts | 1 + ...tform-configuration-processor.expected.txt | 1 - .../tests/ModularCompilationTests.java | 4 +- 6 files changed, 44 insertions(+), 8 deletions(-) diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index 6ad1164cea04..557f95ea7bc8 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -378,10 +378,11 @@ tasks { args.addAll( "--module", modularProjects.joinToString(",") { it.javaModuleName }, - "--add-modules", "info.picocli,org.opentest4j.reporting.events,de.siegmar.fastcsv", + "--add-modules", "info.picocli,org.opentest4j.reporting.events,de.siegmar.fastcsv,jakarta.json", "--add-reads", "org.junit.platform.console=info.picocli", "--add-reads", "org.junit.platform.reporting=org.opentest4j.reporting.events", "--add-reads", "org.junit.jupiter.params=de.siegmar.fastcsv", + "--add-reads", "org.junit.platform.configuration.processor=jakarta.json", ) outputDirectory = layout.buildDirectory.dir("docs/javadoc") diff --git a/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts b/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts index cf75f4dfc7a9..5a027dd0cbf4 100644 --- a/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts +++ b/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts @@ -1,3 +1,5 @@ +import junitbuild.extensions.javaModuleName + plugins { id("junitbuild.java-library-conventions") id("junitbuild.shadow-conventions") @@ -9,17 +11,49 @@ dependencies { api(platform(projects.junitBom)) api(projects.junitPlatformConfigurationApi) api(projects.junitPlatformCommons) - // TODO: Shade? - api(libs.jakarta.json.api) compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) + shadowed(libs.jakarta.json.api) // TODO: Check if shadowed artifact is standalone - // TODO: Shade, but it's non trivial. - implementation(libs.jakarta.json.implementation) + shadowed(libs.jakarta.json.implementation) } backwardCompatibilityChecks { enabled = false // not yet released } + +tasks { + compileJava { + options.compilerArgs.addAll(listOf( + "--add-modules", "jakarta.json", + "--add-reads", "${javaModuleName}=jakarta.json" + )) + } + javadoc { + (options as StandardJavadocDocletOptions).apply { + addStringOption("-add-modules", "jakarta.json") + addStringOption("-add-reads", "${javaModuleName}=jakarta.json") + } + } + val extractLicenses = register("extractLicenses", Sync::class) { + val classPathElements = configurations.shadowedClasspath.flatMap { it.elements } + from(zipTree(classPathElements.map { it.single { file -> file.asFile.name.contains("jakarta.json-api") } })) { + include("META-INF/LICENSE.md") + rename { "LICENSE-jakarta-json.md" } + } + from(zipTree(classPathElements.map { it.single { file -> file.asFile.name.contains("parsson") } })) { + include("META-INF/LICENSE.md") + rename { "LICENSE-parsson.md" } + } + into(layout.buildDirectory.dir("licenses")) + } + shadowJar { + relocate("jakarta.json", "org.junit.platform.configuration.processor.shadow.jakarta.json") + relocate("org.eclipse.parsson", "org.junit.platform.configuration.processor.shadow.org.eclipse.parsson") + exclude("META-INF/LICENSE.md", "META-INF/NOTICE.md") + from(extractLicenses) + mergeServiceFiles() + } +} diff --git a/junit-platform-configuration-processor/src/main/java/module-info.java b/junit-platform-configuration-processor/src/main/java/module-info.java index a6cf4fc3f33f..7aed42f9559f 100644 --- a/junit-platform-configuration-processor/src/main/java/module-info.java +++ b/junit-platform-configuration-processor/src/main/java/module-info.java @@ -15,7 +15,6 @@ requires java.compiler; requires org.junit.platform.configuration.api; requires org.junit.platform.commons; - requires jakarta.json; provides javax.annotation.processing.Processor with org.junit.platform.configuration.processor.ConfigurationMetadataAnnotationProcessor; } 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 d07005fedaee..d21761bb82b3 100644 --- a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts +++ b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts @@ -75,6 +75,7 @@ dependencies { thirdPartyJars(libs.apiguardian) thirdPartyJars(libs.fastcsv) thirdPartyJars(libs.hamcrest) + thirdPartyJars(libs.jakarta.json.api) thirdPartyJars(libs.jimfs) thirdPartyJars(libs.jspecify) thirdPartyJars(kotlin("stdlib")) diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt index b9c09021cb23..7c09f55deb96 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt @@ -1,5 +1,4 @@ org.junit.platform.configuration.processor@${version} jar:file:.+/junit-platform-configuration-processor-\d.+\.jar..module-info\.class -requires jakarta.json requires java.base mandated requires java.compiler requires org.apiguardian.api static transitive diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularCompilationTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularCompilationTests.java index 00c572a25037..687c10c171fb 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularCompilationTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularCompilationTests.java @@ -56,7 +56,9 @@ void compileAllJUnitModules(@TempDir Path workspace, @FilePrefix("javac") Output .addArguments("--add-modules", "org.opentest4j.reporting.events") // .addArguments("--add-reads", "org.junit.platform.reporting=org.opentest4j.reporting.events") // .addArguments("--add-modules", "de.siegmar.fastcsv") // - .addArguments("--add-reads", "org.junit.jupiter.params=de.siegmar.fastcsv") + .addArguments("--add-reads", "org.junit.jupiter.params=de.siegmar.fastcsv") // + .addArguments("--add-modules", "jakarta.json") // + .addArguments("--add-reads", "org.junit.platform.configuration.processor=jakarta.json") // modules to compile .addArguments("--module", String.join(",", moduleNames)) // .redirectOutput(javacOutputFiles) // From 3cfa463067cbc64c49dfdb2d286bcf152997d1aa Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 8 Aug 2026 11:43:05 +0200 Subject: [PATCH 026/117] Fix ArchUnit check --- .../java/platform/tooling/support/tests/ArchUnitTests.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java b/platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java index 026711669ebc..2e595ea9bc6f 100644 --- a/platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java +++ b/platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java @@ -183,7 +183,8 @@ void freeOfPackageCycles(JavaClasses classes) throws Exception { @ArchTest void avoidJavaUtilLogging(JavaClasses classes) { // LoggerFactory.java:80 -> sets field LoggerFactory$DelegatingLogger.julLogger - var subset = classes.that(are(not(name("org.junit.platform.commons.logging.LoggerFactory$DelegatingLogger")))); + var subset = classes.that(are(not( + name("org.junit.platform.commons.logging.LoggerFactory$DelegatingLogger").or(nameContaining(".shadow."))))); GeneralCodingRules.NO_CLASSES_SHOULD_USE_JAVA_UTIL_LOGGING.check(subset); } From 94a9f04c630684afb9e8805ec4c39765c5256336 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 9 Aug 2026 13:05:20 +0200 Subject: [PATCH 027/117] Fix `aggregateJavadocs` task --- documentation/documentation.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index ba7a91e782c7..74a30a9ce3d1 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -101,6 +101,7 @@ dependencies { javadocClasspath(libs.picocli) javadocClasspath(libs.fastcsv) javadocClasspath(libs.openTestReporting.events) + javadocClasspath(libs.jakarta.json.api) javadocClasspath(libs.kotlinx.coroutines.core) javadocClasspath(kotlin("stdlib")) javadocClasspath(kotlin("reflect")) From 433b3b94852a49096d74e985e68ff4a85df42f91 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 9 Aug 2026 13:48:01 +0200 Subject: [PATCH 028/117] Drop dependency on junit-platform-commons --- .../junit-platform-configuration-api.gradle.kts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/junit-platform-configuration-api/junit-platform-configuration-api.gradle.kts b/junit-platform-configuration-api/junit-platform-configuration-api.gradle.kts index fc03f296f01c..ae69a8b20955 100644 --- a/junit-platform-configuration-api/junit-platform-configuration-api.gradle.kts +++ b/junit-platform-configuration-api/junit-platform-configuration-api.gradle.kts @@ -6,7 +6,6 @@ description = "JUnit Platform Configuration API" dependencies { api(platform(projects.junitBom)) - api(projects.junitPlatformCommons) compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) @@ -15,3 +14,16 @@ dependencies { backwardCompatibilityChecks { enabled = false // not yet released } + +tasks.jar { + bundle { + bnd( + """ + Import-Package: \ + ${extra["importAPIGuardian"]},\ + ${extra["importJSpecify"]},\ + * + """ + ) + } +} From 525aa1cbbd57792bb4a4e5689cb9f8bca3f266b2 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 9 Aug 2026 14:41:59 +0200 Subject: [PATCH 029/117] Merge metadata files for standalone jar --- gradle/plugins/common/build.gradle.kts | 1 + ...ConfigurationMetadataMergingTransformer.kt | 64 +++++++++++++++++++ ...nit-platform-console-standalone.gradle.kts | 6 +- 3 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 gradle/plugins/common/src/main/kotlin/junitbuild/shadow/ConfigurationMetadataMergingTransformer.kt diff --git a/gradle/plugins/common/build.gradle.kts b/gradle/plugins/common/build.gradle.kts index 3426a12782cc..cbefb1397cdc 100644 --- a/gradle/plugins/common/build.gradle.kts +++ b/gradle/plugins/common/build.gradle.kts @@ -10,6 +10,7 @@ dependencies { implementation("junitbuild.base:build-parameters") implementation(projects.backwardCompatibility) implementation(projects.javadoc) + implementation(libs.jackson.databind) implementation(libs.plugins.kotlin.markerCoordinates) implementation(libs.plugins.bnd.markerCoordinates) implementation(libs.plugins.develocity.markerCoordinates) diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/shadow/ConfigurationMetadataMergingTransformer.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/shadow/ConfigurationMetadataMergingTransformer.kt new file mode 100644 index 000000000000..bb447e067e00 --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild/shadow/ConfigurationMetadataMergingTransformer.kt @@ -0,0 +1,64 @@ +package junitbuild.shadow + +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import com.github.jengelman.gradle.plugins.shadow.transformers.ResourceTransformer +import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext +import org.apache.tools.zip.ZipEntry +import org.apache.tools.zip.ZipOutputStream +import org.gradle.api.file.FileTreeElement +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import tools.jackson.databind.json.JsonMapper +import tools.jackson.databind.node.ArrayNode +import tools.jackson.databind.node.ObjectNode +import javax.inject.Inject + +/** + * Merges JUnit/Spring configuration metadata files by concatenating the array + * values of their top-level fields (`groups`, `properties`, and `hints`). + * + * @see Configuration Metadata format + */ +abstract class ConfigurationMetadataMergingTransformer @Inject constructor( + override val objectFactory: ObjectFactory +) : ResourceTransformer { + + @get:Input + abstract val resource: Property + + private var mergedTree: ObjectNode? = null + + override fun canTransformResource(element: FileTreeElement): Boolean { + return resource.get().equals(element.path, ignoreCase = true) + } + + override fun transform(context: TransformerContext) { + val tree = JsonMapper().readTree(context.inputStream) as ObjectNode + val merged = mergedTree + if (merged == null) { + mergedTree = tree + } else { + tree.properties().forEach { (name, value) -> + val existing = merged.get(name) + if (existing is ArrayNode && value is ArrayNode) { + existing.addAll(value) + } else { + merged.set(name, value) + } + } + } + } + + override fun hasTransformedResource(): Boolean = mergedTree != null + + override fun modifyOutputStream(os: ZipOutputStream, preserveFileTimestamps: Boolean) { + val entry = ZipEntry(resource.get()) + entry.time = ShadowJar.CONSTANT_TIME_FOR_ZIP_ENTRIES + os.putNextEntry(entry) + os.write(JsonMapper().writeValueAsBytes(mergedTree)) + os.closeEntry() + mergedTree = null + } + +} 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 05d127a64834..31bd2d475ccf 100644 --- a/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts +++ b/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts @@ -1,5 +1,6 @@ import junitbuild.extensions.withArchiveOperations import junitbuild.java.WriteArtifactsFile +import junitbuild.shadow.ConfigurationMetadataMergingTransformer plugins { id("junitbuild.java-library-conventions") @@ -58,8 +59,9 @@ tasks { // https://github.com/junit-team/junit-framework/issues/2557 // exclude compiled module declarations from any source (e.g. /*, /META-INF/versions/N/*) exclude("**/module-info.class") - // TODO: merge meta data files? - exclude("META-INF/junit-platform-configuration-metadata.json") + transform(ConfigurationMetadataMergingTransformer::class.java) { + resource = "META-INF/junit-platform-configuration-metadata.json" + } // https://github.com/junit-team/junit-framework/issues/761 // prevent duplicates, add 3rd-party licenses explicitly exclude("**/COPYRIGHT*") From fa5ab928a74230f0944dc741610b8bfbc6f96cf5 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 9 Aug 2026 15:08:11 +0200 Subject: [PATCH 030/117] Fail if attempting to merge non-array nodes --- .../shadow/ConfigurationMetadataMergingTransformer.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/shadow/ConfigurationMetadataMergingTransformer.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/shadow/ConfigurationMetadataMergingTransformer.kt index bb447e067e00..aa19c0068f02 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild/shadow/ConfigurationMetadataMergingTransformer.kt +++ b/gradle/plugins/common/src/main/kotlin/junitbuild/shadow/ConfigurationMetadataMergingTransformer.kt @@ -40,11 +40,13 @@ abstract class ConfigurationMetadataMergingTransformer @Inject constructor( mergedTree = tree } else { tree.properties().forEach { (name, value) -> + value as ArrayNode val existing = merged.get(name) - if (existing is ArrayNode && value is ArrayNode) { - existing.addAll(value) - } else { + if (existing == null) { merged.set(name, value) + } else { + existing as ArrayNode + existing.addAll(value) } } } From d708416d70f8e69290df10f4dcdeb0bfadffcc09 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 6 Aug 2026 15:37:20 +0200 Subject: [PATCH 031/117] Extract explicit type for deprecation --- .../processor/ConfigurationMetaData.java | 19 +++++++++++++++++-- ...figurationMetadataAnnotationProcessor.java | 4 +++- .../configuration/processor/JsonWriter.java | 6 +++++- ...ationMetadataAnnotationProcessorTests.java | 2 +- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java index e26cc7a3da4d..758f8b4d2f52 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java @@ -64,12 +64,27 @@ interface OneOrManyString { } record Deprecation( // - String level, // + Level level, // @Nullable String reason, // @Nullable String replacement, // @Nullable String since // - ) { + + ) { + + enum Level { + WARNING("warning"), ERROR("error"); + + private final String value; + + Level(String value) { + this.value = value; + } + + String value() { + return value; + } + } } record Hint( // diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index ebe920e8c672..43549abc5d85 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -12,6 +12,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; +import static org.junit.platform.configuration.processor.ConfigurationMetaData.Deprecation.Level.WARNING; import java.io.BufferedWriter; import java.io.OutputStreamWriter; @@ -95,7 +96,8 @@ private ConfigurationMetaData.Property createProperty(VariableElement element) { if (deprecated == null) { return null; } - return new ConfigurationMetaData.Deprecation("warn", // + return new ConfigurationMetaData.Deprecation( // + WARNING, // null, // TODO: null, // TODO: null // TODO: diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonWriter.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonWriter.java index 4b98c6308039..b2cb41506eb4 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonWriter.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonWriter.java @@ -122,7 +122,7 @@ private JsonObject toJsonObject(Property property) { private JsonObject toJsonObject(Deprecation deprecation) { var builder = factory.createObjectBuilder(); - builder.add("level", deprecation.level()); + builder.add("level", toJsonValue(deprecation.level())); var reason = deprecation.reason(); if (reason != null) { @@ -142,6 +142,10 @@ private JsonObject toJsonObject(Deprecation deprecation) { return builder.build(); } + private String toJsonValue(Deprecation.Level level) { + return level.value(); + } + private JsonObject toJsonObject(Hint hint) { var builder = factory.createObjectBuilder(); diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index 7ce4c00d85d8..7bd592576fa4 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -96,7 +96,7 @@ void deprecated() { "name": "org.example.property", "sourceType": "org.junit.platform.configuration.processor.DeprecatedConfigurationProperty", "deprecation": { - "level": "warn" + "level": "warning" } } ] From bf68047dfb7d67bea3d379d36964d9157b51dd37 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 6 Aug 2026 15:42:32 +0200 Subject: [PATCH 032/117] Deprecation.Warning level can be omitted --- .../configuration/processor/ConfigurationMetaData.java | 5 ++--- .../ConfigurationMetadataAnnotationProcessor.java | 2 +- .../platform/configuration/processor/JsonWriter.java | 8 ++++++-- .../ConfigurationMetadataAnnotationProcessorTests.java | 9 +++------ 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java index 758f8b4d2f52..50e061e76629 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java @@ -64,13 +64,12 @@ interface OneOrManyString { } record Deprecation( // - Level level, // + @Nullable Level level, // @Nullable String reason, // @Nullable String replacement, // @Nullable String since // - - ) { + ) { enum Level { WARNING("warning"), ERROR("error"); diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index 43549abc5d85..f1ca68e98560 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -97,7 +97,7 @@ private ConfigurationMetaData.Property createProperty(VariableElement element) { return null; } return new ConfigurationMetaData.Deprecation( // - WARNING, // + null, // null, // TODO: null, // TODO: null // TODO: diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonWriter.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonWriter.java index b2cb41506eb4..837cdee45741 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonWriter.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonWriter.java @@ -17,6 +17,7 @@ import org.jspecify.annotations.Nullable; import org.junit.platform.configuration.processor.ConfigurationMetaData.Deprecation; +import org.junit.platform.configuration.processor.ConfigurationMetaData.Deprecation.Level; import org.junit.platform.configuration.processor.ConfigurationMetaData.Group; import org.junit.platform.configuration.processor.ConfigurationMetaData.Hint; import org.junit.platform.configuration.processor.ConfigurationMetaData.Property; @@ -122,7 +123,10 @@ private JsonObject toJsonObject(Property property) { private JsonObject toJsonObject(Deprecation deprecation) { var builder = factory.createObjectBuilder(); - builder.add("level", toJsonValue(deprecation.level())); + var level = deprecation.level(); + if (level != null) { + builder.add("level", toJsonValue(level)); + } var reason = deprecation.reason(); if (reason != null) { @@ -142,7 +146,7 @@ private JsonObject toJsonObject(Deprecation deprecation) { return builder.build(); } - private String toJsonValue(Deprecation.Level level) { + private String toJsonValue(Level level) { return level.value(); } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index 7bd592576fa4..3dbfe84ab612 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -88,6 +88,7 @@ void documented() { @Test void deprecated() { // TODO: Inheritance? Meta? + // TODO: Warning level? compiler.compile(DeprecatedConfigurationProperty.class); assertMetaDataIsEqualTo(""" { @@ -95,9 +96,7 @@ void deprecated() { { "name": "org.example.property", "sourceType": "org.junit.platform.configuration.processor.DeprecatedConfigurationProperty", - "deprecation": { - "level": "warning" - } + "deprecation": { } } ] }"""); @@ -116,9 +115,7 @@ void classDeprecated() { { "name": "org.example.property", "sourceType": "org.junit.platform.configuration.processor.ClassDeprecatedConfigurationProperty", - "deprecation": { - "level": "warn" - } + "deprecation": { } } ] }"""); From 5b0eef0c2da6cd4bcef066e16ae4c79d18b8bf12 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 6 Aug 2026 16:05:35 +0200 Subject: [PATCH 033/117] Remove ``: {@value}`` from doc comments --- ...figurationMetadataAnnotationProcessor.java | 13 ++++++++--- ...ationMetadataAnnotationProcessorTests.java | 17 +++++++++++++- ...entedWithAtValueConfigurationProperty.java | 23 +++++++++++++++++++ 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithAtValueConfigurationProperty.java diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index f1ca68e98560..3a8a44f0ce24 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -105,10 +105,17 @@ private ConfigurationMetaData.Property createProperty(VariableElement element) { } private @Nullable String processDescription(VariableElement element) { - // TODO: Clean up doc comment more - String docComment = processingEnvironment().getElementUtils().getDocComment(element); - return docComment == null ? null : docComment.trim(); + var docComment = processingEnvironment().getElementUtils().getDocComment(element); + if (docComment == null) { + return null; + } + return cleanupDocComment(docComment); + } + private static String cleanupDocComment(String docComment) { + return docComment // + .replaceAll(": \\{@value}\\.?", ".") // + .trim(); } private static String processSourceType(VariableElement element) { diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index 3dbfe84ab612..30d12f698c14 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -71,7 +71,6 @@ void minimal() { @Test void documented() { - // TODO: Add more complex documentation samples compiler.compile(DocumentedConfigurationProperty.class); assertMetaDataIsEqualTo(""" { @@ -85,6 +84,22 @@ void documented() { }"""); } + @Test + void documentedWithAtValue() { + compiler.compile(DocumentedWithAtValueConfigurationProperty.class); + assertMetaDataIsEqualTo( + """ + { + "properties": [ + { + "name": "org.example.property", + "description": "A brief description of this property.", + "sourceType": "org.junit.platform.configuration.processor.DocumentedWithAtValueConfigurationProperty" + } + ] + }"""); + } + @Test void deprecated() { // TODO: Inheritance? Meta? diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithAtValueConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithAtValueConfigurationProperty.java new file mode 100644 index 000000000000..7068f8424b37 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithAtValueConfigurationProperty.java @@ -0,0 +1,23 @@ +/* + * Copyright 2026 the original author or 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.configuration.processor; + +import org.junit.platform.configuration.api.ConfigurationProperty; + +final class DocumentedWithAtValueConfigurationProperty { + + /** + * A brief description of this property: {@value}. + */ + @ConfigurationProperty + public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; + +} From 8d46a8b76974334d60d4adec589e0d07981e14d2 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 6 Aug 2026 16:27:57 +0200 Subject: [PATCH 034/117] Only use the first paragraph of the documentation --- ...figurationMetadataAnnotationProcessor.java | 16 +++-- ...ationMetadataAnnotationProcessorTests.java | 68 ++++++++++++++++--- ...mentedWithHeaderConfigurationProperty.java | 27 ++++++++ ...edWithMultiLinesConfigurationProperty.java | 24 +++++++ ...ltipleParagraphsConfigurationProperty.java | 25 +++++++ 5 files changed, 145 insertions(+), 15 deletions(-) create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithHeaderConfigurationProperty.java create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithMultiLinesConfigurationProperty.java create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithMultipleParagraphsConfigurationProperty.java diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index 3a8a44f0ce24..ef9a3cd4c770 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -17,6 +17,7 @@ import java.io.BufferedWriter; import java.io.OutputStreamWriter; import java.util.Set; +import java.util.regex.Pattern; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; @@ -106,14 +107,19 @@ private ConfigurationMetaData.Property createProperty(VariableElement element) { private @Nullable String processDescription(VariableElement element) { var docComment = processingEnvironment().getElementUtils().getDocComment(element); - if (docComment == null) { - return null; - } - return cleanupDocComment(docComment); + return docComment == null ? null : cleanupDocComment(docComment); } private static String cleanupDocComment(String docComment) { - return docComment // + // TODO: Creating patterns over and over is not very efficient + var matcher = Pattern.compile("

|").matcher(docComment); + var firstParagraph = !matcher.find() ? docComment : docComment.substring(0, matcher.start()); + return firstParagraph // + // Replace newlines with space + .replaceAll("[\n\r]", " ") // + // Merge multiple spaces + .replaceAll(" +", " ") // + // Replace the `: {@value}` conventional syntax. .replaceAll(": \\{@value}\\.?", ".") // .trim(); } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index 30d12f698c14..a06a29bfda33 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -75,11 +75,11 @@ void documented() { assertMetaDataIsEqualTo(""" { "properties": [ - { - "name": "org.example.property", - "description": "A brief description of this property.", - "sourceType": "org.junit.platform.configuration.processor.DocumentedConfigurationProperty" - } + { + "name": "org.example.property", + "description": "A brief description of this property.", + "sourceType": "org.junit.platform.configuration.processor.DocumentedConfigurationProperty" + } ] }"""); } @@ -91,11 +91,59 @@ void documentedWithAtValue() { """ { "properties": [ - { - "name": "org.example.property", - "description": "A brief description of this property.", - "sourceType": "org.junit.platform.configuration.processor.DocumentedWithAtValueConfigurationProperty" - } + { + "name": "org.example.property", + "description": "A brief description of this property.", + "sourceType": "org.junit.platform.configuration.processor.DocumentedWithAtValueConfigurationProperty" + } + ] + }"""); + } + + @Test + void documentedWithMultipleLines() { + compiler.compile(DocumentedWithMultiLinesConfigurationProperty.class); + assertMetaDataIsEqualTo( + """ + { + "properties": [ + { + "name": "org.example.property", + "description": "A brief multi-line description of this property.", + "sourceType": "org.junit.platform.configuration.processor.DocumentedWithMultiLinesConfigurationProperty" + } + ] + }"""); + } + + @Test + void documentedWithMultipleParagraphs() { + compiler.compile(DocumentedWithMultipleParagraphsConfigurationProperty.class); + assertMetaDataIsEqualTo( + """ + { + "properties": [ + { + "name": "org.example.property", + "description": "A brief description of this property.", + "sourceType": "org.junit.platform.configuration.processor.DocumentedWithMultipleParagraphsConfigurationProperty" + } + ] + }"""); + } + + @Test + void documentedWithHeader() { + compiler.compile(DocumentedWithHeaderConfigurationProperty.class); + assertMetaDataIsEqualTo( + """ + { + "properties": [ + { + "name": "org.example.property", + "description": "A brief description of this property.", + "sourceType": "org.junit.platform.configuration.processor.DocumentedWithHeaderConfigurationProperty" + } ] }"""); } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithHeaderConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithHeaderConfigurationProperty.java new file mode 100644 index 000000000000..383c036ba134 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithHeaderConfigurationProperty.java @@ -0,0 +1,27 @@ +/* + * Copyright 2026 the original author or 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.configuration.processor; + +import org.junit.platform.configuration.api.ConfigurationProperty; + +final class DocumentedWithHeaderConfigurationProperty { + + /** + * A brief description of this property. + * + *

Examples

+ * + * Followed by an additional paragraph. + */ + @ConfigurationProperty + public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; + +} diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithMultiLinesConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithMultiLinesConfigurationProperty.java new file mode 100644 index 000000000000..e88a54e229d6 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithMultiLinesConfigurationProperty.java @@ -0,0 +1,24 @@ +/* + * Copyright 2026 the original author or 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.configuration.processor; + +import org.junit.platform.configuration.api.ConfigurationProperty; + +final class DocumentedWithMultiLinesConfigurationProperty { + + /** + * A brief multi-line description of + * this property: {@value}. + */ + @ConfigurationProperty + public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; + +} diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithMultipleParagraphsConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithMultipleParagraphsConfigurationProperty.java new file mode 100644 index 000000000000..0e2da89ddfdf --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithMultipleParagraphsConfigurationProperty.java @@ -0,0 +1,25 @@ +/* + * Copyright 2026 the original author or 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.configuration.processor; + +import org.junit.platform.configuration.api.ConfigurationProperty; + +final class DocumentedWithMultipleParagraphsConfigurationProperty { + + /** + * A brief description of this property. + * + *

Followed by an additional paragraph. + */ + @ConfigurationProperty + public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; + +} From b8e923066eefd18e644ae61b0b61bdf1d7ae35b8 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 6 Aug 2026 16:33:11 +0200 Subject: [PATCH 035/117] Add note about error handling --- .../processor/ConfigurationMetadataAnnotationProcessor.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index ef9a3cd4c770..6e44a413c331 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -54,6 +54,7 @@ public SourceVersion getSupportedSourceVersion() { @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { process(roundEnv); + if (roundEnv.processingOver()) { writeMetaData(); } @@ -130,6 +131,7 @@ private static String processSourceType(VariableElement element) { } private static String processPropertyName(VariableElement element) { + // TODO: Report preconditions problems with processingEnvironment().getMessager().printMessage() instead. var enclosingTypeElement = getEnclosingTypeElement(element); Preconditions.condition(isStatic(element), // () -> "Field [%s.%s] must be declared static" // From d6c4cc29e8cdee5647afbf0948cbafa74a05f055 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 6 Aug 2026 18:16:11 +0200 Subject: [PATCH 036/117] Process Property.type --- .../api/ConfigurationProperty.java | 8 +++ ...figurationMetadataAnnotationProcessor.java | 53 ++++++++++++++++--- ...ationMetadataAnnotationProcessorTests.java | 15 ++++++ .../StringTypeConfigurationProperty.java | 20 +++++++ 4 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/processor/StringTypeConfigurationProperty.java diff --git a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationProperty.java b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationProperty.java index bb0aed569f76..381eb2f27924 100644 --- a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationProperty.java +++ b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationProperty.java @@ -21,4 +21,12 @@ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.FIELD) public @interface ConfigurationProperty { + + /** + * The data type of the property. For example {@link String}, + * {@link Integer}, {@link Boolean} or {@link Class}. + * + * @return the data type of the property. + */ + Class type() default Void.class; } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index 6e44a413c331..c4659188375c 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -12,10 +12,10 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; -import static org.junit.platform.configuration.processor.ConfigurationMetaData.Deprecation.Level.WARNING; import java.io.BufferedWriter; import java.io.OutputStreamWriter; +import java.lang.annotation.Annotation; import java.util.Set; import java.util.regex.Pattern; @@ -24,6 +24,8 @@ import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; @@ -58,6 +60,7 @@ public boolean process(Set annotations, RoundEnvironment if (roundEnv.processingOver()) { writeMetaData(); } + // TODO: Consider setting to true? return false; } @@ -84,8 +87,8 @@ private void process(RoundEnvironment roundEnv) { private ConfigurationMetaData.Property createProperty(VariableElement element) { return new ConfigurationMetaData.Property( // - processPropertyName(element), // - null, // TODO: + processName(element), // + processType(element), // processDescription(element), // processSourceType(element), // null, // TODO: @@ -93,8 +96,22 @@ private ConfigurationMetaData.Property createProperty(VariableElement element) { ); } + private @Nullable String processType(VariableElement element) { + var configurationProperty = getAnnotation(element, ConfigurationProperty.class); + if (configurationProperty == null) { + return null; + } + + var type = getAnnotationElementValue(configurationProperty, "type"); + if (Void.class.getName().equals(type)) { + return null; + } + + return type; + } + private ConfigurationMetaData.@Nullable Deprecation processDeprecation(VariableElement element) { - var deprecated = element.getAnnotation(Deprecated.class); + var deprecated = getAnnotation(element, Deprecated.class); if (deprecated == null) { return null; } @@ -106,6 +123,28 @@ private ConfigurationMetaData.Property createProperty(VariableElement element) { ); } + private @Nullable AnnotationMirror getAnnotation(Element element, Class annotationType) { + var annotationTypeName = annotationType.getName(); + for (AnnotationMirror annotation : element.getAnnotationMirrors()) { + if (annotationTypeName.equals(annotation.getAnnotationType().toString())) { + return annotation; + } + } + return null; + } + + private @Nullable String getAnnotationElementValue(AnnotationMirror annotation, String elementName) { + return annotation.getElementValues().entrySet() // + .stream() // + .filter((element) -> element.getKey().getSimpleName().toString().equals(elementName)) // + .map((element) -> { + var value = element.getValue().getValue(); + return value == null ? null : value.toString(); + }) // + .findFirst() // + .orElse(null); + } + private @Nullable String processDescription(VariableElement element) { var docComment = processingEnvironment().getElementUtils().getDocComment(element); return docComment == null ? null : cleanupDocComment(docComment); @@ -125,12 +164,12 @@ private static String cleanupDocComment(String docComment) { .trim(); } - private static String processSourceType(VariableElement element) { + private String processSourceType(VariableElement element) { var enclosingTypeElement = getEnclosingTypeElement(element); return enclosingTypeElement.getQualifiedName().toString(); } - private static String processPropertyName(VariableElement element) { + private String processName(VariableElement element) { // TODO: Report preconditions problems with processingEnvironment().getMessager().printMessage() instead. var enclosingTypeElement = getEnclosingTypeElement(element); Preconditions.condition(isStatic(element), // @@ -146,7 +185,7 @@ private static String processPropertyName(VariableElement element) { return (String) constantValue; } - private static TypeElement getEnclosingTypeElement(VariableElement element) { + private TypeElement getEnclosingTypeElement(VariableElement element) { var enclosingElement = element.getEnclosingElement(); Preconditions.condition(enclosingElement instanceof TypeElement, // () -> "[%s] did not have an enclosing type element" // diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index a06a29bfda33..25c3c85e657a 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -69,6 +69,21 @@ void minimal() { }"""); } + @Test + void stringType() { + compiler.compile(StringTypeConfigurationProperty.class); + assertMetaDataIsEqualTo(""" + { + "properties": [ + { + "name": "org.example.property", + "type": "java.lang.String", + "sourceType": "org.junit.platform.configuration.processor.StringTypeConfigurationProperty" + } + ] + }"""); + } + @Test void documented() { compiler.compile(DocumentedConfigurationProperty.class); diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/StringTypeConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/StringTypeConfigurationProperty.java new file mode 100644 index 000000000000..88376b4767d1 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/StringTypeConfigurationProperty.java @@ -0,0 +1,20 @@ +/* + * Copyright 2026 the original author or 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.configuration.processor; + +import org.junit.platform.configuration.api.ConfigurationProperty; + +final class StringTypeConfigurationProperty { + + @ConfigurationProperty(type = String.class) + public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; + +} From 7cf5a6d43757ba12c2c81d4fb6b82fa936f440ee Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 9 Aug 2026 18:22:30 +0200 Subject: [PATCH 037/117] Rename ConfigurationProperty to ConfigurationParameter --- .../java/org/junit/jupiter/api/Constants.java | 4 +- ...perty.java => ConfigurationParameter.java} | 2 +- ...figurationMetadataAnnotationProcessor.java | 12 +- .../platform/launcher/LauncherConstants.java | 4 +- .../org/junit/vintage/engine/Constants.java | 4 +- ...ationMetadataAnnotationProcessorTests.java | 150 +++++++++--------- .../ClassDeprecated.java} | 8 +- .../Documented.java} | 8 +- .../DocumentedWithAtValue.java} | 8 +- .../DocumentedWithHeader.java} | 8 +- .../DocumentedWithMultiLines.java} | 8 +- .../DocumentedWithMultipleParagraphs.java} | 8 +- .../Minimal.java} | 8 +- .../NonFinal.java} | 8 +- .../NonStatic.java} | 8 +- .../NonString.java} | 8 +- .../PropertyDeprecated.java} | 8 +- .../StringType.java} | 8 +- .../Without.java} | 4 +- 19 files changed, 141 insertions(+), 135 deletions(-) rename junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/{ConfigurationProperty.java => ConfigurationParameter.java} (95%) rename platform-tests/src/test/java/org/junit/platform/configuration/{processor/ClassDeprecatedConfigurationProperty.java => testcases/ClassDeprecated.java} (67%) rename platform-tests/src/test/java/org/junit/platform/configuration/{processor/DocumentedConfigurationProperty.java => testcases/Documented.java} (70%) rename platform-tests/src/test/java/org/junit/platform/configuration/{processor/DocumentedWithAtValueConfigurationProperty.java => testcases/DocumentedWithAtValue.java} (69%) rename platform-tests/src/test/java/org/junit/platform/configuration/{processor/DocumentedWithHeaderConfigurationProperty.java => testcases/DocumentedWithHeader.java} (72%) rename platform-tests/src/test/java/org/junit/platform/configuration/{processor/DocumentedWithMultiLinesConfigurationProperty.java => testcases/DocumentedWithMultiLines.java} (69%) rename platform-tests/src/test/java/org/junit/platform/configuration/{processor/DocumentedWithMultipleParagraphsConfigurationProperty.java => testcases/DocumentedWithMultipleParagraphs.java} (69%) rename platform-tests/src/test/java/org/junit/platform/configuration/{processor/MinimalConfigurationProperty.java => testcases/Minimal.java} (67%) rename platform-tests/src/test/java/org/junit/platform/configuration/{processor/NonFinalConfigurationProperty.java => testcases/NonFinal.java} (67%) rename platform-tests/src/test/java/org/junit/platform/configuration/{processor/NonStaticConfigurationProperty.java => testcases/NonStatic.java} (67%) rename platform-tests/src/test/java/org/junit/platform/configuration/{processor/NonStringConfigurationProperty.java => testcases/NonString.java} (66%) rename platform-tests/src/test/java/org/junit/platform/configuration/{processor/DeprecatedConfigurationProperty.java => testcases/PropertyDeprecated.java} (68%) rename platform-tests/src/test/java/org/junit/platform/configuration/{processor/StringTypeConfigurationProperty.java => testcases/StringType.java} (65%) rename platform-tests/src/test/java/org/junit/platform/configuration/{processor/WithoutConfigurationProperty.java => testcases/Without.java} (76%) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java index 54ac6abffeef..5b8cc4564ac8 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java @@ -20,7 +20,7 @@ import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.parallel.Execution; -import org.junit.platform.configuration.api.ConfigurationProperty; +import org.junit.platform.configuration.api.ConfigurationParameter; /** * Collection of configuration constants for the Jupiter test engine. @@ -65,7 +65,7 @@ public final class Constants { * *

Note: A class that matches both an inclusion and exclusion pattern will be excluded. */ - @ConfigurationProperty + @ConfigurationParameter public static final String EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.include"; /** diff --git a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationProperty.java b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java similarity index 95% rename from junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationProperty.java rename to junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java index 381eb2f27924..5ccc91592a90 100644 --- a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationProperty.java +++ b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java @@ -20,7 +20,7 @@ @API(status = API.Status.EXPERIMENTAL) @Retention(RetentionPolicy.SOURCE) @Target(ElementType.FIELD) -public @interface ConfigurationProperty { +public @interface ConfigurationParameter { /** * The data type of the property. For example {@link String}, diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index c4659188375c..6001a9af0883 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -34,10 +34,10 @@ import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.configuration.api.ConfigurationProperty; +import org.junit.platform.configuration.api.ConfigurationParameter; @API(status = API.Status.EXPERIMENTAL) -@SupportedAnnotationTypes("org.junit.platform.configuration.api.ConfigurationProperty") +@SupportedAnnotationTypes("org.junit.platform.configuration.api.ConfigurationParameter") public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor { private static final String METADATA_PATH = "META-INF/junit-platform-configuration-metadata.json"; private @Nullable ConfigurationMetaData metaData; @@ -78,7 +78,7 @@ private void writeMetaData() { } private void process(RoundEnvironment roundEnv) { - roundEnv.getElementsAnnotatedWith(ConfigurationProperty.class).stream() // + roundEnv.getElementsAnnotatedWith(ConfigurationParameter.class).stream() // .filter(VariableElement.class::isInstance) // .map(VariableElement.class::cast) // .map(this::createProperty) // @@ -97,12 +97,12 @@ private ConfigurationMetaData.Property createProperty(VariableElement element) { } private @Nullable String processType(VariableElement element) { - var configurationProperty = getAnnotation(element, ConfigurationProperty.class); - if (configurationProperty == null) { + var parameter = getAnnotation(element, ConfigurationParameter.class); + if (parameter == null) { return null; } - var type = getAnnotationElementValue(configurationProperty, "type"); + var type = getAnnotationElementValue(parameter, "type"); if (Void.class.getName().equals(type)) { return null; } 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 c7aec3a8e2a1..5f5e6155e9be 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 @@ -16,7 +16,7 @@ import org.apiguardian.api.API; import org.junit.platform.commons.util.ClassNamePatternFilterUtils; -import org.junit.platform.configuration.api.ConfigurationProperty; +import org.junit.platform.configuration.api.ConfigurationParameter; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.reporting.ReportEntry; @@ -45,7 +45,7 @@ public class LauncherConstants { * @see ReportEntry * @see TestExecutionListener#reportingEntryPublished(TestIdentifier, ReportEntry) */ - @ConfigurationProperty + @ConfigurationParameter public static final String CAPTURE_STDOUT_PROPERTY_NAME = "junit.platform.output.capture.stdout"; /** diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java index a6e5c44bf01c..51fa6b0f46a4 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java @@ -14,7 +14,7 @@ import static org.apiguardian.api.API.Status.MAINTAINED; import org.apiguardian.api.API; -import org.junit.platform.configuration.api.ConfigurationProperty; +import org.junit.platform.configuration.api.ConfigurationParameter; /** * Collection of constants related to the {@link VintageTestEngine}. @@ -35,7 +35,7 @@ public final class Constants { * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") - @ConfigurationProperty + @ConfigurationParameter public static final String PARALLEL_EXECUTION_ENABLED = "junit.vintage.execution.parallel.enabled"; /** diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index 25c3c85e657a..c9b185e3c1ec 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -27,6 +27,19 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.configuration.testcases.ClassDeprecated; +import org.junit.platform.configuration.testcases.Documented; +import org.junit.platform.configuration.testcases.DocumentedWithAtValue; +import org.junit.platform.configuration.testcases.DocumentedWithHeader; +import org.junit.platform.configuration.testcases.DocumentedWithMultiLines; +import org.junit.platform.configuration.testcases.DocumentedWithMultipleParagraphs; +import org.junit.platform.configuration.testcases.Minimal; +import org.junit.platform.configuration.testcases.NonFinal; +import org.junit.platform.configuration.testcases.NonStatic; +import org.junit.platform.configuration.testcases.NonString; +import org.junit.platform.configuration.testcases.PropertyDeprecated; +import org.junit.platform.configuration.testcases.StringType; +import org.junit.platform.configuration.testcases.Without; class ConfigurationMetadataAnnotationProcessorTests { @@ -50,20 +63,20 @@ void setup() { @Test void none() { - compiler.compile(WithoutConfigurationProperty.class); + compiler.compile(Without.class); var metaDataPath = outputDirectory.resolve(expectedMetadataPath); assertThat(metaDataPath).doesNotExist(); } @Test void minimal() { - compiler.compile(MinimalConfigurationProperty.class); + compiler.compile(Minimal.class); assertMetaDataIsEqualTo(""" { "properties": [ { "name": "org.example.property", - "sourceType": "org.junit.platform.configuration.processor.MinimalConfigurationProperty" + "sourceType": "org.junit.platform.configuration.testcases.Minimal" } ] }"""); @@ -71,14 +84,14 @@ void minimal() { @Test void stringType() { - compiler.compile(StringTypeConfigurationProperty.class); + compiler.compile(StringType.class); assertMetaDataIsEqualTo(""" { "properties": [ { "name": "org.example.property", "type": "java.lang.String", - "sourceType": "org.junit.platform.configuration.processor.StringTypeConfigurationProperty" + "sourceType": "org.junit.platform.configuration.testcases.StringType" } ] }"""); @@ -86,14 +99,14 @@ void stringType() { @Test void documented() { - compiler.compile(DocumentedConfigurationProperty.class); + compiler.compile(Documented.class); assertMetaDataIsEqualTo(""" { "properties": [ { "name": "org.example.property", "description": "A brief description of this property.", - "sourceType": "org.junit.platform.configuration.processor.DocumentedConfigurationProperty" + "sourceType": "org.junit.platform.configuration.testcases.Documented" } ] }"""); @@ -101,79 +114,75 @@ void documented() { @Test void documentedWithAtValue() { - compiler.compile(DocumentedWithAtValueConfigurationProperty.class); - assertMetaDataIsEqualTo( - """ + compiler.compile(DocumentedWithAtValue.class); + assertMetaDataIsEqualTo(""" + { + "properties": [ { - "properties": [ - { - "name": "org.example.property", - "description": "A brief description of this property.", - "sourceType": "org.junit.platform.configuration.processor.DocumentedWithAtValueConfigurationProperty" - } - ] - }"""); + "name": "org.example.property", + "description": "A brief description of this property.", + "sourceType": "org.junit.platform.configuration.testcases.DocumentedWithAtValue" + } + ] + }"""); } @Test void documentedWithMultipleLines() { - compiler.compile(DocumentedWithMultiLinesConfigurationProperty.class); - assertMetaDataIsEqualTo( - """ + compiler.compile(DocumentedWithMultiLines.class); + assertMetaDataIsEqualTo(""" + { + "properties": [ { - "properties": [ - { - "name": "org.example.property", - "description": "A brief multi-line description of this property.", - "sourceType": "org.junit.platform.configuration.processor.DocumentedWithMultiLinesConfigurationProperty" - } - ] - }"""); + "name": "org.example.property", + "description": "A brief multi-line description of this property.", + "sourceType": "org.junit.platform.configuration.testcases.DocumentedWithMultiLines" + } + ] + }"""); } @Test void documentedWithMultipleParagraphs() { - compiler.compile(DocumentedWithMultipleParagraphsConfigurationProperty.class); - assertMetaDataIsEqualTo( - """ + compiler.compile(DocumentedWithMultipleParagraphs.class); + assertMetaDataIsEqualTo(""" + { + "properties": [ { - "properties": [ - { - "name": "org.example.property", - "description": "A brief description of this property.", - "sourceType": "org.junit.platform.configuration.processor.DocumentedWithMultipleParagraphsConfigurationProperty" - } - ] - }"""); + "name": "org.example.property", + "description": "A brief description of this property.", + "sourceType": "org.junit.platform.configuration.testcases.DocumentedWithMultipleParagraphs" + } + ] + }"""); } @Test void documentedWithHeader() { - compiler.compile(DocumentedWithHeaderConfigurationProperty.class); - assertMetaDataIsEqualTo( - """ + compiler.compile(DocumentedWithHeader.class); + assertMetaDataIsEqualTo(""" + { + "properties": [ { - "properties": [ - { - "name": "org.example.property", - "description": "A brief description of this property.", - "sourceType": "org.junit.platform.configuration.processor.DocumentedWithHeaderConfigurationProperty" - } - ] - }"""); + "name": "org.example.property", + "description": "A brief description of this property.", + "sourceType": "org.junit.platform.configuration.testcases.DocumentedWithHeader" + } + ] + }"""); } @Test void deprecated() { // TODO: Inheritance? Meta? // TODO: Warning level? - compiler.compile(DeprecatedConfigurationProperty.class); + compiler.compile(PropertyDeprecated.class); assertMetaDataIsEqualTo(""" { "properties": [ { "name": "org.example.property", - "sourceType": "org.junit.platform.configuration.processor.DeprecatedConfigurationProperty", + "sourceType": "org.junit.platform.configuration.testcases.PropertyDeprecated", "deprecation": { } } ] @@ -185,39 +194,36 @@ void deprecated() { @Disabled("Not yet implemented") void classDeprecated() { // TODO: Inheritance? Meta? - compiler.compile(ClassDeprecatedConfigurationProperty.class); - assertMetaDataIsEqualTo( - """ + compiler.compile(ClassDeprecated.class); + assertMetaDataIsEqualTo(""" + { + "properties": [ { - "properties": [ - { - "name": "org.example.property", - "sourceType": "org.junit.platform.configuration.processor.ClassDeprecatedConfigurationProperty", - "deprecation": { } - } - ] - }"""); + "name": "org.example.property", + "sourceType": "org.junit.platform.configuration.testcases.ClassDeprecated", + "deprecation": { } + } + ] + }"""); } @Test void mustBeFinal() { - asserPreconditionViolation(() -> compiler.compile(NonFinalConfigurationProperty.class), - "Field [%s.EXAMPLE_PROPERTY_NAME] must be declared final".formatted( - NonFinalConfigurationProperty.class.getName())); + asserPreconditionViolation(() -> compiler.compile(NonFinal.class), + "Field [%s.EXAMPLE_PROPERTY_NAME] must be declared final".formatted(NonFinal.class.getName())); } @Test void mustBeStatic() { - asserPreconditionViolation(() -> compiler.compile(NonStaticConfigurationProperty.class), - "Field [%s.EXAMPLE_PROPERTY_NAME] must be declared static".formatted( - NonStaticConfigurationProperty.class.getName())); + asserPreconditionViolation(() -> compiler.compile(NonStatic.class), + "Field [%s.EXAMPLE_PROPERTY_NAME] must be declared static".formatted(NonStatic.class.getName())); } @Test void mustBeString() { - asserPreconditionViolation(() -> compiler.compile(NonStringConfigurationProperty.class), + asserPreconditionViolation(() -> compiler.compile(NonString.class), "Field [%s.EXAMPLE_PROPERTY_NAME] must have a constant string value".formatted( - NonStringConfigurationProperty.class.getName())); + NonString.class.getName())); } private AbstractStringAssert assertMetaDataIsEqualTo(@Language("JSON") String json) { diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ClassDeprecatedConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/ClassDeprecated.java similarity index 67% rename from platform-tests/src/test/java/org/junit/platform/configuration/processor/ClassDeprecatedConfigurationProperty.java rename to platform-tests/src/test/java/org/junit/platform/configuration/testcases/ClassDeprecated.java index 34139695831d..b253d350d513 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ClassDeprecatedConfigurationProperty.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/ClassDeprecated.java @@ -8,14 +8,14 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.configuration.processor; +package org.junit.platform.configuration.testcases; -import org.junit.platform.configuration.api.ConfigurationProperty; +import org.junit.platform.configuration.api.ConfigurationParameter; @Deprecated -final class ClassDeprecatedConfigurationProperty { +public final class ClassDeprecated { - @ConfigurationProperty + @ConfigurationParameter public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Documented.java similarity index 70% rename from platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedConfigurationProperty.java rename to platform-tests/src/test/java/org/junit/platform/configuration/testcases/Documented.java index 27f0b3fec428..22030ba239b8 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedConfigurationProperty.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Documented.java @@ -8,16 +8,16 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.configuration.processor; +package org.junit.platform.configuration.testcases; -import org.junit.platform.configuration.api.ConfigurationProperty; +import org.junit.platform.configuration.api.ConfigurationParameter; -final class DocumentedConfigurationProperty { +public final class Documented { /** * A brief description of this property. */ - @ConfigurationProperty + @ConfigurationParameter public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithAtValueConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithAtValue.java similarity index 69% rename from platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithAtValueConfigurationProperty.java rename to platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithAtValue.java index 7068f8424b37..1d4c87e7be93 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithAtValueConfigurationProperty.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithAtValue.java @@ -8,16 +8,16 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.configuration.processor; +package org.junit.platform.configuration.testcases; -import org.junit.platform.configuration.api.ConfigurationProperty; +import org.junit.platform.configuration.api.ConfigurationParameter; -final class DocumentedWithAtValueConfigurationProperty { +public final class DocumentedWithAtValue { /** * A brief description of this property: {@value}. */ - @ConfigurationProperty + @ConfigurationParameter public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithHeaderConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithHeader.java similarity index 72% rename from platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithHeaderConfigurationProperty.java rename to platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithHeader.java index 383c036ba134..be494189ce7b 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithHeaderConfigurationProperty.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithHeader.java @@ -8,11 +8,11 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.configuration.processor; +package org.junit.platform.configuration.testcases; -import org.junit.platform.configuration.api.ConfigurationProperty; +import org.junit.platform.configuration.api.ConfigurationParameter; -final class DocumentedWithHeaderConfigurationProperty { +public final class DocumentedWithHeader { /** * A brief description of this property. @@ -21,7 +21,7 @@ final class DocumentedWithHeaderConfigurationProperty { * * Followed by an additional paragraph. */ - @ConfigurationProperty + @ConfigurationParameter public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithMultiLinesConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithMultiLines.java similarity index 69% rename from platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithMultiLinesConfigurationProperty.java rename to platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithMultiLines.java index e88a54e229d6..c13fc030d97c 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithMultiLinesConfigurationProperty.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithMultiLines.java @@ -8,17 +8,17 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.configuration.processor; +package org.junit.platform.configuration.testcases; -import org.junit.platform.configuration.api.ConfigurationProperty; +import org.junit.platform.configuration.api.ConfigurationParameter; -final class DocumentedWithMultiLinesConfigurationProperty { +public final class DocumentedWithMultiLines { /** * A brief multi-line description of * this property: {@value}. */ - @ConfigurationProperty + @ConfigurationParameter public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithMultipleParagraphsConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithMultipleParagraphs.java similarity index 69% rename from platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithMultipleParagraphsConfigurationProperty.java rename to platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithMultipleParagraphs.java index 0e2da89ddfdf..1ff43878c2fb 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/DocumentedWithMultipleParagraphsConfigurationProperty.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithMultipleParagraphs.java @@ -8,18 +8,18 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.configuration.processor; +package org.junit.platform.configuration.testcases; -import org.junit.platform.configuration.api.ConfigurationProperty; +import org.junit.platform.configuration.api.ConfigurationParameter; -final class DocumentedWithMultipleParagraphsConfigurationProperty { +public final class DocumentedWithMultipleParagraphs { /** * A brief description of this property. * *

Followed by an additional paragraph. */ - @ConfigurationProperty + @ConfigurationParameter public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/MinimalConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Minimal.java similarity index 67% rename from platform-tests/src/test/java/org/junit/platform/configuration/processor/MinimalConfigurationProperty.java rename to platform-tests/src/test/java/org/junit/platform/configuration/testcases/Minimal.java index dab610bad93d..5f7d32fe8669 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/MinimalConfigurationProperty.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Minimal.java @@ -8,13 +8,13 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.configuration.processor; +package org.junit.platform.configuration.testcases; -import org.junit.platform.configuration.api.ConfigurationProperty; +import org.junit.platform.configuration.api.ConfigurationParameter; -final class MinimalConfigurationProperty { +public final class Minimal { - @ConfigurationProperty + @ConfigurationParameter public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/NonFinalConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/NonFinal.java similarity index 67% rename from platform-tests/src/test/java/org/junit/platform/configuration/processor/NonFinalConfigurationProperty.java rename to platform-tests/src/test/java/org/junit/platform/configuration/testcases/NonFinal.java index 4b4cb136df97..4c394e95274d 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/NonFinalConfigurationProperty.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/NonFinal.java @@ -8,13 +8,13 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.configuration.processor; +package org.junit.platform.configuration.testcases; -import org.junit.platform.configuration.api.ConfigurationProperty; +import org.junit.platform.configuration.api.ConfigurationParameter; -final class NonFinalConfigurationProperty { +public final class NonFinal { - @ConfigurationProperty + @ConfigurationParameter public static String EXAMPLE_PROPERTY_NAME = "org.example.property"; } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/NonStaticConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/NonStatic.java similarity index 67% rename from platform-tests/src/test/java/org/junit/platform/configuration/processor/NonStaticConfigurationProperty.java rename to platform-tests/src/test/java/org/junit/platform/configuration/testcases/NonStatic.java index 332dfd5d929e..f9b9d1ab017b 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/NonStaticConfigurationProperty.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/NonStatic.java @@ -8,13 +8,13 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.configuration.processor; +package org.junit.platform.configuration.testcases; -import org.junit.platform.configuration.api.ConfigurationProperty; +import org.junit.platform.configuration.api.ConfigurationParameter; -final class NonStaticConfigurationProperty { +public final class NonStatic { - @ConfigurationProperty + @ConfigurationParameter public final String EXAMPLE_PROPERTY_NAME = "org.example.property"; } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/NonStringConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/NonString.java similarity index 66% rename from platform-tests/src/test/java/org/junit/platform/configuration/processor/NonStringConfigurationProperty.java rename to platform-tests/src/test/java/org/junit/platform/configuration/testcases/NonString.java index fc941ff635d3..7089a30ebaa4 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/NonStringConfigurationProperty.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/NonString.java @@ -8,13 +8,13 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.configuration.processor; +package org.junit.platform.configuration.testcases; -import org.junit.platform.configuration.api.ConfigurationProperty; +import org.junit.platform.configuration.api.ConfigurationParameter; -final class NonStringConfigurationProperty { +public final class NonString { - @ConfigurationProperty + @ConfigurationParameter public static final Boolean EXAMPLE_PROPERTY_NAME = true; } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/DeprecatedConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/PropertyDeprecated.java similarity index 68% rename from platform-tests/src/test/java/org/junit/platform/configuration/processor/DeprecatedConfigurationProperty.java rename to platform-tests/src/test/java/org/junit/platform/configuration/testcases/PropertyDeprecated.java index fef6f80ccdab..ac6072cdbdd1 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/DeprecatedConfigurationProperty.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/PropertyDeprecated.java @@ -8,14 +8,14 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.configuration.processor; +package org.junit.platform.configuration.testcases; -import org.junit.platform.configuration.api.ConfigurationProperty; +import org.junit.platform.configuration.api.ConfigurationParameter; -final class DeprecatedConfigurationProperty { +public final class PropertyDeprecated { @Deprecated - @ConfigurationProperty + @ConfigurationParameter public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/StringTypeConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/StringType.java similarity index 65% rename from platform-tests/src/test/java/org/junit/platform/configuration/processor/StringTypeConfigurationProperty.java rename to platform-tests/src/test/java/org/junit/platform/configuration/testcases/StringType.java index 88376b4767d1..dbfbd88bee7e 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/StringTypeConfigurationProperty.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/StringType.java @@ -8,13 +8,13 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.configuration.processor; +package org.junit.platform.configuration.testcases; -import org.junit.platform.configuration.api.ConfigurationProperty; +import org.junit.platform.configuration.api.ConfigurationParameter; -final class StringTypeConfigurationProperty { +public final class StringType { - @ConfigurationProperty(type = String.class) + @ConfigurationParameter(type = String.class) public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/WithoutConfigurationProperty.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Without.java similarity index 76% rename from platform-tests/src/test/java/org/junit/platform/configuration/processor/WithoutConfigurationProperty.java rename to platform-tests/src/test/java/org/junit/platform/configuration/testcases/Without.java index 183ae141a3fc..3e86676e8fc0 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/WithoutConfigurationProperty.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Without.java @@ -8,8 +8,8 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.configuration.processor; +package org.junit.platform.configuration.testcases; -final class WithoutConfigurationProperty { +public final class Without { } From 59e4a87b17fffea8d174a6f9406d2f56516589c8 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 9 Aug 2026 18:55:09 +0200 Subject: [PATCH 038/117] Remove ConfigurationParameter.type from scope --- .../api/ConfigurationParameter.java | 7 ----- ...figurationMetadataAnnotationProcessor.java | 28 +------------------ ...ationMetadataAnnotationProcessorTests.java | 16 ----------- .../configuration/testcases/StringType.java | 20 ------------- 4 files changed, 1 insertion(+), 70 deletions(-) delete mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/testcases/StringType.java diff --git a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java index 5ccc91592a90..979b3da034a7 100644 --- a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java +++ b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java @@ -22,11 +22,4 @@ @Target(ElementType.FIELD) public @interface ConfigurationParameter { - /** - * The data type of the property. For example {@link String}, - * {@link Integer}, {@link Boolean} or {@link Class}. - * - * @return the data type of the property. - */ - Class type() default Void.class; } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index 6001a9af0883..33038df6d22a 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -88,7 +88,7 @@ private void process(RoundEnvironment roundEnv) { private ConfigurationMetaData.Property createProperty(VariableElement element) { return new ConfigurationMetaData.Property( // processName(element), // - processType(element), // + null, // TODO: processDescription(element), // processSourceType(element), // null, // TODO: @@ -96,20 +96,6 @@ private ConfigurationMetaData.Property createProperty(VariableElement element) { ); } - private @Nullable String processType(VariableElement element) { - var parameter = getAnnotation(element, ConfigurationParameter.class); - if (parameter == null) { - return null; - } - - var type = getAnnotationElementValue(parameter, "type"); - if (Void.class.getName().equals(type)) { - return null; - } - - return type; - } - private ConfigurationMetaData.@Nullable Deprecation processDeprecation(VariableElement element) { var deprecated = getAnnotation(element, Deprecated.class); if (deprecated == null) { @@ -133,18 +119,6 @@ private ConfigurationMetaData.Property createProperty(VariableElement element) { return null; } - private @Nullable String getAnnotationElementValue(AnnotationMirror annotation, String elementName) { - return annotation.getElementValues().entrySet() // - .stream() // - .filter((element) -> element.getKey().getSimpleName().toString().equals(elementName)) // - .map((element) -> { - var value = element.getValue().getValue(); - return value == null ? null : value.toString(); - }) // - .findFirst() // - .orElse(null); - } - private @Nullable String processDescription(VariableElement element) { var docComment = processingEnvironment().getElementUtils().getDocComment(element); return docComment == null ? null : cleanupDocComment(docComment); diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index c9b185e3c1ec..d3b8c0c460ac 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -38,7 +38,6 @@ import org.junit.platform.configuration.testcases.NonStatic; import org.junit.platform.configuration.testcases.NonString; import org.junit.platform.configuration.testcases.PropertyDeprecated; -import org.junit.platform.configuration.testcases.StringType; import org.junit.platform.configuration.testcases.Without; class ConfigurationMetadataAnnotationProcessorTests { @@ -82,21 +81,6 @@ void minimal() { }"""); } - @Test - void stringType() { - compiler.compile(StringType.class); - assertMetaDataIsEqualTo(""" - { - "properties": [ - { - "name": "org.example.property", - "type": "java.lang.String", - "sourceType": "org.junit.platform.configuration.testcases.StringType" - } - ] - }"""); - } - @Test void documented() { compiler.compile(Documented.class); diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/StringType.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/StringType.java deleted file mode 100644 index dbfbd88bee7e..000000000000 --- a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/StringType.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2026 the original author or 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.configuration.testcases; - -import org.junit.platform.configuration.api.ConfigurationParameter; - -public final class StringType { - - @ConfigurationParameter(type = String.class) - public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; - -} From 1e1741ff86ed25870e505f17cc2e995148e6b218 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 9 Aug 2026 19:14:07 +0200 Subject: [PATCH 039/117] Document minimal scope --- .../api/ConfigurationParameter.java | 3 +++ ...figurationMetadataAnnotationProcessor.java | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java index 979b3da034a7..4b2f466a8fcd 100644 --- a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java +++ b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java @@ -17,6 +17,9 @@ import org.apiguardian.api.API; +/** + * Marks a field as a configuration parameter for a test engine. + */ @API(status = API.Status.EXPERIMENTAL) @Retention(RetentionPolicy.SOURCE) @Target(ElementType.FIELD) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index 33038df6d22a..e73a09051204 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -36,6 +36,30 @@ import org.junit.platform.commons.util.Preconditions; import org.junit.platform.configuration.api.ConfigurationParameter; +/// Collects all configuration parameters marked with +/// {@link ConfigurationParameter} into +/// [Spring Boot Configuration Metadata](https://docs.spring.io/spring-boot/specification/configuration-metadata/format.html). +/// This enables IDE's and other tools to process and validate Test Engine +/// configuration. +/// +///

Usage

+/// +///
{@code
+/// /**
+///   * A brief multi-line description of
+///   * this property: {@value}.
+///   *
+///   * 

Followed by an additional paragraph. +/// */ +/// @ConfigurationProperty +/// public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; +/// +/// }

+/// +/// The first paragraph from the doc string used to describe the property. If the +/// first paragraph ends with {@code : {@value}.} or {@code : {@value}} it will +/// be replaced with a {@code : {@value}.}. +/// @API(status = API.Status.EXPERIMENTAL) @SupportedAnnotationTypes("org.junit.platform.configuration.api.ConfigurationParameter") public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor { From 5d7daadff834c62225d074a35312f7493775637f Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 9 Aug 2026 20:35:02 +0200 Subject: [PATCH 040/117] Polish deprecation element --- .../api/ConfigurationParameter.java | 34 ++++++++++ ...figurationMetadataAnnotationProcessor.java | 62 +++++++++++++++---- ...ationMetadataAnnotationProcessorTests.java | 36 +++++------ ...opertyDeprecated.java => Deprecation.java} | 2 +- ...cated.java => DeprecationWithDetails.java} | 5 +- 5 files changed, 104 insertions(+), 35 deletions(-) rename platform-tests/src/test/java/org/junit/platform/configuration/testcases/{PropertyDeprecated.java => Deprecation.java} (93%) rename platform-tests/src/test/java/org/junit/platform/configuration/testcases/{ClassDeprecated.java => DeprecationWithDetails.java} (69%) diff --git a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java index 4b2f466a8fcd..ed6daa1ab2ca 100644 --- a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java +++ b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java @@ -19,10 +19,44 @@ /** * Marks a field as a configuration parameter for a test engine. + *

+ * This annotation should be used to facilitate the automated + * generation of documentation. */ @API(status = API.Status.EXPERIMENTAL) @Retention(RetentionPolicy.SOURCE) @Target(ElementType.FIELD) public @interface ConfigurationParameter { + /** + * Specifies that the parameter is deprecated. + * + * @return details about the deprecation. + */ + Deprecation deprecation() default @Deprecation; + + @interface Deprecation { + /** + * A brief description of why the parameter was deprecated. + *

+ * The description should be one or more short paragraphs, ending with a period. + * + * @return a description of why the parameter was deprecated. + */ + String reason() default ""; + + /** + * The full name of the replacement parameter. + * + * @return the full name of the replacement parameter. + */ + String replacement() default ""; + + /** + * The version of the API when the parameter was deprecated. + * + * @return the version of the API when the parameter was deprecated. + */ + String since() default ""; + } } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index e73a09051204..9b9edc86e693 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -16,6 +16,7 @@ import java.io.BufferedWriter; import java.io.OutputStreamWriter; import java.lang.annotation.Annotation; +import java.util.Map; import java.util.Set; import java.util.regex.Pattern; @@ -25,6 +26,7 @@ import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.SourceVersion; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; @@ -37,10 +39,10 @@ import org.junit.platform.configuration.api.ConfigurationParameter; /// Collects all configuration parameters marked with -/// {@link ConfigurationParameter} into -/// [Spring Boot Configuration Metadata](https://docs.spring.io/spring-boot/specification/configuration-metadata/format.html). -/// This enables IDE's and other tools to process and validate Test Engine -/// configuration. +/// {@link ConfigurationParameter} into [Spring Boot Configuration +/// Metadata](https://docs.spring.io/spring-boot/specification/configuration-metadata/format.html) +/// and writes it to {@value #METADATA_PATH}. This enables IDE's and other tools +/// to process and validate Test Engine configuration. /// ///

Usage

/// @@ -84,7 +86,7 @@ public boolean process(Set annotations, RoundEnvironment if (roundEnv.processingOver()) { writeMetaData(); } - // TODO: Consider setting to true? + // See: https://errorprone.info/bugpattern/DoNotClaimAnnotations return false; } @@ -120,17 +122,51 @@ private ConfigurationMetaData.Property createProperty(VariableElement element) { ); } + private @Nullable String getAnnotationElementStringValue(AnnotationMirror annotation, String elementName) { + return annotation.getElementValues().entrySet() // + .stream() // + .filter((element) -> element.getKey().getSimpleName().toString().equals(elementName)) // + .map(Map.Entry::getValue) // + .map(AnnotationValue::getValue) // + .filter(String.class::isInstance) // + .map(String.class::cast) // + .filter(s -> !s.isEmpty()) // + .findFirst() // + .orElse(null); + } + + private @Nullable AnnotationMirror getAnnotationElementAnnotationValue(AnnotationMirror annotation, + String elementName) { + return annotation.getElementValues().entrySet() // + .stream() // + .filter((element) -> element.getKey().getSimpleName().toString().equals(elementName)) // + .map(Map.Entry::getValue) // + .map(AnnotationValue::getValue) // + .filter(AnnotationMirror.class::isInstance) // + .map(AnnotationMirror.class::cast) // + .findFirst() // + .orElse(null); + } + private ConfigurationMetaData.@Nullable Deprecation processDeprecation(VariableElement element) { - var deprecated = getAnnotation(element, Deprecated.class); - if (deprecated == null) { + var parameter = getAnnotation(element, ConfigurationParameter.class); + if (parameter == null) { return null; } - return new ConfigurationMetaData.Deprecation( // - null, // - null, // TODO: - null, // TODO: - null // TODO: - ); + var deprecation = getAnnotationElementAnnotationValue(parameter, "deprecation"); + if (deprecation != null) { + var reason = getAnnotationElementStringValue(deprecation, "reason"); + var replacement = getAnnotationElementStringValue(deprecation, "replacement"); + var since = getAnnotationElementStringValue(deprecation, "since"); + if (reason != null || replacement != null || since != null) { + return new ConfigurationMetaData.Deprecation(null, reason, replacement, since); + } + } + // Fallback, look for @Deprecated + if (getAnnotation(element, Deprecated.class) != null) { + return new ConfigurationMetaData.Deprecation(null, null, null, null); + } + return null; } private @Nullable AnnotationMirror getAnnotation(Element element, Class annotationType) { diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index d3b8c0c460ac..d3a34a4b5a4b 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -22,12 +22,12 @@ import org.assertj.core.api.ThrowableAssert; import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.configuration.testcases.ClassDeprecated; +import org.junit.platform.configuration.testcases.Deprecation; +import org.junit.platform.configuration.testcases.DeprecationWithDetails; import org.junit.platform.configuration.testcases.Documented; import org.junit.platform.configuration.testcases.DocumentedWithAtValue; import org.junit.platform.configuration.testcases.DocumentedWithHeader; @@ -37,7 +37,6 @@ import org.junit.platform.configuration.testcases.NonFinal; import org.junit.platform.configuration.testcases.NonStatic; import org.junit.platform.configuration.testcases.NonString; -import org.junit.platform.configuration.testcases.PropertyDeprecated; import org.junit.platform.configuration.testcases.Without; class ConfigurationMetadataAnnotationProcessorTests { @@ -47,7 +46,7 @@ class ConfigurationMetadataAnnotationProcessorTests { final Path sourceDirectory = Path.of("src/test/java"); @Nested - class ConfigurationProperty { + class ConfigurationParameter { @TempDir Path outputDirectory; @@ -157,36 +156,37 @@ void documentedWithHeader() { } @Test - void deprecated() { - // TODO: Inheritance? Meta? + void deprecation() { + // TODO: Class level? Inheritance? Meta? // TODO: Warning level? - compiler.compile(PropertyDeprecated.class); + compiler.compile(Deprecation.class); assertMetaDataIsEqualTo(""" { "properties": [ { "name": "org.example.property", - "sourceType": "org.junit.platform.configuration.testcases.PropertyDeprecated", + "sourceType": "org.junit.platform.configuration.testcases.Deprecation", "deprecation": { } } ] }"""); } - @SuppressWarnings("deprecation") @Test - @Disabled("Not yet implemented") - void classDeprecated() { - // TODO: Inheritance? Meta? - compiler.compile(ClassDeprecated.class); + void deprecationWithDetails() { + compiler.compile(DeprecationWithDetails.class); assertMetaDataIsEqualTo(""" { "properties": [ - { - "name": "org.example.property", - "sourceType": "org.junit.platform.configuration.testcases.ClassDeprecated", - "deprecation": { } - } + { + "name": "org.example.property", + "sourceType": "org.junit.platform.configuration.testcases.DeprecationWithDetails", + "deprecation": { + "reason": "This property was migrated to com.example", + "replacement": "com.example.property", + "since":"2.0.0" + } + } ] }"""); } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/PropertyDeprecated.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Deprecation.java similarity index 93% rename from platform-tests/src/test/java/org/junit/platform/configuration/testcases/PropertyDeprecated.java rename to platform-tests/src/test/java/org/junit/platform/configuration/testcases/Deprecation.java index ac6072cdbdd1..43a154b650c7 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/PropertyDeprecated.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Deprecation.java @@ -12,7 +12,7 @@ import org.junit.platform.configuration.api.ConfigurationParameter; -public final class PropertyDeprecated { +public final class Deprecation { @Deprecated @ConfigurationParameter diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/ClassDeprecated.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DeprecationWithDetails.java similarity index 69% rename from platform-tests/src/test/java/org/junit/platform/configuration/testcases/ClassDeprecated.java rename to platform-tests/src/test/java/org/junit/platform/configuration/testcases/DeprecationWithDetails.java index b253d350d513..11046d1c0d1f 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/ClassDeprecated.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DeprecationWithDetails.java @@ -12,10 +12,9 @@ import org.junit.platform.configuration.api.ConfigurationParameter; -@Deprecated -public final class ClassDeprecated { +public final class DeprecationWithDetails { - @ConfigurationParameter + @ConfigurationParameter(deprecation = @ConfigurationParameter.Deprecation(reason = "This property was migrated to com.example", replacement = "com.example.property", since = "2.0.0")) public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; } From 3a1fdb923ec986d5a5ffbe1108a229ed80fdf8d8 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 9 Aug 2026 20:57:09 +0200 Subject: [PATCH 041/117] Implement missing json conversions --- .../processor/ConfigurationMetaData.java | 12 +++++- .../configuration/processor/JsonWriter.java | 40 ++++++++++++++----- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java index 50e061e76629..c3b6ee2b136c 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java @@ -53,13 +53,21 @@ record Property( // @Nullable String type, // @Nullable String description, // @Nullable String sourceType, // - @Nullable OneOrManyString defaultValue, // + @Nullable OneOrMany defaultValue, // @Nullable Deprecation deprecation // ) { } - interface OneOrManyString { + sealed interface OneOrMany { + + record One(T value) implements OneOrMany { + + } + + record Many(List values) implements OneOrMany { + + } } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonWriter.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonWriter.java index 837cdee45741..eac6ef0d26d8 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonWriter.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonWriter.java @@ -20,12 +20,15 @@ import org.junit.platform.configuration.processor.ConfigurationMetaData.Deprecation.Level; import org.junit.platform.configuration.processor.ConfigurationMetaData.Group; import org.junit.platform.configuration.processor.ConfigurationMetaData.Hint; +import org.junit.platform.configuration.processor.ConfigurationMetaData.OneOrMany.Many; +import org.junit.platform.configuration.processor.ConfigurationMetaData.OneOrMany.One; import org.junit.platform.configuration.processor.ConfigurationMetaData.Property; import org.junit.platform.configuration.processor.ConfigurationMetaData.ValueHint; import org.junit.platform.configuration.processor.ConfigurationMetaData.ValueProvider; import jakarta.json.Json; import jakarta.json.JsonArray; +import jakarta.json.JsonArrayBuilder; import jakarta.json.JsonBuilderFactory; import jakarta.json.JsonObject; import jakarta.json.JsonValue; @@ -106,11 +109,15 @@ private JsonObject toJsonObject(Property property) { builder.add("sourceType", sourceType); } - // TODO: - // var defaultValue = property.defaultValue(); - // if(defaultValue != null){ - // builder.add("defaultValue", toJsonObject(defaultValue)); - // } + var defaultValue = property.defaultValue(); + if (defaultValue != null) { + if (defaultValue instanceof One one) { + builder.add("defaultValue", toJsonValue(one)); + } + else if (defaultValue instanceof Many many) { + builder.add("defaultValue", toJsonValue(many)); + } + } var deprecation = property.deprecation(); if (deprecation != null) { @@ -120,6 +127,14 @@ private JsonObject toJsonObject(Property property) { return builder.build(); } + private String toJsonValue(One one) { + return one.value(); + } + + private JsonArrayBuilder toJsonValue(Many many) { + return factory.createArrayBuilder(many.values()); + } + private JsonObject toJsonObject(Deprecation deprecation) { var builder = factory.createObjectBuilder(); @@ -186,15 +201,20 @@ private JsonObject toJsonObject(ValueProvider valueProvider) { builder.add("name", valueProvider.name()); - // TODO: - // var parameters = valueProvider.parameters(); - // if (!parameters.isEmpty()) { - // builder.add("parameters", toJsonObject(parameters)); - // } + var parameters = valueProvider.parameters(); + if (!parameters.isEmpty()) { + builder.add("parameters", toJsonObject(parameters)); + } return builder.build(); } + private JsonObject toJsonObject(Map parameters) { + var builder = factory.createObjectBuilder(); + parameters.forEach(builder::add); + return builder.build(); + } + private JsonArray toJsonArray(List properties, Function converter) { var builder = factory.createArrayBuilder(); properties.forEach(element -> builder.add(converter.apply(element))); From adfc951ae62aa30c8a1b9d7b7e2661ceba7f4d38 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 9 Aug 2026 20:58:41 +0200 Subject: [PATCH 042/117] Remove unused Json --- .../processor/ConfigurationMetaData.java | 41 --------- .../configuration/processor/JsonWriter.java | 91 ------------------- 2 files changed, 132 deletions(-) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java index c3b6ee2b136c..aff640ba3ea1 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java @@ -12,42 +12,21 @@ import java.util.ArrayList; import java.util.List; -import java.util.Map; import org.jspecify.annotations.Nullable; class ConfigurationMetaData { - private final List groups = new ArrayList<>(); private final List properties = new ArrayList<>(); - private final List hints = new ArrayList<>(); List properties() { return properties; } - List groups() { - return groups; - } - - List hints() { - return hints; - } - void addProperty(Property property) { properties.add(property); } - record Group( // - String name, // - @Nullable String type, // - @Nullable String description, // - @Nullable String sourceType, // - @Nullable String sourceMethod // - ) { - - } - record Property( // String name, // @Nullable String type, // @@ -94,24 +73,4 @@ String value() { } } - record Hint( // - String name, // - List values, // - List providers // - ) { - } - - record ValueHint( // - String value, // - @Nullable String description // - ) { - - } - - record ValueProvider( // - String name, // - Map parameters // - ) { - - } } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonWriter.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonWriter.java index eac6ef0d26d8..3bf1958de7a7 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonWriter.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonWriter.java @@ -18,13 +18,9 @@ import org.jspecify.annotations.Nullable; import org.junit.platform.configuration.processor.ConfigurationMetaData.Deprecation; import org.junit.platform.configuration.processor.ConfigurationMetaData.Deprecation.Level; -import org.junit.platform.configuration.processor.ConfigurationMetaData.Group; -import org.junit.platform.configuration.processor.ConfigurationMetaData.Hint; import org.junit.platform.configuration.processor.ConfigurationMetaData.OneOrMany.Many; import org.junit.platform.configuration.processor.ConfigurationMetaData.OneOrMany.One; import org.junit.platform.configuration.processor.ConfigurationMetaData.Property; -import org.junit.platform.configuration.processor.ConfigurationMetaData.ValueHint; -import org.junit.platform.configuration.processor.ConfigurationMetaData.ValueProvider; import jakarta.json.Json; import jakarta.json.JsonArray; @@ -45,48 +41,11 @@ void writeValue(Writer out, ConfigurationMetaData metaData) { private @Nullable JsonObject toJsonObject(ConfigurationMetaData metaData) { var builder = factory.createObjectBuilder(); - var groups = metaData.groups(); - if (!groups.isEmpty()) { - builder.add("groups", toJsonArray(groups, this::toJsonObject)); - } - var properties = metaData.properties(); if (!properties.isEmpty()) { builder.add("properties", toJsonArray(properties, this::toJsonObject)); } - var hints = metaData.hints(); - if (!hints.isEmpty()) { - builder.add("hints", toJsonArray(hints, this::toJsonObject)); - } - - return builder.build(); - } - - private JsonObject toJsonObject(Group group) { - var builder = factory.createObjectBuilder(); - - builder.add("name", group.name()); - - var type = group.type(); - if (type != null) { - builder.add("type", type); - } - - var description = group.description(); - if (description != null) { - builder.add("description", description); - } - - var sourceType = group.sourceType(); - if (sourceType != null) { - builder.add("sourceType", sourceType); - } - - var sourceMethod = group.sourceMethod(); - if (sourceMethod != null) { - builder.add("sourceMethod", sourceMethod); - } return builder.build(); } @@ -165,56 +124,6 @@ private String toJsonValue(Level level) { return level.value(); } - private JsonObject toJsonObject(Hint hint) { - var builder = factory.createObjectBuilder(); - - builder.add("name", hint.name()); - - var values = hint.values(); - if (!values.isEmpty()) { - builder.add("values", toJsonArray(values, this::toJsonObject)); - } - - var providers = hint.providers(); - if (!providers.isEmpty()) { - builder.add("providers", toJsonArray(providers, this::toJsonObject)); - } - - return builder.build(); - } - - private JsonObject toJsonObject(ValueHint valueHint) { - var builder = factory.createObjectBuilder(); - - builder.add("type", valueHint.value()); - - var description = valueHint.description(); - if (description != null) { - builder.add("description", description); - } - - return builder.build(); - } - - private JsonObject toJsonObject(ValueProvider valueProvider) { - var builder = factory.createObjectBuilder(); - - builder.add("name", valueProvider.name()); - - var parameters = valueProvider.parameters(); - if (!parameters.isEmpty()) { - builder.add("parameters", toJsonObject(parameters)); - } - - return builder.build(); - } - - private JsonObject toJsonObject(Map parameters) { - var builder = factory.createObjectBuilder(); - parameters.forEach(builder::add); - return builder.build(); - } - private JsonArray toJsonArray(List properties, Function converter) { var builder = factory.createArrayBuilder(); properties.forEach(element -> builder.add(converter.apply(element))); From 53aca3c099d4d33a90085ae465667ee8ee6e56b1 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 9 Aug 2026 21:01:17 +0200 Subject: [PATCH 043/117] Document ConfigurationMetaData --- .../configuration/processor/ConfigurationMetaData.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java index aff640ba3ea1..db3d52c8bea7 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java @@ -15,6 +15,9 @@ import org.jspecify.annotations.Nullable; +/** + * See Spring Boot - Specifications - Configuration Metadata - Metadata Format. + */ class ConfigurationMetaData { private final List properties = new ArrayList<>(); @@ -55,7 +58,6 @@ record Deprecation( // @Nullable String reason, // @Nullable String replacement, // @Nullable String since // - ) { enum Level { From ef7e9492d69ff3e0469f2f4b0f7c4055817a437f Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 9 Aug 2026 21:07:14 +0200 Subject: [PATCH 044/117] Rename JsonWriter to JsonConverter --- .../ConfigurationMetadataAnnotationProcessor.java | 6 +++++- .../processor/{JsonWriter.java => JsonConverter.java} | 11 ++--------- 2 files changed, 7 insertions(+), 10 deletions(-) rename junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/{JsonWriter.java => JsonConverter.java} (91%) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index 9b9edc86e693..ec7b9999fc5b 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -38,6 +38,8 @@ import org.junit.platform.commons.util.Preconditions; import org.junit.platform.configuration.api.ConfigurationParameter; +import jakarta.json.Json; + /// Collects all configuration parameters marked with /// {@link ConfigurationParameter} into [Spring Boot Configuration /// Metadata](https://docs.spring.io/spring-boot/specification/configuration-metadata/format.html) @@ -95,7 +97,9 @@ private void writeMetaData() { var resource = processingEnvironment().getFiler() // .createResource(StandardLocation.CLASS_OUTPUT, "", METADATA_PATH); try (var out = new BufferedWriter(new OutputStreamWriter(resource.openOutputStream(), UTF_8))) { - new JsonWriter().writeValue(out, metaData()); + var converter = new JsonConverter(); + var value = converter.toJsonObject(metaData()); + Json.createWriter(out).write(value); } } catch (Exception ex) { diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonWriter.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java similarity index 91% rename from junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonWriter.java rename to junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java index 3bf1958de7a7..97f5f15e87fa 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonWriter.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java @@ -10,12 +10,10 @@ package org.junit.platform.configuration.processor; -import java.io.Writer; import java.util.List; import java.util.Map; import java.util.function.Function; -import org.jspecify.annotations.Nullable; import org.junit.platform.configuration.processor.ConfigurationMetaData.Deprecation; import org.junit.platform.configuration.processor.ConfigurationMetaData.Deprecation.Level; import org.junit.platform.configuration.processor.ConfigurationMetaData.OneOrMany.Many; @@ -29,16 +27,11 @@ import jakarta.json.JsonObject; import jakarta.json.JsonValue; -class JsonWriter { +class JsonConverter { private final Map config = Map.of(); private final JsonBuilderFactory factory = Json.createBuilderFactory(config); - void writeValue(Writer out, ConfigurationMetaData metaData) { - var value = toJsonObject(metaData); - Json.createWriter(out).write(value); - } - - private @Nullable JsonObject toJsonObject(ConfigurationMetaData metaData) { + JsonObject toJsonObject(ConfigurationMetaData metaData) { var builder = factory.createObjectBuilder(); var properties = metaData.properties(); From 64dc068c54d61c5e2a7fefcbe6e2ad4678557cd8 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 9 Aug 2026 21:21:07 +0200 Subject: [PATCH 045/117] Extract AnnotationMirrorUtil --- .../processor/AnnotationMirrorUtil.java | 62 +++++++++++++++++++ ...figurationMetadataAnnotationProcessor.java | 60 ++++-------------- 2 files changed, 73 insertions(+), 49 deletions(-) create mode 100644 junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java new file mode 100644 index 000000000000..57b653c10de4 --- /dev/null +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java @@ -0,0 +1,62 @@ +/* + * Copyright 2026 the original author or 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.configuration.processor; + +import java.lang.annotation.Annotation; +import java.util.Map; +import java.util.Optional; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; + +import org.jspecify.annotations.Nullable; + +class AnnotationMirrorUtil { + + private AnnotationMirrorUtil() { + /* no-op */ + } + + static @Nullable AnnotationMirror getAnnotationMirror(Element element, Class annotationType) { + var annotationTypeName = annotationType.getName(); + for (AnnotationMirror annotation : element.getAnnotationMirrors()) { + if (annotationTypeName.equals(annotation.getAnnotationType().toString())) { + return annotation; + } + } + return null; + } + + static @Nullable String getStringValue(AnnotationMirror annotation, String elementName) { + return findElementBy(annotation, elementName) // + .filter(String.class::isInstance) // + .map(String.class::cast) // + .filter(s -> !s.isEmpty()) // + .orElse(null); + } + + static @Nullable AnnotationMirror getAnnotationValue(AnnotationMirror annotation, String elementName) { + return findElementBy(annotation, elementName) // + .filter(AnnotationMirror.class::isInstance) // + .map(AnnotationMirror.class::cast) // + .orElse(null); + } + + private static Optional findElementBy(AnnotationMirror annotation, String elementName) { + return annotation.getElementValues().entrySet() // + .stream() // + .filter((element) -> element.getKey().getSimpleName().toString().equals(elementName)) // + .map(Map.Entry::getValue) // + .map(AnnotationValue::getValue) // + .findFirst(); + } +} diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index ec7b9999fc5b..2accb51884ad 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -12,11 +12,12 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; +import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getAnnotationMirror; +import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getAnnotationValue; +import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getStringValue; import java.io.BufferedWriter; import java.io.OutputStreamWriter; -import java.lang.annotation.Annotation; -import java.util.Map; import java.util.Set; import java.util.regex.Pattern; @@ -25,9 +26,6 @@ import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.SourceVersion; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; @@ -83,7 +81,7 @@ public SourceVersion getSupportedSourceVersion() { @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { - process(roundEnv); + processConfigurationParameter(roundEnv); if (roundEnv.processingOver()) { writeMetaData(); @@ -107,7 +105,7 @@ private void writeMetaData() { } } - private void process(RoundEnvironment roundEnv) { + private void processConfigurationParameter(RoundEnvironment roundEnv) { roundEnv.getElementsAnnotatedWith(ConfigurationParameter.class).stream() // .filter(VariableElement.class::isInstance) // .map(VariableElement.class::cast) // @@ -126,63 +124,27 @@ private ConfigurationMetaData.Property createProperty(VariableElement element) { ); } - private @Nullable String getAnnotationElementStringValue(AnnotationMirror annotation, String elementName) { - return annotation.getElementValues().entrySet() // - .stream() // - .filter((element) -> element.getKey().getSimpleName().toString().equals(elementName)) // - .map(Map.Entry::getValue) // - .map(AnnotationValue::getValue) // - .filter(String.class::isInstance) // - .map(String.class::cast) // - .filter(s -> !s.isEmpty()) // - .findFirst() // - .orElse(null); - } - - private @Nullable AnnotationMirror getAnnotationElementAnnotationValue(AnnotationMirror annotation, - String elementName) { - return annotation.getElementValues().entrySet() // - .stream() // - .filter((element) -> element.getKey().getSimpleName().toString().equals(elementName)) // - .map(Map.Entry::getValue) // - .map(AnnotationValue::getValue) // - .filter(AnnotationMirror.class::isInstance) // - .map(AnnotationMirror.class::cast) // - .findFirst() // - .orElse(null); - } - private ConfigurationMetaData.@Nullable Deprecation processDeprecation(VariableElement element) { - var parameter = getAnnotation(element, ConfigurationParameter.class); + var parameter = getAnnotationMirror(element, ConfigurationParameter.class); if (parameter == null) { return null; } - var deprecation = getAnnotationElementAnnotationValue(parameter, "deprecation"); + var deprecation = getAnnotationValue(parameter, "deprecation"); if (deprecation != null) { - var reason = getAnnotationElementStringValue(deprecation, "reason"); - var replacement = getAnnotationElementStringValue(deprecation, "replacement"); - var since = getAnnotationElementStringValue(deprecation, "since"); + var reason = getStringValue(deprecation, "reason"); + var replacement = getStringValue(deprecation, "replacement"); + var since = getStringValue(deprecation, "since"); if (reason != null || replacement != null || since != null) { return new ConfigurationMetaData.Deprecation(null, reason, replacement, since); } } // Fallback, look for @Deprecated - if (getAnnotation(element, Deprecated.class) != null) { + if (getAnnotationMirror(element, Deprecated.class) != null) { return new ConfigurationMetaData.Deprecation(null, null, null, null); } return null; } - private @Nullable AnnotationMirror getAnnotation(Element element, Class annotationType) { - var annotationTypeName = annotationType.getName(); - for (AnnotationMirror annotation : element.getAnnotationMirrors()) { - if (annotationTypeName.equals(annotation.getAnnotationType().toString())) { - return annotation; - } - } - return null; - } - private @Nullable String processDescription(VariableElement element) { var docComment = processingEnvironment().getElementUtils().getDocComment(element); return docComment == null ? null : cleanupDocComment(docComment); From 0084a4a3202a349038030ff176ffe27a58b9a077 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 9 Aug 2026 21:38:35 +0200 Subject: [PATCH 046/117] Extract ConfigurationParameterHandler --- .../processor/AnnotationMirrorUtil.java | 2 +- .../processor/ConfigurationMetaData.java | 2 +- ...figurationMetadataAnnotationProcessor.java | 114 ++------------- .../ConfigurationParameterHandler.java | 134 ++++++++++++++++++ .../processor/JsonConverter.java | 2 +- 5 files changed, 146 insertions(+), 108 deletions(-) create mode 100644 junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java index 57b653c10de4..1cfb268d28d8 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java @@ -20,7 +20,7 @@ import org.jspecify.annotations.Nullable; -class AnnotationMirrorUtil { +final class AnnotationMirrorUtil { private AnnotationMirrorUtil() { /* no-op */ diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java index db3d52c8bea7..401a1e1f4fdc 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java @@ -18,7 +18,7 @@ /** * See Spring Boot - Specifications - Configuration Metadata - Metadata Format. */ -class ConfigurationMetaData { +final class ConfigurationMetaData { private final List properties = new ArrayList<>(); diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index 2accb51884ad..f21aa8b073a8 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -12,34 +12,27 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; -import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getAnnotationMirror; -import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getAnnotationValue; -import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getStringValue; import java.io.BufferedWriter; import java.io.OutputStreamWriter; import java.util.Set; -import java.util.regex.Pattern; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.SourceVersion; -import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; import javax.tools.StandardLocation; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.configuration.api.ConfigurationParameter; import jakarta.json.Json; /// Collects all configuration parameters marked with -/// {@link ConfigurationParameter} into [Spring Boot Configuration +/// {@link org.junit.platform.configuration.api.ConfigurationParameter} into +/// [Spring Boot Configuration /// Metadata](https://docs.spring.io/spring-boot/specification/configuration-metadata/format.html) /// and writes it to {@value #METADATA_PATH}. This enables IDE's and other tools /// to process and validate Test Engine configuration. @@ -64,14 +57,17 @@ /// @API(status = API.Status.EXPERIMENTAL) @SupportedAnnotationTypes("org.junit.platform.configuration.api.ConfigurationParameter") -public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor { +public final class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor { private static final String METADATA_PATH = "META-INF/junit-platform-configuration-metadata.json"; private @Nullable ConfigurationMetaData metaData; + private @Nullable ConfigurationParameterHandler configurationParameterHandler; @Override public synchronized void init(ProcessingEnvironment environment) { super.init(environment); this.metaData = new ConfigurationMetaData(); + this.configurationParameterHandler = new ConfigurationParameterHandler(metaData, + processingEnv.getElementUtils()); } @Override @@ -81,7 +77,7 @@ public SourceVersion getSupportedSourceVersion() { @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { - processConfigurationParameter(roundEnv); + configurationParameterHandler().process(roundEnv); if (roundEnv.processingOver()) { writeMetaData(); @@ -105,100 +101,8 @@ private void writeMetaData() { } } - private void processConfigurationParameter(RoundEnvironment roundEnv) { - roundEnv.getElementsAnnotatedWith(ConfigurationParameter.class).stream() // - .filter(VariableElement.class::isInstance) // - .map(VariableElement.class::cast) // - .map(this::createProperty) // - .forEach(element -> metaData().addProperty(element)); - } - - private ConfigurationMetaData.Property createProperty(VariableElement element) { - return new ConfigurationMetaData.Property( // - processName(element), // - null, // TODO: - processDescription(element), // - processSourceType(element), // - null, // TODO: - processDeprecation(element) // - ); - } - - private ConfigurationMetaData.@Nullable Deprecation processDeprecation(VariableElement element) { - var parameter = getAnnotationMirror(element, ConfigurationParameter.class); - if (parameter == null) { - return null; - } - var deprecation = getAnnotationValue(parameter, "deprecation"); - if (deprecation != null) { - var reason = getStringValue(deprecation, "reason"); - var replacement = getStringValue(deprecation, "replacement"); - var since = getStringValue(deprecation, "since"); - if (reason != null || replacement != null || since != null) { - return new ConfigurationMetaData.Deprecation(null, reason, replacement, since); - } - } - // Fallback, look for @Deprecated - if (getAnnotationMirror(element, Deprecated.class) != null) { - return new ConfigurationMetaData.Deprecation(null, null, null, null); - } - return null; - } - - private @Nullable String processDescription(VariableElement element) { - var docComment = processingEnvironment().getElementUtils().getDocComment(element); - return docComment == null ? null : cleanupDocComment(docComment); - } - - private static String cleanupDocComment(String docComment) { - // TODO: Creating patterns over and over is not very efficient - var matcher = Pattern.compile("

|").matcher(docComment); - var firstParagraph = !matcher.find() ? docComment : docComment.substring(0, matcher.start()); - return firstParagraph // - // Replace newlines with space - .replaceAll("[\n\r]", " ") // - // Merge multiple spaces - .replaceAll(" +", " ") // - // Replace the `: {@value}` conventional syntax. - .replaceAll(": \\{@value}\\.?", ".") // - .trim(); - } - - private String processSourceType(VariableElement element) { - var enclosingTypeElement = getEnclosingTypeElement(element); - return enclosingTypeElement.getQualifiedName().toString(); - } - - private String processName(VariableElement element) { - // TODO: Report preconditions problems with processingEnvironment().getMessager().printMessage() instead. - var enclosingTypeElement = getEnclosingTypeElement(element); - Preconditions.condition(isStatic(element), // - () -> "Field [%s.%s] must be declared static" // - .formatted(enclosingTypeElement.getQualifiedName(), element.getSimpleName())); - Preconditions.condition(isFinal(element), // - () -> "Field [%s.%s] must be declared final" // - .formatted(enclosingTypeElement.getQualifiedName(), element.getSimpleName())); - var constantValue = element.getConstantValue(); - Preconditions.condition(constantValue instanceof String, // - () -> "Field [%s.%s] must have a constant string value" // - .formatted(enclosingTypeElement.getQualifiedName(), element.getSimpleName())); - return (String) constantValue; - } - - private TypeElement getEnclosingTypeElement(VariableElement element) { - var enclosingElement = element.getEnclosingElement(); - Preconditions.condition(enclosingElement instanceof TypeElement, // - () -> "[%s] did not have an enclosing type element" // - .formatted(element.getSimpleName())); - return (TypeElement) enclosingElement; - } - - private static boolean isStatic(VariableElement variableElement) { - return variableElement.getModifiers().contains(Modifier.STATIC); - } - - private static boolean isFinal(VariableElement variableElement) { - return variableElement.getModifiers().contains(Modifier.FINAL); + private ConfigurationParameterHandler configurationParameterHandler() { + return requireNonNull(configurationParameterHandler); } private ProcessingEnvironment processingEnvironment() { diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java new file mode 100644 index 000000000000..f3305a37a781 --- /dev/null +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java @@ -0,0 +1,134 @@ +/* + * Copyright 2026 the original author or 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.configuration.processor; + +import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getAnnotationMirror; +import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getAnnotationValue; +import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getStringValue; + +import java.util.regex.Pattern; + +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.util.Elements; + +import org.jspecify.annotations.Nullable; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.configuration.api.ConfigurationParameter; + +final class ConfigurationParameterHandler { + + private final ConfigurationMetaData metaData; + private final Elements elementUtils; + + ConfigurationParameterHandler(ConfigurationMetaData metaData, Elements elementUtils) { + this.metaData = metaData; + this.elementUtils = elementUtils; + } + + void process(RoundEnvironment roundEnv) { + roundEnv.getElementsAnnotatedWith(ConfigurationParameter.class).stream() // + .filter(VariableElement.class::isInstance) // + .map(VariableElement.class::cast) // + .map(this::createProperty) // + .forEach(metaData::addProperty); + } + + private ConfigurationMetaData.Property createProperty(VariableElement element) { + return new ConfigurationMetaData.Property( // + processName(element), // + null, // TODO: + processDescription(element), // + processSourceType(element), // + null, // TODO: + processDeprecation(element) // + ); + } + + private ConfigurationMetaData.@Nullable Deprecation processDeprecation(VariableElement element) { + var parameter = getAnnotationMirror(element, ConfigurationParameter.class); + if (parameter == null) { + return null; + } + var deprecation = getAnnotationValue(parameter, "deprecation"); + if (deprecation != null) { + var reason = getStringValue(deprecation, "reason"); + var replacement = getStringValue(deprecation, "replacement"); + var since = getStringValue(deprecation, "since"); + if (reason != null || replacement != null || since != null) { + return new ConfigurationMetaData.Deprecation(null, reason, replacement, since); + } + } + // Fallback, look for @Deprecated + if (getAnnotationMirror(element, Deprecated.class) != null) { + return new ConfigurationMetaData.Deprecation(null, null, null, null); + } + return null; + } + + private @Nullable String processDescription(VariableElement element) { + var docComment = elementUtils.getDocComment(element); + return docComment == null ? null : cleanupDocComment(docComment); + } + + private static String cleanupDocComment(String docComment) { + // TODO: Creating patterns over and over is not very efficient + var matcher = Pattern.compile("

|").matcher(docComment); + var firstParagraph = !matcher.find() ? docComment : docComment.substring(0, matcher.start()); + return firstParagraph // + // Replace newlines with space + .replaceAll("[\n\r]", " ") // + // Merge multiple spaces + .replaceAll(" +", " ") // + // Replace the `: {@value}` conventional syntax. + .replaceAll(": \\{@value}\\.?", ".") // + .trim(); + } + + private String processSourceType(VariableElement element) { + var enclosingTypeElement = getEnclosingTypeElement(element); + return enclosingTypeElement.getQualifiedName().toString(); + } + + private String processName(VariableElement element) { + // TODO: Report preconditions problems with processingEnvironment().getMessager().printMessage() instead. + var enclosingTypeElement = getEnclosingTypeElement(element); + Preconditions.condition(isStatic(element), // + () -> "Field [%s.%s] must be declared static" // + .formatted(enclosingTypeElement.getQualifiedName(), element.getSimpleName())); + Preconditions.condition(isFinal(element), // + () -> "Field [%s.%s] must be declared final" // + .formatted(enclosingTypeElement.getQualifiedName(), element.getSimpleName())); + var constantValue = element.getConstantValue(); + Preconditions.condition(constantValue instanceof String, // + () -> "Field [%s.%s] must have a constant string value" // + .formatted(enclosingTypeElement.getQualifiedName(), element.getSimpleName())); + return (String) constantValue; + } + + private TypeElement getEnclosingTypeElement(VariableElement element) { + var enclosingElement = element.getEnclosingElement(); + Preconditions.condition(enclosingElement instanceof TypeElement, // + () -> "[%s] did not have an enclosing type element" // + .formatted(element.getSimpleName())); + return (TypeElement) enclosingElement; + } + + private static boolean isStatic(VariableElement variableElement) { + return variableElement.getModifiers().contains(Modifier.STATIC); + } + + private static boolean isFinal(VariableElement variableElement) { + return variableElement.getModifiers().contains(Modifier.FINAL); + } +} diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java index 97f5f15e87fa..3820f73cc41b 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java @@ -27,7 +27,7 @@ import jakarta.json.JsonObject; import jakarta.json.JsonValue; -class JsonConverter { +final class JsonConverter { private final Map config = Map.of(); private final JsonBuilderFactory factory = Json.createBuilderFactory(config); From 8c264aaf78696752a61a5e316d355c2e805a7e17 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 9 Aug 2026 21:41:48 +0200 Subject: [PATCH 047/117] Reorg ConfigurationParameterHandler --- .../ConfigurationParameterHandler.java | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java index f3305a37a781..815b8c78684e 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java @@ -55,25 +55,20 @@ private ConfigurationMetaData.Property createProperty(VariableElement element) { ); } - private ConfigurationMetaData.@Nullable Deprecation processDeprecation(VariableElement element) { - var parameter = getAnnotationMirror(element, ConfigurationParameter.class); - if (parameter == null) { - return null; - } - var deprecation = getAnnotationValue(parameter, "deprecation"); - if (deprecation != null) { - var reason = getStringValue(deprecation, "reason"); - var replacement = getStringValue(deprecation, "replacement"); - var since = getStringValue(deprecation, "since"); - if (reason != null || replacement != null || since != null) { - return new ConfigurationMetaData.Deprecation(null, reason, replacement, since); - } - } - // Fallback, look for @Deprecated - if (getAnnotationMirror(element, Deprecated.class) != null) { - return new ConfigurationMetaData.Deprecation(null, null, null, null); - } - return null; + private String processName(VariableElement element) { + // TODO: Report preconditions problems with processingEnvironment().getMessager().printMessage() instead. + var enclosingTypeElement = getEnclosingTypeElement(element); + Preconditions.condition(isStatic(element), // + () -> "Field [%s.%s] must be declared static" // + .formatted(enclosingTypeElement.getQualifiedName(), element.getSimpleName())); + Preconditions.condition(isFinal(element), // + () -> "Field [%s.%s] must be declared final" // + .formatted(enclosingTypeElement.getQualifiedName(), element.getSimpleName())); + var constantValue = element.getConstantValue(); + Preconditions.condition(constantValue instanceof String, // + () -> "Field [%s.%s] must have a constant string value" // + .formatted(enclosingTypeElement.getQualifiedName(), element.getSimpleName())); + return (String) constantValue; } private @Nullable String processDescription(VariableElement element) { @@ -100,22 +95,6 @@ private String processSourceType(VariableElement element) { return enclosingTypeElement.getQualifiedName().toString(); } - private String processName(VariableElement element) { - // TODO: Report preconditions problems with processingEnvironment().getMessager().printMessage() instead. - var enclosingTypeElement = getEnclosingTypeElement(element); - Preconditions.condition(isStatic(element), // - () -> "Field [%s.%s] must be declared static" // - .formatted(enclosingTypeElement.getQualifiedName(), element.getSimpleName())); - Preconditions.condition(isFinal(element), // - () -> "Field [%s.%s] must be declared final" // - .formatted(enclosingTypeElement.getQualifiedName(), element.getSimpleName())); - var constantValue = element.getConstantValue(); - Preconditions.condition(constantValue instanceof String, // - () -> "Field [%s.%s] must have a constant string value" // - .formatted(enclosingTypeElement.getQualifiedName(), element.getSimpleName())); - return (String) constantValue; - } - private TypeElement getEnclosingTypeElement(VariableElement element) { var enclosingElement = element.getEnclosingElement(); Preconditions.condition(enclosingElement instanceof TypeElement, // @@ -124,6 +103,27 @@ private TypeElement getEnclosingTypeElement(VariableElement element) { return (TypeElement) enclosingElement; } + private ConfigurationMetaData.@Nullable Deprecation processDeprecation(VariableElement element) { + var parameter = getAnnotationMirror(element, ConfigurationParameter.class); + if (parameter == null) { + return null; + } + var deprecation = getAnnotationValue(parameter, "deprecation"); + if (deprecation != null) { + var reason = getStringValue(deprecation, "reason"); + var replacement = getStringValue(deprecation, "replacement"); + var since = getStringValue(deprecation, "since"); + if (reason != null || replacement != null || since != null) { + return new ConfigurationMetaData.Deprecation(null, reason, replacement, since); + } + } + // Fallback, look for @Deprecated + if (getAnnotationMirror(element, Deprecated.class) != null) { + return new ConfigurationMetaData.Deprecation(null, null, null, null); + } + return null; + } + private static boolean isStatic(VariableElement variableElement) { return variableElement.getModifiers().contains(Modifier.STATIC); } From c0121e22870f48c32d4d0001c6935671aa88b075 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 9 Aug 2026 21:58:18 +0200 Subject: [PATCH 048/117] Add type and default value --- .../api/ConfigurationParameter.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java index ed6daa1ab2ca..02acb0050835 100644 --- a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java +++ b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java @@ -28,6 +28,21 @@ @Target(ElementType.FIELD) public @interface ConfigurationParameter { + /** + * The type of the data type of the parameter. + * + * @return the signature of the data type of the parameter. + */ + Class[] type() default Void.class; + + /** + * The default value used if the parameter is not specified. It can be a + * list of values if the parameter supports comma separated values. + * + * @return the default values. + */ + Value defaultValue() default @Value; + /** * Specifies that the parameter is deprecated. * @@ -59,4 +74,58 @@ */ String since() default ""; } + + @interface Value { + + /** + * The {@code short} values to use as the defaults; must not be empty. + */ + short[] shorts() default {}; + + /** + * The {@code byte} values to use as the defaults; must not be empty. + */ + byte[] bytes() default {}; + + /** + * The {@code int} values to use as the defaults; must not be empty. + */ + int[] ints() default {}; + + /** + * The {@code long} values to use as the defaults; must not be empty. + */ + long[] longs() default {}; + + /** + * The {@code float} values to use as the defaults; must not be empty. + */ + float[] floats() default {}; + + /** + * The {@code double} values to use as the defaults; must not be empty. + */ + double[] doubles() default {}; + + /** + * The {@code char} values to use as the defaults; must not be empty. + */ + char[] chars() default {}; + + /** + * The {@code boolean} values to use as the defaults; must not be empty. + */ + boolean[] booleans() default {}; + + /** + * The {@link String} values to use as the defaults; must not be empty. + */ + String[] strings() default {}; + + /** + * The {@link Class} values to use as the defaults; must not be empty. + */ + Class[] classes() default {}; + } + } From e4ec3dfbe76bef60b83dc498e5d88d8b03a5c508 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 10 Aug 2026 00:06:21 +0200 Subject: [PATCH 049/117] Read type and default value from annotation --- .../api/ConfigurationParameter.java | 4 +- .../processor/AnnotationMirrorUtil.java | 35 +++++- .../processor/ConfigurationMetaData.java | 12 +- .../ConfigurationParameterHandler.java | 80 ++++++++++-- .../processor/JsonConverter.java | 19 +-- ...ationMetadataAnnotationProcessorTests.java | 118 +++++++++++++++++- .../testcases/DefaultMultiple.java | 21 ++++ .../testcases/DefaultMultipleSets.java | 21 ++++ .../configuration/testcases/Defaults.java | 52 ++++++++ .../configuration/testcases/TypeString.java | 20 +++ 10 files changed, 340 insertions(+), 42 deletions(-) create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultMultiple.java create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultMultipleSets.java create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/testcases/Defaults.java create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeString.java diff --git a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java index 02acb0050835..ceeecd7ae5b7 100644 --- a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java +++ b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java @@ -30,10 +30,12 @@ /** * The type of the data type of the parameter. + *

+ * If the type is left blank, the type of the default value is used. * * @return the signature of the data type of the parameter. */ - Class[] type() default Void.class; + Class type() default Void.class; /** * The default value used if the parameter is not specified. It can be a diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java index 1cfb268d28d8..388994ab27a5 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java @@ -10,17 +10,23 @@ package org.junit.platform.configuration.processor; +import static java.util.stream.Collectors.toMap; + import java.lang.annotation.Annotation; +import java.util.Collections; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; import org.jspecify.annotations.Nullable; -final class AnnotationMirrorUtil { +class AnnotationMirrorUtil { private AnnotationMirrorUtil() { /* no-op */ @@ -38,8 +44,7 @@ private AnnotationMirrorUtil() { static @Nullable String getStringValue(AnnotationMirror annotation, String elementName) { return findElementBy(annotation, elementName) // - .filter(String.class::isInstance) // - .map(String.class::cast) // + .map(Object::toString) // .filter(s -> !s.isEmpty()) // .orElse(null); } @@ -55,8 +60,30 @@ private static Optional findElementBy(AnnotationMirror annotation, Strin return annotation.getElementValues().entrySet() // .stream() // .filter((element) -> element.getKey().getSimpleName().toString().equals(elementName)) // - .map(Map.Entry::getValue) // + .map(Entry::getValue) // .map(AnnotationValue::getValue) // .findFirst(); } + + static Map> getStringValuesMap(AnnotationMirror defaults) { + return defaults.getElementValues().entrySet() // + .stream() // + .collect(toMap(AnnotationMirrorUtil::getSimpleName, AnnotationMirrorUtil::getStringValues)); + } + + private static List getStringValues(Entry entry) { + if (entry.getValue().getValue() instanceof List values) { + return values.stream().filter(AnnotationValue.class::isInstance) // + .map(AnnotationValue.class::cast) // + .map(AnnotationValue::getValue) // + .map(Object::toString) // + .filter(s -> !s.isEmpty()) // + .toList(); + } + return Collections.emptyList(); + } + + private static String getSimpleName(Entry entry) { + return entry.getKey().getSimpleName().toString(); + } } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java index 401a1e1f4fdc..03fe91adfd51 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java @@ -35,21 +35,13 @@ record Property( // @Nullable String type, // @Nullable String description, // @Nullable String sourceType, // - @Nullable OneOrMany defaultValue, // + @Nullable CommaSeparatedList defaultValue, // @Nullable Deprecation deprecation // ) { } - sealed interface OneOrMany { - - record One(T value) implements OneOrMany { - - } - - record Many(List values) implements OneOrMany { - - } + record CommaSeparatedList(List elements) { } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java index 815b8c78684e..9253607f3209 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java @@ -10,10 +10,13 @@ package org.junit.platform.configuration.processor; +import static java.util.Objects.requireNonNull; import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getAnnotationMirror; import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getAnnotationValue; import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getStringValue; +import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getStringValuesMap; +import java.util.Map; import java.util.regex.Pattern; import javax.annotation.processing.RoundEnvironment; @@ -25,9 +28,24 @@ import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.configuration.api.ConfigurationParameter; +import org.junit.platform.configuration.processor.ConfigurationMetaData.CommaSeparatedList; +import org.junit.platform.configuration.processor.ConfigurationMetaData.Property; final class ConfigurationParameterHandler { + private static final Map NAME_TO_TYPE_NAME = Map.of( // + "shorts", Short.class.getName(), // + "bytes", Byte.class.getName(), // + "ints", Integer.class.getName(), // + "longs", Long.class.getName(), // + "floats", Float.class.getName(), // + "doubles", Double.class.getName(), // + "chars", Character.class.getName(), // + "booleans", Boolean.class.getName(), // + "strings", String.class.getName(), // + "classes", Class.class.getName() // + ); + private final ConfigurationMetaData metaData; private final Elements elementUtils; @@ -44,15 +62,16 @@ void process(RoundEnvironment roundEnv) { .forEach(metaData::addProperty); } - private ConfigurationMetaData.Property createProperty(VariableElement element) { - return new ConfigurationMetaData.Property( // - processName(element), // - null, // TODO: - processDescription(element), // - processSourceType(element), // - null, // TODO: - processDeprecation(element) // - ); + private Property createProperty(VariableElement element) { + var name = processName(element); + var description = processDescription(element); + var sourceType = processSourceType(element); + var defaults = processDefaults(element); + var deprecation = processDeprecation(element); + var defaultType = defaults == null ? null : defaults.defaultType(); + var defaultValue = defaults == null ? null : defaults.value(); + var type = processType(element, defaultType); + return new Property(name, type, description, sourceType, defaultValue, deprecation); } private String processName(VariableElement element) { @@ -71,6 +90,20 @@ private String processName(VariableElement element) { return (String) constantValue; } + private @Nullable String processType(VariableElement element, @Nullable String defaultType) { + var parameter = getAnnotationMirror(element, ConfigurationParameter.class); + if (parameter == null) { + return defaultType; + } + + var type = getStringValue(parameter, "type"); + if (type == null || type.equals(Void.class.getName())) { + return defaultType; + } + + return type; + } + private @Nullable String processDescription(VariableElement element) { var docComment = elementUtils.getDocComment(element); return docComment == null ? null : cleanupDocComment(docComment); @@ -103,6 +136,35 @@ private TypeElement getEnclosingTypeElement(VariableElement element) { return (TypeElement) enclosingElement; } + private @Nullable Default processDefaults(VariableElement element) { + var parameter = getAnnotationMirror(element, ConfigurationParameter.class); + if (parameter == null) { + return null; + } + + var defaults = getAnnotationValue(parameter, "defaultValue"); + if (defaults == null) { + return null; + } + + var defaultValues = getStringValuesMap(defaults); + Preconditions.condition(defaultValues.size() == 1, () -> { + var enclosingTypeElement = getEnclosingTypeElement(element); + return "Field [%s.%s] must have exactly one (set of) default value(s)".formatted(enclosingTypeElement, + element.getSimpleName()); + }); + + return defaultValues.entrySet().stream() // + .map(entry -> new Default( // + requireNonNull(NAME_TO_TYPE_NAME.get(entry.getKey())), + new CommaSeparatedList<>(entry.getValue()))).findFirst() // + .orElse(null); + } + + private record Default(String defaultType, CommaSeparatedList value) { + + } + private ConfigurationMetaData.@Nullable Deprecation processDeprecation(VariableElement element) { var parameter = getAnnotationMirror(element, ConfigurationParameter.class); if (parameter == null) { diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java index 3820f73cc41b..753156fc25df 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java @@ -14,15 +14,13 @@ import java.util.Map; import java.util.function.Function; +import org.junit.platform.configuration.processor.ConfigurationMetaData.CommaSeparatedList; import org.junit.platform.configuration.processor.ConfigurationMetaData.Deprecation; import org.junit.platform.configuration.processor.ConfigurationMetaData.Deprecation.Level; -import org.junit.platform.configuration.processor.ConfigurationMetaData.OneOrMany.Many; -import org.junit.platform.configuration.processor.ConfigurationMetaData.OneOrMany.One; import org.junit.platform.configuration.processor.ConfigurationMetaData.Property; import jakarta.json.Json; import jakarta.json.JsonArray; -import jakarta.json.JsonArrayBuilder; import jakarta.json.JsonBuilderFactory; import jakarta.json.JsonObject; import jakarta.json.JsonValue; @@ -63,12 +61,7 @@ private JsonObject toJsonObject(Property property) { var defaultValue = property.defaultValue(); if (defaultValue != null) { - if (defaultValue instanceof One one) { - builder.add("defaultValue", toJsonValue(one)); - } - else if (defaultValue instanceof Many many) { - builder.add("defaultValue", toJsonValue(many)); - } + builder.add("defaultValue", toJsonValue(defaultValue)); } var deprecation = property.deprecation(); @@ -79,12 +72,8 @@ else if (defaultValue instanceof Many many) { return builder.build(); } - private String toJsonValue(One one) { - return one.value(); - } - - private JsonArrayBuilder toJsonValue(Many many) { - return factory.createArrayBuilder(many.values()); + private String toJsonValue(CommaSeparatedList values) { + return String.join(", ", values.elements()); } private JsonObject toJsonObject(Deprecation deprecation) { diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index d3a34a4b5a4b..0f738ddba003 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -18,7 +18,6 @@ import java.nio.file.Files; import java.nio.file.Path; -import org.assertj.core.api.AbstractStringAssert; import org.assertj.core.api.ThrowableAssert; import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.BeforeEach; @@ -26,6 +25,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.configuration.testcases.DefaultMultiple; +import org.junit.platform.configuration.testcases.DefaultMultipleSets; +import org.junit.platform.configuration.testcases.Defaults; import org.junit.platform.configuration.testcases.Deprecation; import org.junit.platform.configuration.testcases.DeprecationWithDetails; import org.junit.platform.configuration.testcases.Documented; @@ -37,6 +39,7 @@ import org.junit.platform.configuration.testcases.NonFinal; import org.junit.platform.configuration.testcases.NonStatic; import org.junit.platform.configuration.testcases.NonString; +import org.junit.platform.configuration.testcases.TypeString; import org.junit.platform.configuration.testcases.Without; class ConfigurationMetadataAnnotationProcessorTests { @@ -191,6 +194,108 @@ void deprecationWithDetails() { }"""); } + @Test + void stringType() { + compiler.compile(TypeString.class); + assertMetaDataIsEqualTo(""" + { + "properties": [ + { + "name": "org.example.property", + "type": "java.lang.String", + "sourceType": "org.junit.platform.configuration.testcases.TypeString" + } + ] + }"""); + } + + @Test + void defaultsMultiple() { + compiler.compile(DefaultMultiple.class); + assertMetaDataIsEqualTo(""" + { + "properties": [ + { + "name": "org.example.property", + "type": "java.lang.String", + "sourceType": "org.junit.platform.configuration.testcases.DefaultMultiple", + "defaultValue": "default, another-default" + } + ] + }"""); + } + + @Test + void defaults() { + compiler.compile(Defaults.class); + assertMetaDataIsEqualTo(""" + { + "properties": [ + { + "name": "org.example.shorts", + "type": "java.lang.Short", + "sourceType": "org.junit.platform.configuration.testcases.Defaults", + "defaultValue": "1" + }, + { + "name": "org.example.bytes", + "type": "java.lang.Byte", + "sourceType": "org.junit.platform.configuration.testcases.Defaults", + "defaultValue": "42" + }, + { + "name": "org.example.ints", + "type": "java.lang.Integer", + "sourceType": "org.junit.platform.configuration.testcases.Defaults", + "defaultValue": "42" + }, + { + "name": "org.example.longs", + "type": "java.lang.Long", + "sourceType": "org.junit.platform.configuration.testcases.Defaults", + "defaultValue": "42" + }, + { + "name": "org.example.floats", + "type": "java.lang.Float", + "sourceType": "org.junit.platform.configuration.testcases.Defaults", + "defaultValue": "42.0" + }, + { + "name": "org.example.doubles", + "type": "java.lang.Double", + "sourceType": "org.junit.platform.configuration.testcases.Defaults", + "defaultValue": "42.0" + }, + { + "name": "org.example.chars", + "type": "java.lang.Character", + "sourceType": "org.junit.platform.configuration.testcases.Defaults", + "defaultValue": "4" + }, + { + "name": "org.example.booleans", + "type": "java.lang.Boolean", + "sourceType": "org.junit.platform.configuration.testcases.Defaults", + "defaultValue": "true" + }, + { + "name": "org.example.strings", + "type": "java.lang.String", + "sourceType": "org.junit.platform.configuration.testcases.Defaults", + "defaultValue": "default" + }, + { + "name": "org.example.classes", + "type": "java.lang.Class", + "sourceType": "org.junit.platform.configuration.testcases.Defaults", + "defaultValue": "org.junit.platform.configuration.testcases.Defaults.Example" + } + ] + } + """); + } + @Test void mustBeFinal() { asserPreconditionViolation(() -> compiler.compile(NonFinal.class), @@ -210,8 +315,15 @@ void mustBeString() { NonString.class.getName())); } - private AbstractStringAssert assertMetaDataIsEqualTo(@Language("JSON") String json) { - return assertThat(metaData()).isEqualToIgnoringWhitespace(json); + @Test + void mustHaveExactlyOneSetOfDefaults() { + asserPreconditionViolation(() -> compiler.compile(DefaultMultipleSets.class), + "Field [%s.EXAMPLE_PROPERTY_NAME] must have exactly one (set of) default value(s)".formatted( + DefaultMultipleSets.class.getName())); + } + + private void assertMetaDataIsEqualTo(@Language("JSON") String json) { + assertThat(metaData()).isEqualToIgnoringWhitespace(json); } private String metaData() throws UncheckedIOException { diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultMultiple.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultMultiple.java new file mode 100644 index 000000000000..3eb4648b4da6 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultMultiple.java @@ -0,0 +1,21 @@ +/* + * Copyright 2026 the original author or 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.configuration.testcases; + +import org.junit.platform.configuration.api.ConfigurationParameter; +import org.junit.platform.configuration.api.ConfigurationParameter.Value; + +public final class DefaultMultiple { + + @ConfigurationParameter(defaultValue = @Value(strings = { "default", "another-default" })) + public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; + +} diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultMultipleSets.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultMultipleSets.java new file mode 100644 index 000000000000..a34b54f7905f --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultMultipleSets.java @@ -0,0 +1,21 @@ +/* + * Copyright 2026 the original author or 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.configuration.testcases; + +import org.junit.platform.configuration.api.ConfigurationParameter; +import org.junit.platform.configuration.api.ConfigurationParameter.Value; + +public final class DefaultMultipleSets { + + @ConfigurationParameter(defaultValue = @Value(strings = "default", ints = 42)) + public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; + +} diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Defaults.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Defaults.java new file mode 100644 index 000000000000..06e388673f3b --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Defaults.java @@ -0,0 +1,52 @@ +/* + * Copyright 2026 the original author or 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.configuration.testcases; + +import org.junit.platform.configuration.api.ConfigurationParameter; +import org.junit.platform.configuration.api.ConfigurationParameter.Value; + +public final class Defaults { + + @ConfigurationParameter(defaultValue = @Value(shorts = 1)) + public static final String SHORTS_PROPERTY_NAME = "org.example.shorts"; + + @ConfigurationParameter(defaultValue = @Value(bytes = 0x2A)) + public static final String BYTES_PROPERTY_NAME = "org.example.bytes"; + + @ConfigurationParameter(defaultValue = @Value(ints = 42)) + public static final String INTS_PROPERTY_NAME = "org.example.ints"; + + @ConfigurationParameter(defaultValue = @Value(longs = 42)) + public static final String LONGS_PROPERTY_NAME = "org.example.longs"; + + @ConfigurationParameter(defaultValue = @Value(floats = 42.0f)) + public static final String FLOATS_PROPERTY_NAME = "org.example.floats"; + + @ConfigurationParameter(defaultValue = @Value(doubles = 42.0)) + public static final String DOUBLES_PROPERTY_NAME = "org.example.doubles"; + + @ConfigurationParameter(defaultValue = @Value(chars = '4')) + public static final String CHARS_PROPERTY_NAME = "org.example.chars"; + + @ConfigurationParameter(defaultValue = @Value(booleans = true)) + public static final String BOOLEANS_PROPERTY_NAME = "org.example.booleans"; + + @ConfigurationParameter(defaultValue = @Value(strings = "default")) + public static final String STRING_PROPERTY_NAME = "org.example.strings"; + + @ConfigurationParameter(defaultValue = @Value(classes = Example.class)) + public static final String CLASSES_PROPERTY_NAME = "org.example.classes"; + + private record Example() { + + } + +} diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeString.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeString.java new file mode 100644 index 000000000000..62db3ca4d50c --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeString.java @@ -0,0 +1,20 @@ +/* + * Copyright 2026 the original author or 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.configuration.testcases; + +import org.junit.platform.configuration.api.ConfigurationParameter; + +public final class TypeString { + + @ConfigurationParameter(type = String.class) + public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; + +} From 70f6af3d4eeaa2e79703634f126a18596448328b Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 10 Aug 2026 16:19:30 +0200 Subject: [PATCH 050/117] Only support singular default values for now --- .../api/ConfigurationParameter.java | 47 +++++++++--------- .../processor/ConfigurationMetaData.java | 6 +-- .../ConfigurationParameterHandler.java | 49 ++++++++++++------- .../processor/JsonConverter.java | 7 +-- ...ationMetadataAnnotationProcessorTests.java | 43 +++++++--------- ...ultiple.java => DefaultDifferentSets.java} | 4 +- ...leSets.java => DefaultMultipleValues.java} | 4 +- .../configuration/testcases/Defaults.java | 20 ++++---- 8 files changed, 89 insertions(+), 91 deletions(-) rename platform-tests/src/test/java/org/junit/platform/configuration/testcases/{DefaultMultiple.java => DefaultDifferentSets.java} (81%) rename platform-tests/src/test/java/org/junit/platform/configuration/testcases/{DefaultMultipleSets.java => DefaultMultipleValues.java} (80%) diff --git a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java index ceeecd7ae5b7..23c3e058ac7e 100644 --- a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java +++ b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java @@ -38,8 +38,8 @@ Class type() default Void.class; /** - * The default value used if the parameter is not specified. It can be a - * list of values if the parameter supports comma separated values. + * The default value used if the parameter is not specified. + * At most one value may be set. * * @return the default values. */ @@ -77,57 +77,60 @@ String since() default ""; } + /** + * The value to use as the default. At most one value may be set. + */ @interface Value { /** - * The {@code short} values to use as the defaults; must not be empty. + * The {@code short} value to use as the default. */ - short[] shorts() default {}; + short[] shortValue() default {}; /** - * The {@code byte} values to use as the defaults; must not be empty. + * The {@code byte} value to use as the default. */ - byte[] bytes() default {}; + byte[] byteValue() default {}; /** - * The {@code int} values to use as the defaults; must not be empty. + * The {@code int} value to use as the default. */ - int[] ints() default {}; + int[] intValue() default {}; /** - * The {@code long} values to use as the defaults; must not be empty. + * The {@code long} value to use as the default. */ - long[] longs() default {}; + long[] longValue() default {}; /** - * The {@code float} values to use as the defaults; must not be empty. + * The {@code float} value to use as the default. */ - float[] floats() default {}; + float[] floatValue() default {}; /** - * The {@code double} values to use as the defaults; must not be empty. + * The {@code double} value to use as the default. */ - double[] doubles() default {}; + double[] doubleValue() default {}; /** - * The {@code char} values to use as the defaults; must not be empty. + * The {@code char} value to use as the default. */ - char[] chars() default {}; + char[] charValue() default {}; /** - * The {@code boolean} values to use as the defaults; must not be empty. + * The {@code boolean} value to use as the default. */ - boolean[] booleans() default {}; + boolean[] booleanValue() default {}; /** - * The {@link String} values to use as the defaults; must not be empty. + * The {@link String} value to use as the default. */ - String[] strings() default {}; + String[] stringValue() default {}; /** - * The {@link Class} values to use as the defaults; must not be empty. + * The {@link Class} value to use as the default. */ - Class[] classes() default {}; + Class[] classValue() default {}; } } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java index 03fe91adfd51..0228ccd5a3e0 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java @@ -35,16 +35,12 @@ record Property( // @Nullable String type, // @Nullable String description, // @Nullable String sourceType, // - @Nullable CommaSeparatedList defaultValue, // + @Nullable String defaultValue, // @Nullable Deprecation deprecation // ) { } - record CommaSeparatedList(List elements) { - - } - record Deprecation( // @Nullable Level level, // @Nullable String reason, // diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java index 9253607f3209..8b8ad43a4560 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java @@ -28,22 +28,21 @@ import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.configuration.api.ConfigurationParameter; -import org.junit.platform.configuration.processor.ConfigurationMetaData.CommaSeparatedList; import org.junit.platform.configuration.processor.ConfigurationMetaData.Property; final class ConfigurationParameterHandler { private static final Map NAME_TO_TYPE_NAME = Map.of( // - "shorts", Short.class.getName(), // - "bytes", Byte.class.getName(), // - "ints", Integer.class.getName(), // - "longs", Long.class.getName(), // - "floats", Float.class.getName(), // - "doubles", Double.class.getName(), // - "chars", Character.class.getName(), // - "booleans", Boolean.class.getName(), // - "strings", String.class.getName(), // - "classes", Class.class.getName() // + "shortValue", Short.class.getName(), // + "byteValue", Byte.class.getName(), // + "intValue", Integer.class.getName(), // + "longValue", Long.class.getName(), // + "floatValue", Float.class.getName(), // + "doubleValue", Double.class.getName(), // + "charValue", Character.class.getName(), // + "booleanValue", Boolean.class.getName(), // + "stringValue", String.class.getName(), // + "classValue", Class.class.getName() // ); private final ConfigurationMetaData metaData; @@ -150,18 +149,30 @@ private TypeElement getEnclosingTypeElement(VariableElement element) { var defaultValues = getStringValuesMap(defaults); Preconditions.condition(defaultValues.size() == 1, () -> { var enclosingTypeElement = getEnclosingTypeElement(element); - return "Field [%s.%s] must have exactly one (set of) default value(s)".formatted(enclosingTypeElement, - element.getSimpleName()); + return "Field [%s.%s] must have exactly one default value" // + .formatted(enclosingTypeElement, element.getSimpleName()); }); - return defaultValues.entrySet().stream() // - .map(entry -> new Default( // - requireNonNull(NAME_TO_TYPE_NAME.get(entry.getKey())), - new CommaSeparatedList<>(entry.getValue()))).findFirst() // - .orElse(null); + Preconditions.condition(defaultValues.size() == 1, () -> { + var enclosingTypeElement = getEnclosingTypeElement(element); + return "Field [%s.%s] must have exactly one default value" // + .formatted(enclosingTypeElement, element.getSimpleName()); + }); + + var entry = defaultValues.entrySet().iterator().next(); + var value = entry.getValue(); + Preconditions.condition(value.size() == 1, () -> { + var enclosingTypeElement = getEnclosingTypeElement(element); + return "Field [%s.%s] must have exactly one default value" // + .formatted(enclosingTypeElement, element.getSimpleName()); + }); + var defaultValue = value.get(0); + var defaultName = entry.getKey(); + var defaultType = requireNonNull(NAME_TO_TYPE_NAME.get(defaultName)); + return new Default(defaultType, defaultValue); } - private record Default(String defaultType, CommaSeparatedList value) { + private record Default(String defaultType, String value) { } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java index 753156fc25df..24656f69b1c1 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java @@ -14,7 +14,6 @@ import java.util.Map; import java.util.function.Function; -import org.junit.platform.configuration.processor.ConfigurationMetaData.CommaSeparatedList; import org.junit.platform.configuration.processor.ConfigurationMetaData.Deprecation; import org.junit.platform.configuration.processor.ConfigurationMetaData.Deprecation.Level; import org.junit.platform.configuration.processor.ConfigurationMetaData.Property; @@ -61,7 +60,7 @@ private JsonObject toJsonObject(Property property) { var defaultValue = property.defaultValue(); if (defaultValue != null) { - builder.add("defaultValue", toJsonValue(defaultValue)); + builder.add("defaultValue", defaultValue); } var deprecation = property.deprecation(); @@ -72,10 +71,6 @@ private JsonObject toJsonObject(Property property) { return builder.build(); } - private String toJsonValue(CommaSeparatedList values) { - return String.join(", ", values.elements()); - } - private JsonObject toJsonObject(Deprecation deprecation) { var builder = factory.createObjectBuilder(); diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index 0f738ddba003..09ea2e266e52 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -25,8 +25,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.configuration.testcases.DefaultMultiple; -import org.junit.platform.configuration.testcases.DefaultMultipleSets; +import org.junit.platform.configuration.testcases.DefaultDifferentSets; +import org.junit.platform.configuration.testcases.DefaultMultipleValues; import org.junit.platform.configuration.testcases.Defaults; import org.junit.platform.configuration.testcases.Deprecation; import org.junit.platform.configuration.testcases.DeprecationWithDetails; @@ -209,22 +209,6 @@ void stringType() { }"""); } - @Test - void defaultsMultiple() { - compiler.compile(DefaultMultiple.class); - assertMetaDataIsEqualTo(""" - { - "properties": [ - { - "name": "org.example.property", - "type": "java.lang.String", - "sourceType": "org.junit.platform.configuration.testcases.DefaultMultiple", - "defaultValue": "default, another-default" - } - ] - }"""); - } - @Test void defaults() { compiler.compile(Defaults.class); @@ -299,27 +283,36 @@ void defaults() { @Test void mustBeFinal() { asserPreconditionViolation(() -> compiler.compile(NonFinal.class), - "Field [%s.EXAMPLE_PROPERTY_NAME] must be declared final".formatted(NonFinal.class.getName())); + "Field [%s.EXAMPLE_PROPERTY_NAME] must be declared final" // + .formatted(NonFinal.class.getName())); } @Test void mustBeStatic() { asserPreconditionViolation(() -> compiler.compile(NonStatic.class), - "Field [%s.EXAMPLE_PROPERTY_NAME] must be declared static".formatted(NonStatic.class.getName())); + "Field [%s.EXAMPLE_PROPERTY_NAME] must be declared static" // + .formatted(NonStatic.class.getName())); } @Test void mustBeString() { asserPreconditionViolation(() -> compiler.compile(NonString.class), - "Field [%s.EXAMPLE_PROPERTY_NAME] must have a constant string value".formatted( - NonString.class.getName())); + "Field [%s.EXAMPLE_PROPERTY_NAME] must have a constant string value" // + .formatted(NonString.class.getName())); } @Test void mustHaveExactlyOneSetOfDefaults() { - asserPreconditionViolation(() -> compiler.compile(DefaultMultipleSets.class), - "Field [%s.EXAMPLE_PROPERTY_NAME] must have exactly one (set of) default value(s)".formatted( - DefaultMultipleSets.class.getName())); + asserPreconditionViolation(() -> compiler.compile(DefaultDifferentSets.class), + "Field [%s.EXAMPLE_PROPERTY_NAME] must have exactly one default value" // + .formatted(DefaultDifferentSets.class.getName())); + } + + @Test + void mustHaveExactlyOneDefaultValue() { + asserPreconditionViolation(() -> compiler.compile(DefaultMultipleValues.class), + "Field [%s.EXAMPLE_PROPERTY_NAME] must have exactly one default value" // + .formatted(DefaultMultipleValues.class.getName())); } private void assertMetaDataIsEqualTo(@Language("JSON") String json) { diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultMultiple.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultDifferentSets.java similarity index 81% rename from platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultMultiple.java rename to platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultDifferentSets.java index 3eb4648b4da6..cc80cd29f008 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultMultiple.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultDifferentSets.java @@ -13,9 +13,9 @@ import org.junit.platform.configuration.api.ConfigurationParameter; import org.junit.platform.configuration.api.ConfigurationParameter.Value; -public final class DefaultMultiple { +public final class DefaultDifferentSets { - @ConfigurationParameter(defaultValue = @Value(strings = { "default", "another-default" })) + @ConfigurationParameter(defaultValue = @Value(stringValue = "default", intValue = 42)) public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultMultipleSets.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultMultipleValues.java similarity index 80% rename from platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultMultipleSets.java rename to platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultMultipleValues.java index a34b54f7905f..712a5a9b751a 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultMultipleSets.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultMultipleValues.java @@ -13,9 +13,9 @@ import org.junit.platform.configuration.api.ConfigurationParameter; import org.junit.platform.configuration.api.ConfigurationParameter.Value; -public final class DefaultMultipleSets { +public final class DefaultMultipleValues { - @ConfigurationParameter(defaultValue = @Value(strings = "default", ints = 42)) + @ConfigurationParameter(defaultValue = @Value(stringValue = { "default", "another-default" })) public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Defaults.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Defaults.java index 06e388673f3b..9be84a44c1af 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Defaults.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Defaults.java @@ -15,34 +15,34 @@ public final class Defaults { - @ConfigurationParameter(defaultValue = @Value(shorts = 1)) + @ConfigurationParameter(defaultValue = @Value(shortValue = 1)) public static final String SHORTS_PROPERTY_NAME = "org.example.shorts"; - @ConfigurationParameter(defaultValue = @Value(bytes = 0x2A)) + @ConfigurationParameter(defaultValue = @Value(byteValue = 0x2A)) public static final String BYTES_PROPERTY_NAME = "org.example.bytes"; - @ConfigurationParameter(defaultValue = @Value(ints = 42)) + @ConfigurationParameter(defaultValue = @Value(intValue = 42)) public static final String INTS_PROPERTY_NAME = "org.example.ints"; - @ConfigurationParameter(defaultValue = @Value(longs = 42)) + @ConfigurationParameter(defaultValue = @Value(longValue = 42)) public static final String LONGS_PROPERTY_NAME = "org.example.longs"; - @ConfigurationParameter(defaultValue = @Value(floats = 42.0f)) + @ConfigurationParameter(defaultValue = @Value(floatValue = 42.0f)) public static final String FLOATS_PROPERTY_NAME = "org.example.floats"; - @ConfigurationParameter(defaultValue = @Value(doubles = 42.0)) + @ConfigurationParameter(defaultValue = @Value(doubleValue = 42.0)) public static final String DOUBLES_PROPERTY_NAME = "org.example.doubles"; - @ConfigurationParameter(defaultValue = @Value(chars = '4')) + @ConfigurationParameter(defaultValue = @Value(charValue = '4')) public static final String CHARS_PROPERTY_NAME = "org.example.chars"; - @ConfigurationParameter(defaultValue = @Value(booleans = true)) + @ConfigurationParameter(defaultValue = @Value(booleanValue = true)) public static final String BOOLEANS_PROPERTY_NAME = "org.example.booleans"; - @ConfigurationParameter(defaultValue = @Value(strings = "default")) + @ConfigurationParameter(defaultValue = @Value(stringValue = "default")) public static final String STRING_PROPERTY_NAME = "org.example.strings"; - @ConfigurationParameter(defaultValue = @Value(classes = Example.class)) + @ConfigurationParameter(defaultValue = @Value(classValue = Example.class)) public static final String CLASSES_PROPERTY_NAME = "org.example.classes"; private record Example() { From 2493c679cae404a7f16ca8402130d9bbd5702d5c Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 10 Aug 2026 17:15:32 +0200 Subject: [PATCH 051/117] Render defaults as json values where applicable --- .../processor/AnnotationMirrorUtil.java | 8 +- .../processor/ConfigurationMetaData.java | 2 +- .../ConfigurationParameterHandler.java | 6 +- .../processor/JsonConverter.java | 36 ++++- ...ationMetadataAnnotationProcessorTests.java | 129 +++++++++--------- 5 files changed, 106 insertions(+), 75 deletions(-) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java index 388994ab27a5..249b0502edd2 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java @@ -65,19 +65,17 @@ private static Optional findElementBy(AnnotationMirror annotation, Strin .findFirst(); } - static Map> getStringValuesMap(AnnotationMirror defaults) { + static Map> getValuesMap(AnnotationMirror defaults) { return defaults.getElementValues().entrySet() // .stream() // - .collect(toMap(AnnotationMirrorUtil::getSimpleName, AnnotationMirrorUtil::getStringValues)); + .collect(toMap(AnnotationMirrorUtil::getSimpleName, AnnotationMirrorUtil::getValues)); } - private static List getStringValues(Entry entry) { + private static List getValues(Entry entry) { if (entry.getValue().getValue() instanceof List values) { return values.stream().filter(AnnotationValue.class::isInstance) // .map(AnnotationValue.class::cast) // .map(AnnotationValue::getValue) // - .map(Object::toString) // - .filter(s -> !s.isEmpty()) // .toList(); } return Collections.emptyList(); diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java index 0228ccd5a3e0..aad773dbde34 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java @@ -35,7 +35,7 @@ record Property( // @Nullable String type, // @Nullable String description, // @Nullable String sourceType, // - @Nullable String defaultValue, // + @Nullable Object defaultValue, // @Nullable Deprecation deprecation // ) { diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java index 8b8ad43a4560..156feeedeff8 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java @@ -14,7 +14,7 @@ import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getAnnotationMirror; import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getAnnotationValue; import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getStringValue; -import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getStringValuesMap; +import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getValuesMap; import java.util.Map; import java.util.regex.Pattern; @@ -146,7 +146,7 @@ private TypeElement getEnclosingTypeElement(VariableElement element) { return null; } - var defaultValues = getStringValuesMap(defaults); + var defaultValues = getValuesMap(defaults); Preconditions.condition(defaultValues.size() == 1, () -> { var enclosingTypeElement = getEnclosingTypeElement(element); return "Field [%s.%s] must have exactly one default value" // @@ -172,7 +172,7 @@ private TypeElement getEnclosingTypeElement(VariableElement element) { return new Default(defaultType, defaultValue); } - private record Default(String defaultType, String value) { + private record Default(String defaultType, Object value) { } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java index 24656f69b1c1..766c1781a793 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java @@ -22,6 +22,7 @@ import jakarta.json.JsonArray; import jakarta.json.JsonBuilderFactory; import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; import jakarta.json.JsonValue; final class JsonConverter { @@ -60,7 +61,7 @@ private JsonObject toJsonObject(Property property) { var defaultValue = property.defaultValue(); if (defaultValue != null) { - builder.add("defaultValue", defaultValue); + addObjectValue(builder, "defaultValue", defaultValue); } var deprecation = property.deprecation(); @@ -71,6 +72,39 @@ private JsonObject toJsonObject(Property property) { return builder.build(); } + private void addObjectValue(JsonObjectBuilder builder, String name, Object defaultValue) { + if (defaultValue instanceof Short v) { + builder.add(name, v); + } + else if (defaultValue instanceof Byte v) { + builder.add(name, "%02X".formatted(v)); + } + else if (defaultValue instanceof Integer v) { + builder.add(name, v); + } + else if (defaultValue instanceof Long v) { + builder.add(name, v); + } + else if (defaultValue instanceof Float v) { + builder.add(name, v); + } + else if (defaultValue instanceof Double v) { + builder.add(name, v); + } + else if (defaultValue instanceof Character v) { + builder.add(name, String.valueOf(v)); + } + else if (defaultValue instanceof Boolean v) { + builder.add(name, v); + } + else if (defaultValue instanceof String v) { + builder.add(name, v); + } + else { + builder.add(name, defaultValue.toString()); + } + } + private JsonObject toJsonObject(Deprecation deprecation) { var builder = factory.createObjectBuilder(); diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index 09ea2e266e52..4e64c9420f11 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -213,71 +213,70 @@ void stringType() { void defaults() { compiler.compile(Defaults.class); assertMetaDataIsEqualTo(""" - { - "properties": [ - { - "name": "org.example.shorts", - "type": "java.lang.Short", - "sourceType": "org.junit.platform.configuration.testcases.Defaults", - "defaultValue": "1" - }, - { - "name": "org.example.bytes", - "type": "java.lang.Byte", - "sourceType": "org.junit.platform.configuration.testcases.Defaults", - "defaultValue": "42" - }, - { - "name": "org.example.ints", - "type": "java.lang.Integer", - "sourceType": "org.junit.platform.configuration.testcases.Defaults", - "defaultValue": "42" - }, - { - "name": "org.example.longs", - "type": "java.lang.Long", - "sourceType": "org.junit.platform.configuration.testcases.Defaults", - "defaultValue": "42" - }, - { - "name": "org.example.floats", - "type": "java.lang.Float", - "sourceType": "org.junit.platform.configuration.testcases.Defaults", - "defaultValue": "42.0" - }, - { - "name": "org.example.doubles", - "type": "java.lang.Double", - "sourceType": "org.junit.platform.configuration.testcases.Defaults", - "defaultValue": "42.0" - }, - { - "name": "org.example.chars", - "type": "java.lang.Character", - "sourceType": "org.junit.platform.configuration.testcases.Defaults", - "defaultValue": "4" - }, - { - "name": "org.example.booleans", - "type": "java.lang.Boolean", - "sourceType": "org.junit.platform.configuration.testcases.Defaults", - "defaultValue": "true" - }, - { - "name": "org.example.strings", - "type": "java.lang.String", - "sourceType": "org.junit.platform.configuration.testcases.Defaults", - "defaultValue": "default" - }, - { - "name": "org.example.classes", - "type": "java.lang.Class", - "sourceType": "org.junit.platform.configuration.testcases.Defaults", - "defaultValue": "org.junit.platform.configuration.testcases.Defaults.Example" - } - ] - } - """); + { + "properties": [ + { + "name": "org.example.shorts", + "type": "java.lang.Short", + "sourceType": "org.junit.platform.configuration.testcases.Defaults", + "defaultValue": 1 + }, + { + "name": "org.example.bytes", + "type": "java.lang.Byte", + "sourceType": "org.junit.platform.configuration.testcases.Defaults", + "defaultValue": "2A" + }, + { + "name": "org.example.ints", + "type": "java.lang.Integer", + "sourceType": "org.junit.platform.configuration.testcases.Defaults", + "defaultValue": 42 + }, + { + "name": "org.example.longs", + "type": "java.lang.Long", + "sourceType": "org.junit.platform.configuration.testcases.Defaults", + "defaultValue": 42 + }, + { + "name": "org.example.floats", + "type": "java.lang.Float", + "sourceType": "org.junit.platform.configuration.testcases.Defaults", + "defaultValue": 42.0 + }, + { + "name": "org.example.doubles", + "type": "java.lang.Double", + "sourceType": "org.junit.platform.configuration.testcases.Defaults", + "defaultValue": 42.0 + }, + { + "name": "org.example.chars", + "type": "java.lang.Character", + "sourceType": "org.junit.platform.configuration.testcases.Defaults", + "defaultValue": "4" + }, + { + "name": "org.example.booleans", + "type": "java.lang.Boolean", + "sourceType": "org.junit.platform.configuration.testcases.Defaults", + "defaultValue": true + }, + { + "name": "org.example.strings", + "type": "java.lang.String", + "sourceType": "org.junit.platform.configuration.testcases.Defaults", + "defaultValue": "default" + }, + { + "name": "org.example.classes", + "type": "java.lang.Class", + "sourceType": "org.junit.platform.configuration.testcases.Defaults", + "defaultValue": "org.junit.platform.configuration.testcases.Defaults.Example" + } + ] + }"""); } @Test From 5e6d62ece0673048b3222562fba334f984a2e071 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 10 Aug 2026 18:14:53 +0200 Subject: [PATCH 052/117] Annotate Jupiter Constants --- .../java/org/junit/jupiter/api/Constants.java | 72 ++++++++++++++++++- ...ationMetadataAnnotationProcessorTests.java | 17 +++++ .../testcases/TypeEnumWithStringDefault.java | 24 +++++++ 3 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnumWithStringDefault.java diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java index 5b8cc4564ac8..2a8d1a4c68f1 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java @@ -13,13 +13,21 @@ import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; +import static org.junit.platform.configuration.api.ConfigurationParameter.Value; + +import java.math.BigDecimal; import org.apiguardian.api.API; +import org.junit.jupiter.api.DisplayNameGenerator.Standard; +import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.PreInterruptCallback; import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.api.io.TempDirDeletionStrategy; +import org.junit.jupiter.api.io.TempDirFactory; import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.platform.configuration.api.ConfigurationParameter; /** @@ -65,7 +73,8 @@ public final class Constants { * *

Note: A class that matches both an inclusion and exclusion pattern will be excluded. */ - @ConfigurationParameter + // TODO: ClassNamePatternFilterUtils.ALL_PATTERN isn't part of the public API + @ConfigurationParameter(defaultValue = @Value(stringValue = "*")) public static final String EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.include"; /** @@ -101,6 +110,7 @@ public final class Constants { * *

Note: A class that matches both an inclusion and exclusion pattern will be excluded. */ + @ConfigurationParameter(type = String.class) public static final String EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.exclude"; /** @@ -109,6 +119,8 @@ public final class Constants { * *

The default behavior is not to perform auto-detection. */ + // TODO: Handle @link references. + @ConfigurationParameter(defaultValue = @Value(booleanValue = false)) public static final String EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.enabled"; /** @@ -117,6 +129,7 @@ public final class Constants { *

By default, auto-closing is enabled. * */ + @ConfigurationParameter(defaultValue = @Value(booleanValue = true)) public static final String CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME = "junit.jupiter.extensions.store.close.autocloseable.enabled"; /** @@ -153,6 +166,7 @@ public final class Constants { * @see #DEACTIVATE_ALL_CONDITIONS_PATTERN * @see org.junit.jupiter.api.extension.ExecutionCondition */ + @ConfigurationParameter(type = String.class) public static final String DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME = "junit.jupiter.conditions.deactivate"; /** @@ -168,6 +182,7 @@ public final class Constants { * * @see DisplayNameGenerator#DEFAULT_GENERATOR_PROPERTY_NAME */ + @ConfigurationParameter(type = DisplayNameGenerator.class, defaultValue = @Value(classValue = Standard.class)) public static final String DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME = DisplayNameGenerator.DEFAULT_GENERATOR_PROPERTY_NAME; /** @@ -176,14 +191,17 @@ public final class Constants { * *

This behavior is disabled by default. */ + @ConfigurationParameter(defaultValue = @Value(booleanValue = false)) public static final String EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME = PreInterruptCallback.THREAD_DUMP_ENABLED_PROPERTY_NAME; /** * Property name used to set the default test instance lifecycle mode: {@value} * - * @see TestInstance.Lifecycle#DEFAULT_LIFECYCLE_PROPERTY_NAME + * @see Lifecycle#DEFAULT_LIFECYCLE_PROPERTY_NAME */ - public static final String DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME = TestInstance.Lifecycle.DEFAULT_LIFECYCLE_PROPERTY_NAME; + // TODO: Reference enum values as strings? + @ConfigurationParameter(type = Lifecycle.class, defaultValue = @Value(stringValue = "PER_METHOD")) + public static final String DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME = Lifecycle.DEFAULT_LIFECYCLE_PROPERTY_NAME; /** * Property name used to enable parallel test execution: {@value} @@ -191,6 +209,7 @@ public final class Constants { *

By default, tests are executed sequentially in a single thread. * */ + @ConfigurationParameter(defaultValue = @Value(booleanValue = false)) public static final String PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME = "junit.jupiter.execution.parallel.enabled"; /** @@ -198,6 +217,8 @@ public final class Constants { * * @see Execution#DEFAULT_EXECUTION_MODE_PROPERTY_NAME */ + // TODO: Reference enum values as strings? + @ConfigurationParameter(type = ExecutionMode.class, defaultValue = @Value(stringValue = "SAME_THREAD")) public static final String DEFAULT_EXECUTION_MODE_PROPERTY_NAME = Execution.DEFAULT_EXECUTION_MODE_PROPERTY_NAME; /** @@ -206,6 +227,7 @@ public final class Constants { * * @see Execution#DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME */ + @ConfigurationParameter(type = ExecutionMode.class) public static final String DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME = Execution.DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME; /** @@ -223,6 +245,9 @@ public final class Constants { * ignoring case. * */ + // TODO: ParallelExecutorServiceType is not part of the public API + // TODO: Reference enum values as strings? + @ConfigurationParameter(/* type = ParallelExecutorServiceType.class,*/ defaultValue = @Value(stringValue = "FORK_JOIN_POOL")) public static final String PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "executor-service"; @@ -234,6 +259,9 @@ public final class Constants { * {@code custom}. * */ + // TODO: DefaultParallelExecutionConfigurationStrategy is not part of the public API + // TODO: Reference enum values as strings? + @ConfigurationParameter(/* type = DefaultParallelExecutionConfigurationStrategy.class,*/ defaultValue = @Value(stringValue = "DYNAMIC")) public static final String PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "strategy"; /** @@ -243,6 +271,7 @@ public final class Constants { *

No default value; must be a positive integer. * */ + @ConfigurationParameter(type = Integer.class) public static final String PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "fixed.parallelism"; @@ -255,6 +284,7 @@ public final class Constants { * {@code 256 + fixed.parallelism}. * */ + @ConfigurationParameter(type = Integer.class) public static final String PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "fixed.max-pool-size"; @@ -269,6 +299,7 @@ public final class Constants { *

Value must either {@code true} or {@code false}; defaults to {@code true}. * */ + @ConfigurationParameter(defaultValue = @Value(booleanValue = true)) public static final String PARALLEL_CONFIG_FIXED_SATURATE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "fixed.saturate"; /** @@ -279,6 +310,7 @@ public final class Constants { *

Value must be a positive decimal number; defaults to {@code 1}. * */ + @ConfigurationParameter(type = BigDecimal.class, defaultValue = @Value(doubleValue = 1.0)) public static final String PARALLEL_CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "dynamic.factor"; /** @@ -287,6 +319,8 @@ public final class Constants { * {@value} * */ + // TODO: ParallelExecutionConfigurationStrategy is not part of the public API + @ConfigurationParameter /*( type = ParallelExecutionConfigurationStrategy.class)*/ public static final String PARALLEL_CONFIG_CUSTOM_CLASS_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "custom.class"; /** @@ -295,6 +329,8 @@ public final class Constants { * * @see Timeout#DEFAULT_TIMEOUT_PROPERTY_NAME */ + // TODO: TimeoutDuration is not part of the public API + @ConfigurationParameter /*(type = TimeoutDuration.class)*/ public static final String DEFAULT_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TIMEOUT_PROPERTY_NAME; /** @@ -302,6 +338,8 @@ public final class Constants { * * @see Timeout#DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME */ + // TODO: TimeoutDuration is not part of the public API + @ConfigurationParameter /*(type = TimeoutDuration.class)*/ public static final String DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -310,6 +348,8 @@ public final class Constants { * * @see Timeout#DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME */ + // TODO: TimeoutDuration is not part of the public API + @ConfigurationParameter /*(type = TimeoutDuration.class)*/ public static final String DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -318,6 +358,8 @@ public final class Constants { * * @see Timeout#DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME */ + // TODO: TimeoutDuration is not part of the public API + @ConfigurationParameter /*(type = TimeoutDuration.class)*/ public static final String DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -326,6 +368,8 @@ public final class Constants { * * @see Timeout#DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME */ + // TODO: TimeoutDuration is not part of the public API + @ConfigurationParameter /*(type = TimeoutDuration.class)*/ public static final String DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -333,6 +377,8 @@ public final class Constants { * * @see Timeout#DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME */ + // TODO: TimeoutDuration is not part of the public API + @ConfigurationParameter /*(type = TimeoutDuration.class)*/ public static final String DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -341,6 +387,8 @@ public final class Constants { * * @see Timeout#DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME */ + // TODO: TimeoutDuration is not part of the public API + @ConfigurationParameter /*(type = TimeoutDuration.class)*/ public static final String DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -349,6 +397,8 @@ public final class Constants { * * @see Timeout#DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME */ + // TODO: TimeoutDuration is not part of the public API + @ConfigurationParameter /*(type = TimeoutDuration.class)*/ public static final String DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -357,6 +407,8 @@ public final class Constants { * * @see Timeout#DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME */ + // TODO: TimeoutDuration is not part of the public API + @ConfigurationParameter /*(type = TimeoutDuration.class)*/ public static final String DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -365,6 +417,8 @@ public final class Constants { * * @see Timeout#DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME */ + // TODO: TimeoutDuration is not part of the public API + @ConfigurationParameter /*(type = TimeoutDuration.class)*/ public static final String DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -372,6 +426,8 @@ public final class Constants { * * @see Timeout#TIMEOUT_MODE_PROPERTY_NAME */ + // TODO: TimeoutDuration is not part of the public API + @ConfigurationParameter /*(type = TimeoutDuration.class)*/ public static final String TIMEOUT_MODE_PROPERTY_NAME = Timeout.TIMEOUT_MODE_PROPERTY_NAME; /** @@ -379,6 +435,7 @@ public final class Constants { * * @see MethodOrderer#DEFAULT_ORDER_PROPERTY_NAME */ + @ConfigurationParameter(type = MethodOrderer.class) public static final String DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME = MethodOrderer.DEFAULT_ORDER_PROPERTY_NAME; /** @@ -386,6 +443,7 @@ public final class Constants { * * @see ClassOrderer#DEFAULT_ORDER_PROPERTY_NAME */ + @ConfigurationParameter(type = ClassOrderer.class) public static final String DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME = ClassOrderer.DEFAULT_ORDER_PROPERTY_NAME; /** @@ -394,6 +452,8 @@ public final class Constants { * @see Timeout * @see Timeout.ThreadMode */ + // TODO: Reference enum values as strings? + @ConfigurationParameter(type = Timeout.ThreadMode.class, defaultValue = @Value(stringValue = "SAME_THREAD")) public static final String DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME = Timeout.DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME; /** @@ -402,6 +462,7 @@ public final class Constants { * * @see TempDir#DEFAULT_FACTORY_PROPERTY_NAME */ + @ConfigurationParameter(type = TempDirFactory.class, defaultValue = @Value(classValue = TempDirFactory.Standard.class)) public static final String DEFAULT_TEMP_DIR_FACTORY_PROPERTY_NAME = TempDir.DEFAULT_FACTORY_PROPERTY_NAME; /** @@ -411,6 +472,8 @@ public final class Constants { * * @see TempDir#DEFAULT_CLEANUP_MODE_PROPERTY_NAME */ + // TODO: Reference enum values as strings? + @ConfigurationParameter(type = CleanupMode.class, defaultValue = @Value(stringValue = "ALWAYS")) public static final String DEFAULT_TEMP_DIR_CLEANUP_MODE_PROPERTY_NAME = TempDir.DEFAULT_CLEANUP_MODE_PROPERTY_NAME; /** @@ -421,6 +484,7 @@ public final class Constants { * @see TempDir#DEFAULT_DELETION_STRATEGY_PROPERTY_NAME */ @API(status = EXPERIMENTAL, since = "6.1") + @ConfigurationParameter(type = TempDirDeletionStrategy.class, defaultValue = @Value(classValue = TempDirDeletionStrategy.Standard.class)) public static final String DEFAULT_TEMP_DIR_DELETION_STRATEGY_PROPERTY_NAME = TempDir.DEFAULT_DELETION_STRATEGY_PROPERTY_NAME; /** @@ -429,6 +493,8 @@ public final class Constants { * * @see org.junit.jupiter.api.extension.TestInstantiationAwareExtension */ + // TODO: Reference enum values as strings? + @ConfigurationParameter(type = ExtensionContextScope.class, defaultValue = @Value(stringValue = "DEFAULT")) public static final String DEFAULT_TEST_CLASS_INSTANCE_CONSTRUCTION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME = ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME; private Constants() { diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index 4e64c9420f11..9028d152e4b1 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -39,6 +39,7 @@ import org.junit.platform.configuration.testcases.NonFinal; import org.junit.platform.configuration.testcases.NonStatic; import org.junit.platform.configuration.testcases.NonString; +import org.junit.platform.configuration.testcases.TypeEnumWithStringDefault; import org.junit.platform.configuration.testcases.TypeString; import org.junit.platform.configuration.testcases.Without; @@ -209,6 +210,22 @@ void stringType() { }"""); } + @Test + void enumTypeWithStringDefault() { + compiler.compile(TypeEnumWithStringDefault.class); + assertMetaDataIsEqualTo(""" + { + "properties": [ + { + "name": "org.example.property", + "type": "org.junit.platform.configuration.testcases.TypeEnumWithStringDefault.ExampleEnum", + "sourceType": "org.junit.platform.configuration.testcases.TypeEnumWithStringDefault", + "defaultValue": "A" + } + ] + }"""); + } + @Test void defaults() { compiler.compile(Defaults.class); diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnumWithStringDefault.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnumWithStringDefault.java new file mode 100644 index 000000000000..d6387c89a40c --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnumWithStringDefault.java @@ -0,0 +1,24 @@ +/* + * Copyright 2026 the original author or 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.configuration.testcases; + +import org.junit.platform.configuration.api.ConfigurationParameter; +import org.junit.platform.configuration.api.ConfigurationParameter.Value; + +public final class TypeEnumWithStringDefault { + + @ConfigurationParameter(type = ExampleEnum.class, defaultValue = @Value(stringValue = "A")) + public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; + + enum ExampleEnum { + A, B + } +} From a235f3e80ad7103354f7773aee98f145e49f69f1 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 10 Aug 2026 20:26:10 +0200 Subject: [PATCH 053/117] Claim ConfigurationParameter annotations --- .../ConfigurationMetadataAnnotationProcessor.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index f21aa8b073a8..a1dca9204297 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -76,14 +76,17 @@ public SourceVersion getSupportedSourceVersion() { } @Override + @SuppressWarnings("DoNotClaimAnnotations") public boolean process(Set annotations, RoundEnvironment roundEnv) { configurationParameterHandler().process(roundEnv); if (roundEnv.processingOver()) { writeMetaData(); } - // See: https://errorprone.info/bugpattern/DoNotClaimAnnotations - return false; + + // Very simple check. Works because this processor only processes + // ConfigurationParameter annotations. + return !annotations.isEmpty(); } private void writeMetaData() { From 6918e324e53c6da81bafd89d5c0cb530be56a8b2 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 11 Aug 2026 02:21:53 +0200 Subject: [PATCH 054/117] Extract ProcessingContext --- .../processor/AnnotationMirrorUtil.java | 12 +-- .../ConfigurationParameterHandler.java | 93 +++++++++---------- 2 files changed, 48 insertions(+), 57 deletions(-) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java index 249b0502edd2..3c23f4550f3c 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java @@ -32,14 +32,12 @@ private AnnotationMirrorUtil() { /* no-op */ } - static @Nullable AnnotationMirror getAnnotationMirror(Element element, Class annotationType) { + static AnnotationMirror getAnnotationMirror(Element element, Class annotationType) { var annotationTypeName = annotationType.getName(); - for (AnnotationMirror annotation : element.getAnnotationMirrors()) { - if (annotationTypeName.equals(annotation.getAnnotationType().toString())) { - return annotation; - } - } - return null; + return element.getAnnotationMirrors().stream() // + .filter(annotation -> annotationTypeName.equals(annotation.getAnnotationType().toString())) // + .findFirst() // + .orElseThrow(); } static @Nullable String getStringValue(AnnotationMirror annotation, String elementName) { diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java index 156feeedeff8..a614d254e36d 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java @@ -20,6 +20,7 @@ import java.util.regex.Pattern; import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; @@ -57,25 +58,35 @@ void process(RoundEnvironment roundEnv) { roundEnv.getElementsAnnotatedWith(ConfigurationParameter.class).stream() // .filter(VariableElement.class::isInstance) // .map(VariableElement.class::cast) // + .map(element -> new ProcessingContext( // + element, // + getEnclosingTypeElement(element), // + getAnnotationMirror(element, ConfigurationParameter.class))) // .map(this::createProperty) // .forEach(metaData::addProperty); } - private Property createProperty(VariableElement element) { - var name = processName(element); - var description = processDescription(element); - var sourceType = processSourceType(element); - var defaults = processDefaults(element); - var deprecation = processDeprecation(element); + private record ProcessingContext(VariableElement element, TypeElement enclosingType, + AnnotationMirror annotationMirror) { + + } + + private Property createProperty(ProcessingContext context) { + var name = processName(context); + var description = processDescription(context); + var sourceType = processSourceType(context); + var defaults = processDefaults(context); + var deprecation = processDeprecation(context); var defaultType = defaults == null ? null : defaults.defaultType(); var defaultValue = defaults == null ? null : defaults.value(); - var type = processType(element, defaultType); + var type = processType(context, defaultType); return new Property(name, type, description, sourceType, defaultValue, deprecation); } - private String processName(VariableElement element) { + private String processName(ProcessingContext context) { // TODO: Report preconditions problems with processingEnvironment().getMessager().printMessage() instead. - var enclosingTypeElement = getEnclosingTypeElement(element); + var enclosingTypeElement = context.enclosingType(); + var element = context.element(); Preconditions.condition(isStatic(element), // () -> "Field [%s.%s] must be declared static" // .formatted(enclosingTypeElement.getQualifiedName(), element.getSimpleName())); @@ -89,22 +100,17 @@ private String processName(VariableElement element) { return (String) constantValue; } - private @Nullable String processType(VariableElement element, @Nullable String defaultType) { - var parameter = getAnnotationMirror(element, ConfigurationParameter.class); - if (parameter == null) { - return defaultType; - } - + private @Nullable String processType(ProcessingContext context, @Nullable String defaultType) { + var parameter = context.annotationMirror(); var type = getStringValue(parameter, "type"); if (type == null || type.equals(Void.class.getName())) { return defaultType; } - return type; } - private @Nullable String processDescription(VariableElement element) { - var docComment = elementUtils.getDocComment(element); + private @Nullable String processDescription(ProcessingContext context) { + var docComment = elementUtils.getDocComment(context.element()); return docComment == null ? null : cleanupDocComment(docComment); } @@ -122,9 +128,8 @@ private static String cleanupDocComment(String docComment) { .trim(); } - private String processSourceType(VariableElement element) { - var enclosingTypeElement = getEnclosingTypeElement(element); - return enclosingTypeElement.getQualifiedName().toString(); + private String processSourceType(ProcessingContext context) { + return context.enclosingType().getQualifiedName().toString(); } private TypeElement getEnclosingTypeElement(VariableElement element) { @@ -135,37 +140,29 @@ private TypeElement getEnclosingTypeElement(VariableElement element) { return (TypeElement) enclosingElement; } - private @Nullable Default processDefaults(VariableElement element) { - var parameter = getAnnotationMirror(element, ConfigurationParameter.class); - if (parameter == null) { - return null; - } - + private @Nullable Default processDefaults(ProcessingContext context) { + var element = context.element(); + var parameter = context.annotationMirror(); var defaults = getAnnotationValue(parameter, "defaultValue"); if (defaults == null) { return null; } var defaultValues = getValuesMap(defaults); - Preconditions.condition(defaultValues.size() == 1, () -> { - var enclosingTypeElement = getEnclosingTypeElement(element); - return "Field [%s.%s] must have exactly one default value" // - .formatted(enclosingTypeElement, element.getSimpleName()); - }); - - Preconditions.condition(defaultValues.size() == 1, () -> { - var enclosingTypeElement = getEnclosingTypeElement(element); - return "Field [%s.%s] must have exactly one default value" // - .formatted(enclosingTypeElement, element.getSimpleName()); - }); + Preconditions.condition(defaultValues.size() == 1, // + () -> "Field [%s.%s] must have exactly one default value" // + .formatted(context.enclosingType(), element.getSimpleName())); + + Preconditions.condition(defaultValues.size() == 1, // + () -> "Field [%s.%s] must have exactly one default value" // + .formatted(context.enclosingType(), element.getSimpleName())); var entry = defaultValues.entrySet().iterator().next(); var value = entry.getValue(); - Preconditions.condition(value.size() == 1, () -> { - var enclosingTypeElement = getEnclosingTypeElement(element); - return "Field [%s.%s] must have exactly one default value" // - .formatted(enclosingTypeElement, element.getSimpleName()); - }); + Preconditions.condition(value.size() == 1, // + () -> "Field [%s.%s] must have exactly one default value" // + .formatted(context.enclosingType(), element.getSimpleName())); + var defaultValue = value.get(0); var defaultName = entry.getKey(); var defaultType = requireNonNull(NAME_TO_TYPE_NAME.get(defaultName)); @@ -176,12 +173,8 @@ private record Default(String defaultType, Object value) { } - private ConfigurationMetaData.@Nullable Deprecation processDeprecation(VariableElement element) { - var parameter = getAnnotationMirror(element, ConfigurationParameter.class); - if (parameter == null) { - return null; - } - var deprecation = getAnnotationValue(parameter, "deprecation"); + private ConfigurationMetaData.@Nullable Deprecation processDeprecation(ProcessingContext context) { + var deprecation = getAnnotationValue(context.annotationMirror(), "deprecation"); if (deprecation != null) { var reason = getStringValue(deprecation, "reason"); var replacement = getStringValue(deprecation, "replacement"); @@ -191,7 +184,7 @@ private record Default(String defaultType, Object value) { } } // Fallback, look for @Deprecated - if (getAnnotationMirror(element, Deprecated.class) != null) { + if (getAnnotationMirror(context.element(), Deprecated.class) != null) { return new ConfigurationMetaData.Deprecation(null, null, null, null); } return null; From 93c0e3a7a4caec5acab4d5dcb7d3850a7c1c68a0 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 11 Aug 2026 03:22:09 +0200 Subject: [PATCH 055/117] Extract ConfigurationParameterAnnotatedField --- .../processor/AnnotationMirrorUtil.java | 44 +++--- .../ConfigurationParameterAnnotatedField.java | 93 +++++++++++++ .../ConfigurationParameterHandler.java | 130 +++++++----------- 3 files changed, 161 insertions(+), 106 deletions(-) create mode 100644 junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java index 3c23f4550f3c..95fa1c13e324 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java @@ -17,7 +17,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Optional; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; @@ -32,46 +31,42 @@ private AnnotationMirrorUtil() { /* no-op */ } - static AnnotationMirror getAnnotationMirror(Element element, Class annotationType) { - var annotationTypeName = annotationType.getName(); + static @Nullable AnnotationMirror getAnnotationMirror(Element element, Class annotationType) { + var elementName = annotationType.getName(); return element.getAnnotationMirrors().stream() // - .filter(annotation -> annotationTypeName.equals(annotation.getAnnotationType().toString())) // + .filter(annotation -> elementName.equals(annotation.getAnnotationType().toString())) // .findFirst() // - .orElseThrow(); - } - - static @Nullable String getStringValue(AnnotationMirror annotation, String elementName) { - return findElementBy(annotation, elementName) // - .map(Object::toString) // - .filter(s -> !s.isEmpty()) // .orElse(null); } - static @Nullable AnnotationMirror getAnnotationValue(AnnotationMirror annotation, String elementName) { - return findElementBy(annotation, elementName) // + static @Nullable AnnotationMirror getAnnotationMirror(AnnotationMirror annotation, String elementName) { + return annotation.getElementValues().entrySet() // + .stream() // + .filter(element -> elementName.equals(element.getKey().getSimpleName().toString())) // + .map(Entry::getValue) // + .map(AnnotationValue::getValue) // + .findFirst() // .filter(AnnotationMirror.class::isInstance) // .map(AnnotationMirror.class::cast) // .orElse(null); } - private static Optional findElementBy(AnnotationMirror annotation, String elementName) { + static Map> getValuesMap(AnnotationMirror annotation) { return annotation.getElementValues().entrySet() // .stream() // - .filter((element) -> element.getKey().getSimpleName().toString().equals(elementName)) // - .map(Entry::getValue) // - .map(AnnotationValue::getValue) // - .findFirst(); + .collect(toMap(AnnotationMirrorUtil::getSimpleName, AnnotationMirrorUtil::getValues)); } - static Map> getValuesMap(AnnotationMirror defaults) { - return defaults.getElementValues().entrySet() // + static Map getStringValuesMap(AnnotationMirror annotation) { + return annotation.getElementValues().entrySet() // .stream() // - .collect(toMap(AnnotationMirrorUtil::getSimpleName, AnnotationMirrorUtil::getValues)); + .collect(toMap(AnnotationMirrorUtil::getSimpleName, AnnotationMirrorUtil::getStringValue)); } private static List getValues(Entry entry) { if (entry.getValue().getValue() instanceof List values) { - return values.stream().filter(AnnotationValue.class::isInstance) // + return values.stream() // + .filter(AnnotationValue.class::isInstance) // .map(AnnotationValue.class::cast) // .map(AnnotationValue::getValue) // .toList(); @@ -79,6 +74,11 @@ private static List getValues(Entry entry) { + Object value = entry.getValue().getValue(); + return value.toString(); + } + private static String getSimpleName(Entry entry) { return entry.getKey().getSimpleName().toString(); } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java new file mode 100644 index 000000000000..a939eaaee399 --- /dev/null +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java @@ -0,0 +1,93 @@ +/* + * Copyright 2026 the original author or 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.configuration.processor; + +import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getAnnotationMirror; +import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getStringValuesMap; +import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getValuesMap; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.util.Elements; + +import org.jspecify.annotations.Nullable; + +final class ConfigurationParameterAnnotatedField { + private final VariableElement field; + private final TypeElement enclosingType; + private final AnnotationMirror configurationParameter; + private final Elements elementUtils; + + ConfigurationParameterAnnotatedField(VariableElement field, Elements elementUtils, TypeElement enclosingType, + AnnotationMirror configurationParameter) { + this.field = field; + this.elementUtils = elementUtils; + this.enclosingType = enclosingType; + this.configurationParameter = configurationParameter; + } + + Map deprecationValues() { + var deprecation = getAnnotationMirror(configurationParameter, "deprecation"); + if (deprecation == null) { + return Collections.emptyMap(); + } + return getStringValuesMap(deprecation); + } + + Map> defaultValues() { + var defaultValue = getAnnotationMirror(configurationParameter, "defaultValue"); + if (defaultValue == null) { + return Collections.emptyMap(); + } + return getValuesMap(defaultValue); + } + + @Nullable + String typeValue() { + return getStringValuesMap(configurationParameter).get("type"); + } + + @Nullable + Object constantValue() { + return field.getConstantValue(); + } + + String name() { + return "%s.%s".formatted(enclosingType.getQualifiedName(), field.getSimpleName()); + } + + boolean isStatic() { + return field.getModifiers().contains(Modifier.STATIC); + } + + boolean isFinal() { + return field.getModifiers().contains(Modifier.FINAL); + } + + boolean isDeprecated() { + return getAnnotationMirror(field, Deprecated.class) != null; + } + + @Nullable + String docComment() { + return elementUtils.getDocComment(field); + } + + String enclosingTypeName() { + return enclosingType.getQualifiedName().toString(); + } +} diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java index a614d254e36d..af1cc58ef85b 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java @@ -12,16 +12,11 @@ import static java.util.Objects.requireNonNull; import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getAnnotationMirror; -import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getAnnotationValue; -import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getStringValue; -import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getValuesMap; import java.util.Map; import java.util.regex.Pattern; import javax.annotation.processing.RoundEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.util.Elements; @@ -29,6 +24,7 @@ import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.configuration.api.ConfigurationParameter; +import org.junit.platform.configuration.processor.ConfigurationMetaData.Deprecation; import org.junit.platform.configuration.processor.ConfigurationMetaData.Property; final class ConfigurationParameterHandler { @@ -58,63 +54,55 @@ void process(RoundEnvironment roundEnv) { roundEnv.getElementsAnnotatedWith(ConfigurationParameter.class).stream() // .filter(VariableElement.class::isInstance) // .map(VariableElement.class::cast) // - .map(element -> new ProcessingContext( // - element, // - getEnclosingTypeElement(element), // - getAnnotationMirror(element, ConfigurationParameter.class))) // + .map(this::createConfigurationParameterAnnotatedField) // .map(this::createProperty) // .forEach(metaData::addProperty); } - private record ProcessingContext(VariableElement element, TypeElement enclosingType, - AnnotationMirror annotationMirror) { - + private ConfigurationParameterAnnotatedField createConfigurationParameterAnnotatedField(VariableElement element) { + var enclosingElement = element.getEnclosingElement(); + Preconditions.condition(enclosingElement instanceof TypeElement, // + () -> "[%s] did not have an enclosing typeValue element".formatted(element.getSimpleName().toString())); + var enclosingTypeElement = (TypeElement) enclosingElement; + var configurationParameter = requireNonNull(getAnnotationMirror(element, ConfigurationParameter.class)); + return new ConfigurationParameterAnnotatedField(element, elementUtils, enclosingTypeElement, + configurationParameter); } - private Property createProperty(ProcessingContext context) { - var name = processName(context); - var description = processDescription(context); - var sourceType = processSourceType(context); - var defaults = processDefaults(context); - var deprecation = processDeprecation(context); + private Property createProperty(ConfigurationParameterAnnotatedField field) { + var name = processName(field); + var description = processDescription(field); + var sourceType = processSourceType(field); + var defaults = processDefaults(field); + var deprecation = processDeprecation(field); var defaultType = defaults == null ? null : defaults.defaultType(); var defaultValue = defaults == null ? null : defaults.value(); - var type = processType(context, defaultType); + var type = processType(field, defaultType); return new Property(name, type, description, sourceType, defaultValue, deprecation); } - private String processName(ProcessingContext context) { + private String processName(ConfigurationParameterAnnotatedField field) { // TODO: Report preconditions problems with processingEnvironment().getMessager().printMessage() instead. - var enclosingTypeElement = context.enclosingType(); - var element = context.element(); - Preconditions.condition(isStatic(element), // - () -> "Field [%s.%s] must be declared static" // - .formatted(enclosingTypeElement.getQualifiedName(), element.getSimpleName())); - Preconditions.condition(isFinal(element), // - () -> "Field [%s.%s] must be declared final" // - .formatted(enclosingTypeElement.getQualifiedName(), element.getSimpleName())); - var constantValue = element.getConstantValue(); + Preconditions.condition(field.isStatic(), // + () -> "Field [%s] must be declared static".formatted(field.name())); + Preconditions.condition(field.isFinal(), // + () -> "Field [%s] must be declared final".formatted(field.name())); + var constantValue = field.constantValue(); Preconditions.condition(constantValue instanceof String, // - () -> "Field [%s.%s] must have a constant string value" // - .formatted(enclosingTypeElement.getQualifiedName(), element.getSimpleName())); + () -> "Field [%s] must have a constant string value".formatted(field.name())); return (String) constantValue; } - private @Nullable String processType(ProcessingContext context, @Nullable String defaultType) { - var parameter = context.annotationMirror(); - var type = getStringValue(parameter, "type"); - if (type == null || type.equals(Void.class.getName())) { - return defaultType; - } - return type; - } - - private @Nullable String processDescription(ProcessingContext context) { - var docComment = elementUtils.getDocComment(context.element()); - return docComment == null ? null : cleanupDocComment(docComment); + private @Nullable String processType(ConfigurationParameterAnnotatedField field, @Nullable String defaultType) { + var type = field.typeValue(); + return type == null ? defaultType : type; } - private static String cleanupDocComment(String docComment) { + private @Nullable String processDescription(ConfigurationParameterAnnotatedField field) { + var docComment = field.docComment(); + if (docComment == null) { + return null; + } // TODO: Creating patterns over and over is not very efficient var matcher = Pattern.compile("

|").matcher(docComment); var firstParagraph = !matcher.find() ? docComment : docComment.substring(0, matcher.start()); @@ -128,40 +116,26 @@ private static String cleanupDocComment(String docComment) { .trim(); } - private String processSourceType(ProcessingContext context) { - return context.enclosingType().getQualifiedName().toString(); + private String processSourceType(ConfigurationParameterAnnotatedField field) { + return field.enclosingTypeName(); } - private TypeElement getEnclosingTypeElement(VariableElement element) { - var enclosingElement = element.getEnclosingElement(); - Preconditions.condition(enclosingElement instanceof TypeElement, // - () -> "[%s] did not have an enclosing type element" // - .formatted(element.getSimpleName())); - return (TypeElement) enclosingElement; - } - - private @Nullable Default processDefaults(ProcessingContext context) { - var element = context.element(); - var parameter = context.annotationMirror(); - var defaults = getAnnotationValue(parameter, "defaultValue"); - if (defaults == null) { + private @Nullable Default processDefaults(ConfigurationParameterAnnotatedField field) { + var defaultValues = field.defaultValues(); + if (defaultValues.isEmpty()) { return null; } - var defaultValues = getValuesMap(defaults); Preconditions.condition(defaultValues.size() == 1, // - () -> "Field [%s.%s] must have exactly one default value" // - .formatted(context.enclosingType(), element.getSimpleName())); + () -> "Field [%s] must have exactly one default value".formatted(field.name())); Preconditions.condition(defaultValues.size() == 1, // - () -> "Field [%s.%s] must have exactly one default value" // - .formatted(context.enclosingType(), element.getSimpleName())); + () -> "Field [%s] must have exactly one default value".formatted(field.name())); var entry = defaultValues.entrySet().iterator().next(); var value = entry.getValue(); Preconditions.condition(value.size() == 1, // - () -> "Field [%s.%s] must have exactly one default value" // - .formatted(context.enclosingType(), element.getSimpleName())); + () -> "Field [%s] must have exactly one default value".formatted(field.name())); var defaultValue = value.get(0); var defaultName = entry.getKey(); @@ -173,28 +147,16 @@ private record Default(String defaultType, Object value) { } - private ConfigurationMetaData.@Nullable Deprecation processDeprecation(ProcessingContext context) { - var deprecation = getAnnotationValue(context.annotationMirror(), "deprecation"); - if (deprecation != null) { - var reason = getStringValue(deprecation, "reason"); - var replacement = getStringValue(deprecation, "replacement"); - var since = getStringValue(deprecation, "since"); - if (reason != null || replacement != null || since != null) { - return new ConfigurationMetaData.Deprecation(null, reason, replacement, since); - } + private @Nullable Deprecation processDeprecation(ConfigurationParameterAnnotatedField field) { + var values = field.deprecationValues(); + if (!values.isEmpty()) { + return new Deprecation(null, values.get("reason"), values.get("replacement"), values.get("since")); } // Fallback, look for @Deprecated - if (getAnnotationMirror(context.element(), Deprecated.class) != null) { - return new ConfigurationMetaData.Deprecation(null, null, null, null); + if (field.isDeprecated()) { + return new Deprecation(null, null, null, null); } return null; } - private static boolean isStatic(VariableElement variableElement) { - return variableElement.getModifiers().contains(Modifier.STATIC); - } - - private static boolean isFinal(VariableElement variableElement) { - return variableElement.getModifiers().contains(Modifier.FINAL); - } } From 3a6ce7f3d8cb0090609eb3152de155be29c3822a Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 11 Aug 2026 04:54:56 +0200 Subject: [PATCH 056/117] Replace preconditions with error message --- .../src/main/java/module-info.java | 1 - ...latform-configuration-processor.gradle.kts | 11 ++- .../src/main/java/module-info.java | 1 - ...figurationMetadataAnnotationProcessor.java | 34 ++++---- ...nMetadataAnnotationProcessorException.java | 23 ++++++ .../ConfigurationParameterAnnotatedField.java | 39 +++++---- .../ConfigurationParameterHandler.java | 81 +++++++++---------- ...ationMetadataAnnotationProcessorTests.java | 63 ++++++++------- .../configuration/processor/TestCompiler.java | 43 ++++++---- .../junit-platform-commons.expected.txt | 2 +- ...tform-configuration-processor.expected.txt | 3 +- 11 files changed, 175 insertions(+), 126 deletions(-) create mode 100644 junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorException.java diff --git a/junit-platform-commons/src/main/java/module-info.java b/junit-platform-commons/src/main/java/module-info.java index e47524dd9ebb..d066d025a055 100644 --- a/junit-platform-commons/src/main/java/module-info.java +++ b/junit-platform-commons/src/main/java/module-info.java @@ -50,7 +50,6 @@ org.junit.jupiter.migrationsupport, org.junit.jupiter.params, org.junit.platform.console, - org.junit.platform.configuration.processor, org.junit.platform.engine, org.junit.platform.launcher, org.junit.platform.reporting, diff --git a/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts b/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts index 5a027dd0cbf4..2abb3c592344 100644 --- a/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts +++ b/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts @@ -10,7 +10,6 @@ description = "JUnit Platform Configuration Processor" dependencies { api(platform(projects.junitBom)) api(projects.junitPlatformConfigurationApi) - api(projects.junitPlatformCommons) compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) @@ -50,6 +49,16 @@ tasks { into(layout.buildDirectory.dir("licenses")) } shadowJar { + bundle { + bnd( + """ + Import-Package: \ + ${extra["importAPIGuardian"]},\ + ${extra["importJSpecify"]},\ + * + """ + ) + } relocate("jakarta.json", "org.junit.platform.configuration.processor.shadow.jakarta.json") relocate("org.eclipse.parsson", "org.junit.platform.configuration.processor.shadow.org.eclipse.parsson") exclude("META-INF/LICENSE.md", "META-INF/NOTICE.md") diff --git a/junit-platform-configuration-processor/src/main/java/module-info.java b/junit-platform-configuration-processor/src/main/java/module-info.java index 7aed42f9559f..1c134963564b 100644 --- a/junit-platform-configuration-processor/src/main/java/module-info.java +++ b/junit-platform-configuration-processor/src/main/java/module-info.java @@ -14,7 +14,6 @@ requires java.compiler; requires org.junit.platform.configuration.api; - requires org.junit.platform.commons; provides javax.annotation.processing.Processor with org.junit.platform.configuration.processor.ConfigurationMetadataAnnotationProcessor; } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index a1dca9204297..89435063257d 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -14,6 +14,8 @@ import static java.util.Objects.requireNonNull; import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; import java.io.OutputStreamWriter; import java.util.Set; @@ -67,7 +69,7 @@ public synchronized void init(ProcessingEnvironment environment) { super.init(environment); this.metaData = new ConfigurationMetaData(); this.configurationParameterHandler = new ConfigurationParameterHandler(metaData, - processingEnv.getElementUtils()); + processingEnv.getElementUtils(), processingEnv.getMessager()); } @Override @@ -78,7 +80,7 @@ public SourceVersion getSupportedSourceVersion() { @Override @SuppressWarnings("DoNotClaimAnnotations") public boolean process(Set annotations, RoundEnvironment roundEnv) { - configurationParameterHandler().process(roundEnv); + requireNonNull(configurationParameterHandler).process(roundEnv); if (roundEnv.processingOver()) { writeMetaData(); @@ -90,29 +92,21 @@ public boolean process(Set annotations, RoundEnvironment } private void writeMetaData() { - try { - var resource = processingEnvironment().getFiler() // - .createResource(StandardLocation.CLASS_OUTPUT, "", METADATA_PATH); - try (var out = new BufferedWriter(new OutputStreamWriter(resource.openOutputStream(), UTF_8))) { - var converter = new JsonConverter(); - var value = converter.toJsonObject(metaData()); - Json.createWriter(out).write(value); - } + var converter = new JsonConverter(); + try (var out = new BufferedWriter(new OutputStreamWriter(openOutputStream(), UTF_8))) { + var value = converter.toJsonObject(requireNonNull(metaData)); + Json.createWriter(out).write(value); } catch (Exception ex) { - throw new IllegalStateException("Failed to write metadata to [%s]".formatted(METADATA_PATH), ex); + var message = "Failed to write metadata to [%s]".formatted(METADATA_PATH); + throw new ConfigurationMetadataAnnotationProcessorException(message, ex); } } - private ConfigurationParameterHandler configurationParameterHandler() { - return requireNonNull(configurationParameterHandler); + private OutputStream openOutputStream() throws IOException { + var filer = requireNonNull(processingEnv).getFiler(); + var resource = filer.createResource(StandardLocation.CLASS_OUTPUT, "", METADATA_PATH); + return resource.openOutputStream(); } - private ProcessingEnvironment processingEnvironment() { - return requireNonNull(processingEnv); - } - - private ConfigurationMetaData metaData() { - return requireNonNull(metaData); - } } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorException.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorException.java new file mode 100644 index 000000000000..a6cc63bb53e9 --- /dev/null +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorException.java @@ -0,0 +1,23 @@ +/* + * Copyright 2026 the original author or 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.configuration.processor; + +import java.io.Serial; + +final class ConfigurationMetadataAnnotationProcessorException extends RuntimeException { + + @Serial + private static final long serialVersionUID = 1L; + + ConfigurationMetadataAnnotationProcessorException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java index a939eaaee399..5812fb127b1c 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java @@ -19,6 +19,7 @@ import java.util.Map; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; @@ -27,21 +28,29 @@ import org.jspecify.annotations.Nullable; final class ConfigurationParameterAnnotatedField { - private final VariableElement field; + private final VariableElement element; private final TypeElement enclosingType; - private final AnnotationMirror configurationParameter; + private final AnnotationMirror annotationMirror; private final Elements elementUtils; - ConfigurationParameterAnnotatedField(VariableElement field, Elements elementUtils, TypeElement enclosingType, - AnnotationMirror configurationParameter) { - this.field = field; + ConfigurationParameterAnnotatedField(VariableElement element, Elements elementUtils, TypeElement enclosingType, + AnnotationMirror annotationMirror) { + this.element = element; this.elementUtils = elementUtils; this.enclosingType = enclosingType; - this.configurationParameter = configurationParameter; + this.annotationMirror = annotationMirror; + } + + Element element() { + return element; + } + + AnnotationMirror annotationMirror() { + return annotationMirror; } Map deprecationValues() { - var deprecation = getAnnotationMirror(configurationParameter, "deprecation"); + var deprecation = getAnnotationMirror(annotationMirror, "deprecation"); if (deprecation == null) { return Collections.emptyMap(); } @@ -49,7 +58,7 @@ Map deprecationValues() { } Map> defaultValues() { - var defaultValue = getAnnotationMirror(configurationParameter, "defaultValue"); + var defaultValue = getAnnotationMirror(annotationMirror, "defaultValue"); if (defaultValue == null) { return Collections.emptyMap(); } @@ -58,33 +67,33 @@ Map> defaultValues() { @Nullable String typeValue() { - return getStringValuesMap(configurationParameter).get("type"); + return getStringValuesMap(annotationMirror).get("type"); } @Nullable Object constantValue() { - return field.getConstantValue(); + return element.getConstantValue(); } String name() { - return "%s.%s".formatted(enclosingType.getQualifiedName(), field.getSimpleName()); + return "%s.%s".formatted(enclosingType.getQualifiedName(), element.getSimpleName()); } boolean isStatic() { - return field.getModifiers().contains(Modifier.STATIC); + return element.getModifiers().contains(Modifier.STATIC); } boolean isFinal() { - return field.getModifiers().contains(Modifier.FINAL); + return element.getModifiers().contains(Modifier.FINAL); } boolean isDeprecated() { - return getAnnotationMirror(field, Deprecated.class) != null; + return getAnnotationMirror(element, Deprecated.class) != null; } @Nullable String docComment() { - return elementUtils.getDocComment(field); + return elementUtils.getDocComment(element); } String enclosingTypeName() { diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java index af1cc58ef85b..12361002edc1 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java @@ -11,18 +11,20 @@ package org.junit.platform.configuration.processor; import static java.util.Objects.requireNonNull; +import static javax.tools.Diagnostic.Kind.ERROR; import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getAnnotationMirror; import java.util.Map; import java.util.regex.Pattern; +import javax.annotation.processing.Messager; import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.util.Elements; import org.jspecify.annotations.Nullable; -import org.junit.platform.commons.util.Preconditions; import org.junit.platform.configuration.api.ConfigurationParameter; import org.junit.platform.configuration.processor.ConfigurationMetaData.Deprecation; import org.junit.platform.configuration.processor.ConfigurationMetaData.Property; @@ -44,33 +46,35 @@ final class ConfigurationParameterHandler { private final ConfigurationMetaData metaData; private final Elements elementUtils; + private final Messager messager; - ConfigurationParameterHandler(ConfigurationMetaData metaData, Elements elementUtils) { + ConfigurationParameterHandler(ConfigurationMetaData metaData, Elements elementUtils, Messager messager) { this.metaData = metaData; this.elementUtils = elementUtils; + this.messager = messager; } void process(RoundEnvironment roundEnv) { - roundEnv.getElementsAnnotatedWith(ConfigurationParameter.class).stream() // - .filter(VariableElement.class::isInstance) // - .map(VariableElement.class::cast) // - .map(this::createConfigurationParameterAnnotatedField) // - .map(this::createProperty) // - .forEach(metaData::addProperty); + roundEnv.getElementsAnnotatedWith(ConfigurationParameter.class).forEach(this::processElement); } - private ConfigurationParameterAnnotatedField createConfigurationParameterAnnotatedField(VariableElement element) { - var enclosingElement = element.getEnclosingElement(); - Preconditions.condition(enclosingElement instanceof TypeElement, // - () -> "[%s] did not have an enclosing typeValue element".formatted(element.getSimpleName().toString())); - var enclosingTypeElement = (TypeElement) enclosingElement; - var configurationParameter = requireNonNull(getAnnotationMirror(element, ConfigurationParameter.class)); - return new ConfigurationParameterAnnotatedField(element, elementUtils, enclosingTypeElement, - configurationParameter); - } - - private Property createProperty(ConfigurationParameterAnnotatedField field) { - var name = processName(field); + private void processElement(Element element) { + if (!(element instanceof VariableElement variableElement)) { + messager.printMessage(ERROR, "@ConfigurationParameter annotated element was not a field", element); + return; + } + if (!(variableElement.getEnclosingElement() instanceof TypeElement enclosingTypeElement)) { + messager.printMessage(ERROR, "@ConfigurationParameter annotated element did not have an enclosing type element", element); + return; + } + var annotationMirror = requireNonNull(getAnnotationMirror(element, ConfigurationParameter.class)); + var field = new ConfigurationParameterAnnotatedField(variableElement, elementUtils, enclosingTypeElement, + annotationMirror); + if (!field.isStatic() || !field.isFinal() || !(field.constantValue() instanceof String name)) { + messager.printMessage(ERROR, + "@ConfigurationParameter annotated field must static, final, and have constant string value", element); + return; + } var description = processDescription(field); var sourceType = processSourceType(field); var defaults = processDefaults(field); @@ -78,19 +82,8 @@ private Property createProperty(ConfigurationParameterAnnotatedField field) { var defaultType = defaults == null ? null : defaults.defaultType(); var defaultValue = defaults == null ? null : defaults.value(); var type = processType(field, defaultType); - return new Property(name, type, description, sourceType, defaultValue, deprecation); - } - - private String processName(ConfigurationParameterAnnotatedField field) { - // TODO: Report preconditions problems with processingEnvironment().getMessager().printMessage() instead. - Preconditions.condition(field.isStatic(), // - () -> "Field [%s] must be declared static".formatted(field.name())); - Preconditions.condition(field.isFinal(), // - () -> "Field [%s] must be declared final".formatted(field.name())); - var constantValue = field.constantValue(); - Preconditions.condition(constantValue instanceof String, // - () -> "Field [%s] must have a constant string value".formatted(field.name())); - return (String) constantValue; + var property = new Property(name, type, description, sourceType, defaultValue, deprecation); + metaData.addProperty(property); } private @Nullable String processType(ConfigurationParameterAnnotatedField field, @Nullable String defaultType) { @@ -104,6 +97,7 @@ private String processName(ConfigurationParameterAnnotatedField field) { return null; } // TODO: Creating patterns over and over is not very efficient + // TODO: Handle {@link ...}, check how does Spring do that? var matcher = Pattern.compile("

|").matcher(docComment); var firstParagraph = !matcher.find() ? docComment : docComment.substring(0, matcher.start()); return firstParagraph // @@ -125,18 +119,19 @@ private String processSourceType(ConfigurationParameterAnnotatedField field) { if (defaultValues.isEmpty()) { return null; } - - Preconditions.condition(defaultValues.size() == 1, // - () -> "Field [%s] must have exactly one default value".formatted(field.name())); - - Preconditions.condition(defaultValues.size() == 1, // - () -> "Field [%s] must have exactly one default value".formatted(field.name())); - + if (defaultValues.size() != 1) { + messager.printMessage(ERROR, "@ConfigurationParameter must have exactly one default value", field.element(), + field.annotationMirror()); + } var entry = defaultValues.entrySet().iterator().next(); var value = entry.getValue(); - Preconditions.condition(value.size() == 1, // - () -> "Field [%s] must have exactly one default value".formatted(field.name())); - + if (value.isEmpty()) { + return null; + } + if (value.size() != 1) { + messager.printMessage(ERROR, "@ConfigurationParameter must have exactly one default value", field.element(), + field.annotationMirror()); + } var defaultValue = value.get(0); var defaultName = entry.getKey(); var defaultType = requireNonNull(NAME_TO_TYPE_NAME.get(defaultName)); diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index 9028d152e4b1..b0b00534c57c 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -17,6 +17,7 @@ import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Locale; import org.assertj.core.api.ThrowableAssert; import org.intellij.lang.annotations.Language; @@ -65,14 +66,14 @@ void setup() { @Test void none() { - compiler.compile(Without.class); + compiler.compileWithoutError(Without.class); var metaDataPath = outputDirectory.resolve(expectedMetadataPath); assertThat(metaDataPath).doesNotExist(); } @Test void minimal() { - compiler.compile(Minimal.class); + compiler.compileWithoutError(Minimal.class); assertMetaDataIsEqualTo(""" { "properties": [ @@ -86,7 +87,7 @@ void minimal() { @Test void documented() { - compiler.compile(Documented.class); + compiler.compileWithoutError(Documented.class); assertMetaDataIsEqualTo(""" { "properties": [ @@ -101,7 +102,7 @@ void documented() { @Test void documentedWithAtValue() { - compiler.compile(DocumentedWithAtValue.class); + compiler.compileWithoutError(DocumentedWithAtValue.class); assertMetaDataIsEqualTo(""" { "properties": [ @@ -116,7 +117,7 @@ void documentedWithAtValue() { @Test void documentedWithMultipleLines() { - compiler.compile(DocumentedWithMultiLines.class); + compiler.compileWithoutError(DocumentedWithMultiLines.class); assertMetaDataIsEqualTo(""" { "properties": [ @@ -131,7 +132,7 @@ void documentedWithMultipleLines() { @Test void documentedWithMultipleParagraphs() { - compiler.compile(DocumentedWithMultipleParagraphs.class); + compiler.compileWithoutError(DocumentedWithMultipleParagraphs.class); assertMetaDataIsEqualTo(""" { "properties": [ @@ -146,7 +147,7 @@ void documentedWithMultipleParagraphs() { @Test void documentedWithHeader() { - compiler.compile(DocumentedWithHeader.class); + compiler.compileWithoutError(DocumentedWithHeader.class); assertMetaDataIsEqualTo(""" { "properties": [ @@ -163,7 +164,7 @@ void documentedWithHeader() { void deprecation() { // TODO: Class level? Inheritance? Meta? // TODO: Warning level? - compiler.compile(Deprecation.class); + compiler.compileWithoutError(Deprecation.class); assertMetaDataIsEqualTo(""" { "properties": [ @@ -178,7 +179,7 @@ void deprecation() { @Test void deprecationWithDetails() { - compiler.compile(DeprecationWithDetails.class); + compiler.compileWithoutError(DeprecationWithDetails.class); assertMetaDataIsEqualTo(""" { "properties": [ @@ -197,7 +198,7 @@ void deprecationWithDetails() { @Test void stringType() { - compiler.compile(TypeString.class); + compiler.compileWithoutError(TypeString.class); assertMetaDataIsEqualTo(""" { "properties": [ @@ -212,7 +213,7 @@ void stringType() { @Test void enumTypeWithStringDefault() { - compiler.compile(TypeEnumWithStringDefault.class); + compiler.compileWithoutError(TypeEnumWithStringDefault.class); assertMetaDataIsEqualTo(""" { "properties": [ @@ -228,7 +229,7 @@ void enumTypeWithStringDefault() { @Test void defaults() { - compiler.compile(Defaults.class); + compiler.compileWithoutError(Defaults.class); assertMetaDataIsEqualTo(""" { "properties": [ @@ -298,37 +299,45 @@ void defaults() { @Test void mustBeFinal() { - asserPreconditionViolation(() -> compiler.compile(NonFinal.class), - "Field [%s.EXAMPLE_PROPERTY_NAME] must be declared final" // - .formatted(NonFinal.class.getName())); + var result = compiler.compile(NonFinal.class); + assertThat(result.diagnostics()) // + .extracting(diagnostic -> diagnostic.getMessage(Locale.ROOT)) // + .contains( + "@ConfigurationParameter annotated field must static, final, and have constant string value"); } @Test void mustBeStatic() { - asserPreconditionViolation(() -> compiler.compile(NonStatic.class), - "Field [%s.EXAMPLE_PROPERTY_NAME] must be declared static" // - .formatted(NonStatic.class.getName())); + var result = compiler.compile(NonStatic.class); + assertThat(result.diagnostics()) // + .extracting(diagnostic -> diagnostic.getMessage(Locale.ROOT)) // + .contains( + "@ConfigurationParameter annotated field must static, final, and have constant string value"); } @Test void mustBeString() { - asserPreconditionViolation(() -> compiler.compile(NonString.class), - "Field [%s.EXAMPLE_PROPERTY_NAME] must have a constant string value" // - .formatted(NonString.class.getName())); + var result = compiler.compile(NonString.class); + assertThat(result.diagnostics()) // + .extracting(diagnostic -> diagnostic.getMessage(Locale.ROOT)) // + .contains( + "@ConfigurationParameter annotated field must static, final, and have constant string value"); } @Test void mustHaveExactlyOneSetOfDefaults() { - asserPreconditionViolation(() -> compiler.compile(DefaultDifferentSets.class), - "Field [%s.EXAMPLE_PROPERTY_NAME] must have exactly one default value" // - .formatted(DefaultDifferentSets.class.getName())); + var result = compiler.compile(DefaultDifferentSets.class); + assertThat(result.diagnostics()) // + .extracting(diagnostic -> diagnostic.getMessage(Locale.ROOT)) // + .contains("@ConfigurationParameter must have exactly one default value"); } @Test void mustHaveExactlyOneDefaultValue() { - asserPreconditionViolation(() -> compiler.compile(DefaultMultipleValues.class), - "Field [%s.EXAMPLE_PROPERTY_NAME] must have exactly one default value" // - .formatted(DefaultMultipleValues.class.getName())); + var result = compiler.compile(DefaultMultipleValues.class); + assertThat(result.diagnostics()) // + .extracting(diagnostic -> diagnostic.getMessage(Locale.ROOT)) // + .contains("@ConfigurationParameter must have exactly one default value"); } private void assertMetaDataIsEqualTo(@Language("JSON") String json) { diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/TestCompiler.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/TestCompiler.java index b56f33c52888..f619ccbf1e46 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/TestCompiler.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/TestCompiler.java @@ -25,6 +25,7 @@ import java.util.stream.Collectors; import javax.annotation.processing.Processor; +import javax.tools.Diagnostic; import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler; import javax.tools.SimpleJavaFileObject; @@ -44,7 +45,30 @@ class TestCompiler { this.processor = processor; } - void compile(Type type) { + void compileWithoutError(Type type) { + var result = compile(type); + var diagnostics = result.diagnostics(); + if (result.success() && diagnostics.isEmpty()) { + return; + } + fail(""" + Compilation of %s was not successful. + + Javac output: + + %s + + Diagnostics: + + %s + """.formatted(type, // + result.additionalOutput(), // + diagnostics.stream() // + .map(Objects::toString) // + .collect(Collectors.joining("\n")))); + } + + CompilationResult compile(Type type) { var options = List.of("-d", outputDirectory.toString()); var listener = new DiagnosticCollector<>(); var additionalOutput = new StringWriter(); @@ -58,22 +82,11 @@ void compile(Type type) { ); task.setProcessors(Set.of(processor)); var result = task.call(); - if (!result || !listener.getDiagnostics().isEmpty()) { - var diagnostics = listener.getDiagnostics().stream() // - .map(Objects::toString) // - .collect(Collectors.joining("\n")); - fail(""" - Compilation of %s was not successful. - - Javac output: - - %s + return new CompilationResult(result, listener.getDiagnostics(), additionalOutput.toString()); + } - Diagnostics: + record CompilationResult(boolean success, List> diagnostics, String additionalOutput) { - %s - """.formatted(type, additionalOutput, diagnostics)); - } } private static StandardJavaFileManager fileManagerOf(JavaCompiler compiler, DiagnosticCollector listener) { diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt index e33205f83333..65f6236bc4fb 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt @@ -16,4 +16,4 @@ requires org.apiguardian.api static transitive requires org.jspecify static transitive uses org.junit.platform.commons.support.scanning.ClasspathScanner qualified exports org.junit.platform.commons.logging to org.junit.jupiter.api org.junit.jupiter.engine org.junit.jupiter.migrationsupport org.junit.jupiter.params org.junit.platform.console org.junit.platform.engine org.junit.platform.launcher org.junit.platform.reporting org.junit.platform.suite.api org.junit.platform.suite.engine org.junit.platform.testkit org.junit.vintage.engine -qualified exports org.junit.platform.commons.util to org.junit.jupiter.api org.junit.jupiter.engine org.junit.jupiter.migrationsupport org.junit.jupiter.params org.junit.platform.configuration.processor org.junit.platform.console org.junit.platform.engine org.junit.platform.launcher org.junit.platform.reporting org.junit.platform.suite.api org.junit.platform.suite.engine org.junit.platform.testkit org.junit.vintage.engine +qualified exports org.junit.platform.commons.util to org.junit.jupiter.api org.junit.jupiter.engine org.junit.jupiter.migrationsupport org.junit.jupiter.params org.junit.platform.console org.junit.platform.engine org.junit.platform.launcher org.junit.platform.reporting org.junit.platform.suite.api org.junit.platform.suite.engine org.junit.platform.testkit org.junit.vintage.engine diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt index 7c09f55deb96..b9faa0009c62 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-configuration-processor.expected.txt @@ -3,7 +3,6 @@ requires java.base mandated requires java.compiler requires org.apiguardian.api static transitive requires org.jspecify static transitive -requires org.junit.platform.commons requires org.junit.platform.configuration.api provides javax.annotation.processing.Processor with org.junit.platform.configuration.processor.ConfigurationMetadataAnnotationProcessor -contains org.junit.platform.configuration.processor \ No newline at end of file +contains org.junit.platform.configuration.processor From ae8b921848faa4dea1574f483f9b47d3520dd998 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 11 Aug 2026 04:57:55 +0200 Subject: [PATCH 057/117] Nitpick --- .../configuration/processor/ConfigurationParameterHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java index 12361002edc1..d3d507434914 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java @@ -64,7 +64,8 @@ private void processElement(Element element) { return; } if (!(variableElement.getEnclosingElement() instanceof TypeElement enclosingTypeElement)) { - messager.printMessage(ERROR, "@ConfigurationParameter annotated element did not have an enclosing type element", element); + messager.printMessage(ERROR, "@ConfigurationParameter annotated element did not have an enclosing element", + element); return; } var annotationMirror = requireNonNull(getAnnotationMirror(element, ConfigurationParameter.class)); From dc9a43abf7073e4b424b82a5ede6d5d445dd7966 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 11 Aug 2026 05:11:47 +0200 Subject: [PATCH 058/117] Documentation --- ...ConfigurationMetadataAnnotationProcessor.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index 89435063257d..6e904454327c 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -32,12 +32,12 @@ import jakarta.json.Json; -/// Collects all configuration parameters marked with -/// {@link org.junit.platform.configuration.api.ConfigurationParameter} into -/// [Spring Boot Configuration +/// Writes all configuration parameters marked with +/// {@link org.junit.platform.configuration.api.ConfigurationParameter} to +/// {@value #METADATA_PATH} in [Spring Boot's Configuration /// Metadata](https://docs.spring.io/spring-boot/specification/configuration-metadata/format.html) -/// and writes it to {@value #METADATA_PATH}. This enables IDE's and other tools -/// to process and validate Test Engine configuration. +/// format. This enables IDEs and other tools to process and validate Test Engine +/// configuration. /// ///

Usage

/// @@ -53,9 +53,9 @@ /// /// } /// -/// The first paragraph from the doc string used to describe the property. If the -/// first paragraph ends with {@code : {@value}.} or {@code : {@value}} it will -/// be replaced with a {@code : {@value}.}. +/// The first paragraph from the doc string will be used to describe the +/// property. If the first paragraph ends with {@code : {@value}.} or +/// {@code : {@value}} it will be replaced with a {@code : {@value}.}. /// @API(status = API.Status.EXPERIMENTAL) @SupportedAnnotationTypes("org.junit.platform.configuration.api.ConfigurationParameter") From 024414719e99281018c6f5a5aea177195f449a71 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 11 Aug 2026 16:37:22 +0200 Subject: [PATCH 059/117] Add integration test with Maven --- .../pom.xml | 69 +++++++++++++++++++ .../src/main/java/org/example/Constants.java | 19 +++++ .../platform/tooling/support/Projects.java | 1 + ...itPlatformConfigurationProcessorTests.java | 59 ++++++++++++++++ 4 files changed, 148 insertions(+) create mode 100644 platform-tooling-support-tests/projects/maven-junit-platform-configuration-processor/pom.xml create mode 100644 platform-tooling-support-tests/projects/maven-junit-platform-configuration-processor/src/main/java/org/example/Constants.java create mode 100644 platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenJunitPlatformConfigurationProcessorTests.java diff --git a/platform-tooling-support-tests/projects/maven-junit-platform-configuration-processor/pom.xml b/platform-tooling-support-tests/projects/maven-junit-platform-configuration-processor/pom.xml new file mode 100644 index 000000000000..9eda21a3de00 --- /dev/null +++ b/platform-tooling-support-tests/projects/maven-junit-platform-configuration-processor/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + + org.example + maven-junit-platform-configuration-processor + 1.0-SNAPSHOT + + + UTF-8 + 17 + + + + + org.junit.platform + junit-platform-configuration-api + true + + + + + + + org.junit + junit-bom + ${junit.version} + pom + import + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.15.0 + + true + + + org.junit.platform + junit-platform-configuration-processor + + + + + + + + + + local-temp + file://${maven.repo} + + true + ignore + + + true + ignore + + + + + diff --git a/platform-tooling-support-tests/projects/maven-junit-platform-configuration-processor/src/main/java/org/example/Constants.java b/platform-tooling-support-tests/projects/maven-junit-platform-configuration-processor/src/main/java/org/example/Constants.java new file mode 100644 index 000000000000..7356159f151c --- /dev/null +++ b/platform-tooling-support-tests/projects/maven-junit-platform-configuration-processor/src/main/java/org/example/Constants.java @@ -0,0 +1,19 @@ +/* + * Copyright 2026 the original author or 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.example; + +import org.junit.platform.configuration.api.ConfigurationParameter; + +public final class Constants { + + @ConfigurationParameter + public static final String EXAMPLE_PROPERTY = "org.example.property"; +} diff --git a/platform-tooling-support-tests/src/main/java/platform/tooling/support/Projects.java b/platform-tooling-support-tests/src/main/java/platform/tooling/support/Projects.java index a29439b115e6..23284fa19b40 100644 --- a/platform-tooling-support-tests/src/main/java/platform/tooling/support/Projects.java +++ b/platform-tooling-support-tests/src/main/java/platform/tooling/support/Projects.java @@ -25,6 +25,7 @@ public class Projects { public static final String JUPITER_STARTER = "jupiter-starter"; public static final String KOTLIN_COROUTINES = "kotlin-coroutines"; public static final String MAVEN_SUREFIRE_COMPATIBILITY = "maven-surefire-compatibility"; + public static final String MAVEN_JUNIT_PLATFORM_CONFIGURATION_PROCESSOR = "maven-junit-platform-configuration-processor"; public static final String MEMORY_CLEANUP = "memory-cleanup"; public static final String REFLECTION_TESTS = "reflection-tests"; public static final String STANDALONE = "standalone"; diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenJunitPlatformConfigurationProcessorTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenJunitPlatformConfigurationProcessorTests.java new file mode 100644 index 000000000000..08aa7f8da8ea --- /dev/null +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenJunitPlatformConfigurationProcessorTests.java @@ -0,0 +1,59 @@ +/* + * Copyright 2015-2026 the original author or 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 platform.tooling.support.tests; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static platform.tooling.support.Projects.copyToWorkspace; + +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.platform.tests.process.OutputFiles; +import org.opentest4j.TestAbortedException; + +import platform.tooling.support.FilePrefix; +import platform.tooling.support.Helper; +import platform.tooling.support.MavenRepo; +import platform.tooling.support.ProcessStarters; +import platform.tooling.support.Projects; + +/** + * @since 6.2.0 + */ +class MavenJunitPlatformConfigurationProcessorTests { + + @ManagedResource + LocalMavenRepo localMavenRepo; + + @Test + void processesAnnotationsIntoMetaData(@TempDir Path workspace, @FilePrefix("maven") OutputFiles outputFiles) + throws Exception { + var result = ProcessStarters.maven(Helper.getJavaHome(17).orElseThrow(TestAbortedException::new)) // + .workingDir(copyToWorkspace(Projects.MAVEN_JUNIT_PLATFORM_CONFIGURATION_PROCESSOR, workspace)) // + .addArguments(localMavenRepo.toCliArgument(), "-Dmaven.repo=" + MavenRepo.dir()) // + .addArguments("--update-snapshots", "--batch-mode", "compile") // + .redirectOutput(outputFiles) // + .startAndWait(); + + assertEquals(0, result.exitCode()); + assertEquals("", result.stdErr()); + + var output = result.stdOutLines(); + assertTrue(output.contains("[INFO] BUILD SUCCESS")); + + var metaData = workspace.resolve("target/classes/META-INF/junit-platform-configuration-metadata.json"); + assertThat(metaData).exists().content().isEqualTo(""" + {"properties":[{"name":"org.example.property","sourceType":"org.example.Constants"}]}"""); + } +} From 98f088541c569a0aca2b14e29d77f7c4b6236591 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 11 Aug 2026 17:05:28 +0200 Subject: [PATCH 060/117] Annotate constant classes --- .../java/org/junit/jupiter/api/Constants.java | 14 ++++---- .../platform/launcher/LauncherConstants.java | 33 ++++++++++++++----- .../org/junit/vintage/engine/Constants.java | 7 +++- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java index 2a8d1a4c68f1..146a22256081 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java @@ -200,7 +200,7 @@ public final class Constants { * @see Lifecycle#DEFAULT_LIFECYCLE_PROPERTY_NAME */ // TODO: Reference enum values as strings? - @ConfigurationParameter(type = Lifecycle.class, defaultValue = @Value(stringValue = "PER_METHOD")) + @ConfigurationParameter(type = Lifecycle.class, defaultValue = @Value(stringValue = "per_method")) public static final String DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME = Lifecycle.DEFAULT_LIFECYCLE_PROPERTY_NAME; /** @@ -218,7 +218,7 @@ public final class Constants { * @see Execution#DEFAULT_EXECUTION_MODE_PROPERTY_NAME */ // TODO: Reference enum values as strings? - @ConfigurationParameter(type = ExecutionMode.class, defaultValue = @Value(stringValue = "SAME_THREAD")) + @ConfigurationParameter(type = ExecutionMode.class, defaultValue = @Value(stringValue = "same_thread")) public static final String DEFAULT_EXECUTION_MODE_PROPERTY_NAME = Execution.DEFAULT_EXECUTION_MODE_PROPERTY_NAME; /** @@ -247,7 +247,7 @@ public final class Constants { */ // TODO: ParallelExecutorServiceType is not part of the public API // TODO: Reference enum values as strings? - @ConfigurationParameter(/* type = ParallelExecutorServiceType.class,*/ defaultValue = @Value(stringValue = "FORK_JOIN_POOL")) + @ConfigurationParameter(/* type = ParallelExecutorServiceType.class,*/ defaultValue = @Value(stringValue = "fork_join_pool")) public static final String PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "executor-service"; @@ -261,7 +261,7 @@ public final class Constants { */ // TODO: DefaultParallelExecutionConfigurationStrategy is not part of the public API // TODO: Reference enum values as strings? - @ConfigurationParameter(/* type = DefaultParallelExecutionConfigurationStrategy.class,*/ defaultValue = @Value(stringValue = "DYNAMIC")) + @ConfigurationParameter(/* type = DefaultParallelExecutionConfigurationStrategy.class,*/ defaultValue = @Value(stringValue = "dynamic")) public static final String PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "strategy"; /** @@ -453,7 +453,7 @@ public final class Constants { * @see Timeout.ThreadMode */ // TODO: Reference enum values as strings? - @ConfigurationParameter(type = Timeout.ThreadMode.class, defaultValue = @Value(stringValue = "SAME_THREAD")) + @ConfigurationParameter(type = Timeout.ThreadMode.class, defaultValue = @Value(stringValue = "same_thread")) public static final String DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME = Timeout.DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME; /** @@ -473,7 +473,7 @@ public final class Constants { * @see TempDir#DEFAULT_CLEANUP_MODE_PROPERTY_NAME */ // TODO: Reference enum values as strings? - @ConfigurationParameter(type = CleanupMode.class, defaultValue = @Value(stringValue = "ALWAYS")) + @ConfigurationParameter(type = CleanupMode.class, defaultValue = @Value(stringValue = "always")) public static final String DEFAULT_TEMP_DIR_CLEANUP_MODE_PROPERTY_NAME = TempDir.DEFAULT_CLEANUP_MODE_PROPERTY_NAME; /** @@ -494,7 +494,7 @@ public final class Constants { * @see org.junit.jupiter.api.extension.TestInstantiationAwareExtension */ // TODO: Reference enum values as strings? - @ConfigurationParameter(type = ExtensionContextScope.class, defaultValue = @Value(stringValue = "DEFAULT")) + @ConfigurationParameter(type = ExtensionContextScope.class, defaultValue = @Value(stringValue = "default")) public static final String DEFAULT_TEST_CLASS_INSTANCE_CONSTRUCTION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME = ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME; private Constants() { 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 5f5e6155e9be..9fc74b42932d 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 @@ -17,6 +17,8 @@ import org.apiguardian.api.API; import org.junit.platform.commons.util.ClassNamePatternFilterUtils; import org.junit.platform.configuration.api.ConfigurationParameter; +import org.junit.platform.configuration.api.ConfigurationParameter.Value; +import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.reporting.ReportEntry; @@ -45,7 +47,7 @@ public class LauncherConstants { * @see ReportEntry * @see TestExecutionListener#reportingEntryPublished(TestIdentifier, ReportEntry) */ - @ConfigurationParameter + @ConfigurationParameter(defaultValue = @Value(booleanValue = false)) public static final String CAPTURE_STDOUT_PROPERTY_NAME = "junit.platform.output.capture.stdout"; /** @@ -63,8 +65,17 @@ public class LauncherConstants { * @see ReportEntry * @see TestExecutionListener#reportingEntryPublished(TestIdentifier, ReportEntry) */ + @ConfigurationParameter(defaultValue = @Value(booleanValue = false)) public static final String CAPTURE_STDERR_PROPERTY_NAME = "junit.platform.output.capture.stderr"; + /** + * Default maximum number of bytes for buffering to use per thread and + * output type if output capturing is enabled. + * + * @see #CAPTURE_MAX_BUFFER_PROPERTY_NAME + */ + public static final int CAPTURE_MAX_BUFFER_DEFAULT = 4 * 1024 * 1024; + /** * Property name used to configure the maximum number of bytes for buffering * to use per thread and output type if output capturing is enabled: @@ -74,16 +85,9 @@ public class LauncherConstants { * * @see #CAPTURE_MAX_BUFFER_DEFAULT */ + @ConfigurationParameter(defaultValue = @Value(intValue = CAPTURE_MAX_BUFFER_DEFAULT)) public static final String CAPTURE_MAX_BUFFER_PROPERTY_NAME = "junit.platform.output.capture.maxBuffer"; - /** - * Default maximum number of bytes for buffering to use per thread and - * output type if output capturing is enabled. - * - * @see #CAPTURE_MAX_BUFFER_PROPERTY_NAME - */ - public static final int CAPTURE_MAX_BUFFER_DEFAULT = 4 * 1024 * 1024; - /** * Key used to publish captured output to {@link System#out} as part of a * {@link ReportEntry}: {@value} @@ -141,6 +145,7 @@ public class LauncherConstants { * @see #DEACTIVATE_ALL_LISTENERS_PATTERN * @see org.junit.platform.launcher.TestExecutionListener */ + @ConfigurationParameter(type = String.class) public static final String DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME = "junit.platform.execution.listeners.deactivate"; /** @@ -168,6 +173,7 @@ public class LauncherConstants { * @see LauncherInterceptor */ @API(status = MAINTAINED, since = "1.13.3") + @ConfigurationParameter(defaultValue = @Value(booleanValue = false)) public static final String ENABLE_LAUNCHER_INTERCEPTORS = "junit.platform.launcher.interceptors.enabled"; /** @@ -184,6 +190,7 @@ public class LauncherConstants { *

Value must be either {@code true} or {@code false}; defaults to {@code false}. */ @API(status = MAINTAINED, since = "1.13.3") + @ConfigurationParameter(defaultValue = @Value(booleanValue = false)) public static final String DRY_RUN_PROPERTY_NAME = "junit.platform.execution.dryRun.enabled"; /** @@ -194,6 +201,7 @@ public class LauncherConstants { * @see org.junit.platform.launcher.core.EngineExecutionOrchestrator */ @API(status = MAINTAINED, since = "1.13.3") + @ConfigurationParameter(defaultValue = @Value(booleanValue = true)) public static final String STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME = "junit.platform.stacktrace.pruning.enabled"; /** @@ -210,6 +218,7 @@ public class LauncherConstants { * @see TestPlan#getOutputDirectoryCreator() */ @API(status = MAINTAINED, since = "1.13.3") + @ConfigurationParameter(type = String.class) public static final String OUTPUT_DIR_PROPERTY_NAME = "junit.platform.reporting.output.dir"; /** @@ -250,6 +259,7 @@ public class LauncherConstants { * @see org.junit.platform.engine.DiscoveryIssue.Severity */ @API(status = EXPERIMENTAL, since = "6.0") + @ConfigurationParameter(type = Severity.class, defaultValue = @Value(stringValue = "error")) public static final String CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME = "junit.platform.discovery.issue.severity.critical"; /** @@ -270,6 +280,9 @@ public class LauncherConstants { * @see #CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME */ @API(status = EXPERIMENTAL, since = "6.0") + // TODO: LauncherPhase is not part of the public API + // TODO: Reference enum values as strings? + @ConfigurationParameter(/*type = LauncherPhase.class,*/ defaultValue = @Value(stringValue = "discovery")) public static final String DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME = "junit.platform.discovery.issue.failure.phase"; /** @@ -296,6 +309,7 @@ public class LauncherConstants { * @see #MEMORY_CLEANUP_EXCLUDED_ENGINES_PROPERTY_NAME */ @API(status = EXPERIMENTAL, since = "6.1") + @ConfigurationParameter(defaultValue = @Value(booleanValue = false)) public static final String MEMORY_CLEANUP_ENABLED_PROPERTY_NAME = "junit.platform.execution.memory.cleanup.enabled"; /** @@ -318,6 +332,7 @@ public class LauncherConstants { * @see #MEMORY_CLEANUP_ENABLED_PROPERTY_NAME */ @API(status = EXPERIMENTAL, since = "6.1.1") + @ConfigurationParameter(type = String.class) public static final String MEMORY_CLEANUP_EXCLUDED_ENGINES_PROPERTY_NAME = "junit.platform.execution.memory.cleanup.engines.excluded"; private LauncherConstants() { diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java index 51fa6b0f46a4..061fa4528599 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java @@ -15,6 +15,7 @@ import org.apiguardian.api.API; import org.junit.platform.configuration.api.ConfigurationParameter; +import org.junit.platform.configuration.api.ConfigurationParameter.Value; /** * Collection of constants related to the {@link VintageTestEngine}. @@ -35,7 +36,7 @@ public final class Constants { * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") - @ConfigurationParameter + @ConfigurationParameter(defaultValue = @Value(booleanValue = false)) public static final String PARALLEL_EXECUTION_ENABLED = "junit.vintage.execution.parallel.enabled"; /** @@ -48,6 +49,7 @@ public final class Constants { * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") + @ConfigurationParameter(type = Integer.class) public static final String PARALLEL_POOL_SIZE = "junit.vintage.execution.parallel.pool-size"; /** @@ -60,6 +62,7 @@ public final class Constants { * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") + @ConfigurationParameter(defaultValue = @Value(booleanValue = false)) public static final String PARALLEL_CLASS_EXECUTION = "junit.vintage.execution.parallel.classes"; /** @@ -72,6 +75,7 @@ public final class Constants { * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") + @ConfigurationParameter(defaultValue = @Value(booleanValue = false)) public static final String PARALLEL_METHOD_EXECUTION = "junit.vintage.execution.parallel.methods"; /** @@ -84,6 +88,7 @@ public final class Constants { * @since 6.0.1 */ @API(status = MAINTAINED, since = "6.0.1") + @ConfigurationParameter(defaultValue = @Value(booleanValue = true)) public static final String DISCOVERY_ISSUE_REPORTING_ENABLED_PROPERTY_NAME = "junit.vintage.discovery.issue.reporting.enabled"; private Constants() { From e0e8e673f71c33172af26870a32bcb51657afc29 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 14 Aug 2026 10:54:13 +0200 Subject: [PATCH 061/117] Move mapping closer to source --- .../ConfigurationParameterAnnotatedField.java | 4 -- .../ConfigurationParameterHandler.java | 42 ++++++++----------- .../processor/JsonConverter.java | 3 +- 3 files changed, 20 insertions(+), 29 deletions(-) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java index 5812fb127b1c..1cc5ecf824de 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java @@ -75,10 +75,6 @@ Object constantValue() { return element.getConstantValue(); } - String name() { - return "%s.%s".formatted(enclosingType.getQualifiedName(), element.getSimpleName()); - } - boolean isStatic() { return element.getModifiers().contains(Modifier.STATIC); } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java index d3d507434914..2aca3925ef81 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java @@ -14,7 +14,6 @@ import static javax.tools.Diagnostic.Kind.ERROR; import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getAnnotationMirror; -import java.util.Map; import java.util.regex.Pattern; import javax.annotation.processing.Messager; @@ -31,19 +30,6 @@ final class ConfigurationParameterHandler { - private static final Map NAME_TO_TYPE_NAME = Map.of( // - "shortValue", Short.class.getName(), // - "byteValue", Byte.class.getName(), // - "intValue", Integer.class.getName(), // - "longValue", Long.class.getName(), // - "floatValue", Float.class.getName(), // - "doubleValue", Double.class.getName(), // - "charValue", Character.class.getName(), // - "booleanValue", Boolean.class.getName(), // - "stringValue", String.class.getName(), // - "classValue", Class.class.getName() // - ); - private final ConfigurationMetaData metaData; private final Elements elementUtils; private final Messager messager; @@ -98,7 +84,7 @@ private void processElement(Element element) { return null; } // TODO: Creating patterns over and over is not very efficient - // TODO: Handle {@link ...}, check how does Spring do that? + // TODO: Handle {@link ...}, {@linkplain ...}, check how does Spring do that? var matcher = Pattern.compile("

|").matcher(docComment); var firstParagraph = !matcher.find() ? docComment : docComment.substring(0, matcher.start()); return firstParagraph // @@ -125,18 +111,26 @@ private String processSourceType(ConfigurationParameterAnnotatedField field) { field.annotationMirror()); } var entry = defaultValues.entrySet().iterator().next(); - var value = entry.getValue(); - if (value.isEmpty()) { - return null; - } - if (value.size() != 1) { + var values = entry.getValue(); + if (values.size() != 1) { messager.printMessage(ERROR, "@ConfigurationParameter must have exactly one default value", field.element(), field.annotationMirror()); } - var defaultValue = value.get(0); - var defaultName = entry.getKey(); - var defaultType = requireNonNull(NAME_TO_TYPE_NAME.get(defaultName)); - return new Default(defaultType, defaultValue); + var key = entry.getKey(); + var value = values.get(0); + return switch (key) { + case "shortValue" -> new Default(Short.class.getName(), value); + case "byteValue" -> new Default(Byte.class.getName(), value); + case "intValue" -> new Default(Integer.class.getName(), value); + case "longValue" -> new Default(Long.class.getName(), value); + case "floatValue" -> new Default(Float.class.getName(), value); + case "doubleValue" -> new Default(Double.class.getName(), value); + case "charValue" -> new Default(Character.class.getName(), value); + case "booleanValue" -> new Default(Boolean.class.getName(), value); + case "stringValue" -> new Default(String.class.getName(), value); + case "classValue" -> new Default(Class.class.getName(), value.toString()); + default -> throw new IllegalStateException("Unexpected value: " + key); + }; } private record Default(String defaultType, Object value) { diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java index 766c1781a793..90323286a838 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java @@ -101,7 +101,8 @@ else if (defaultValue instanceof String v) { builder.add(name, v); } else { - builder.add(name, defaultValue.toString()); + throw new IllegalArgumentException( + "Field [%s] should be a convertable primitive but was %s".formatted(name, defaultValue.getClass())); } } From b9719c6f6fd0120498b90562c9e40311147efa64 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 15 Aug 2026 16:55:59 +0200 Subject: [PATCH 062/117] Extract defaults for Vintage Constants --- .../org/junit/vintage/engine/Constants.java | 32 ++++++++++++++++--- .../engine/discovery/VintageDiscoverer.java | 2 +- .../engine/execution/VintageExecutor.java | 10 +++--- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java index 061fa4528599..ff6826107962 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java @@ -27,6 +27,12 @@ @API(status = DEPRECATED, since = "6.0") public final class Constants { + /** + * TODO: + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final boolean PARALLEL_EXECUTION_ENABLED_DEFAULT = false; + /** * Property name used to indicate whether parallel execution is enabled for the JUnit Vintage engine: {@value} * @@ -36,7 +42,7 @@ public final class Constants { * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") - @ConfigurationParameter(defaultValue = @Value(booleanValue = false)) + @ConfigurationParameter(defaultValue = @Value(booleanValue = PARALLEL_EXECUTION_ENABLED_DEFAULT)) public static final String PARALLEL_EXECUTION_ENABLED = "junit.vintage.execution.parallel.enabled"; /** @@ -52,6 +58,12 @@ public final class Constants { @ConfigurationParameter(type = Integer.class) public static final String PARALLEL_POOL_SIZE = "junit.vintage.execution.parallel.pool-size"; + /** + * TODO: + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final boolean PARALLEL_CLASS_EXECUTION_DEFAULT = false; + /** * Property name used to indicate whether parallel execution is enabled for test classes in the * JUnit Vintage engine: {@value} @@ -62,9 +74,15 @@ public final class Constants { * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") - @ConfigurationParameter(defaultValue = @Value(booleanValue = false)) + @ConfigurationParameter(defaultValue = @Value(booleanValue = PARALLEL_EXECUTION_ENABLED_DEFAULT)) public static final String PARALLEL_CLASS_EXECUTION = "junit.vintage.execution.parallel.classes"; + /** + * TODO: + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final boolean PARALLEL_METHOD_EXECUTION_DEFAULT = false; + /** * Property name used to indicate whether parallel execution is enabled for test methods in the * JUnit Vintage engine: {@value} @@ -75,9 +93,15 @@ public final class Constants { * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") - @ConfigurationParameter(defaultValue = @Value(booleanValue = false)) + @ConfigurationParameter(defaultValue = @Value(booleanValue = PARALLEL_METHOD_EXECUTION_DEFAULT)) public static final String PARALLEL_METHOD_EXECUTION = "junit.vintage.execution.parallel.methods"; + /** + * TODO: + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final boolean DISCOVERY_ISSUE_REPORTING_ENABLED_DEFAULT = true; + /** * Property name used to configure whether the JUnit Vintage engine should * report discovery issues such as deprecation notices: {@value} @@ -88,7 +112,7 @@ public final class Constants { * @since 6.0.1 */ @API(status = MAINTAINED, since = "6.0.1") - @ConfigurationParameter(defaultValue = @Value(booleanValue = true)) + @ConfigurationParameter(defaultValue = @Value(booleanValue = DISCOVERY_ISSUE_REPORTING_ENABLED_DEFAULT)) public static final String DISCOVERY_ISSUE_REPORTING_ENABLED_PROPERTY_NAME = "junit.vintage.discovery.issue.reporting.enabled"; private Constants() { diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java index 14b0245ec9cb..0966ebea17fd 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java @@ -61,7 +61,7 @@ public VintageEngineDescriptor discover(EngineDiscoveryRequest discoveryRequest, private static boolean isDiscoveryIssueReportingEnabled(EngineDiscoveryRequest discoveryRequest) { return discoveryRequest.getConfigurationParameters() // .getBoolean(Constants.DISCOVERY_ISSUE_REPORTING_ENABLED_PROPERTY_NAME) // - .orElse(true); + .orElse(Constants.DISCOVERY_ISSUE_REPORTING_ENABLED_DEFAULT); } } diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/VintageExecutor.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/VintageExecutor.java index 35cb7a575ed8..4d73d50be019 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/VintageExecutor.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/VintageExecutor.java @@ -59,10 +59,12 @@ public VintageExecutor(VintageEngineDescriptor engineDescriptor, EngineExecution this.engineDescriptor = engineDescriptor; this.engineExecutionListener = engineExecutionListener; this.configurationParameters = configurationParameters; - this.parallelExecutionEnabled = configurationParameters.getBoolean(Constants.PARALLEL_EXECUTION_ENABLED).orElse( - false); - this.classes = configurationParameters.getBoolean(Constants.PARALLEL_CLASS_EXECUTION).orElse(false); - this.methods = configurationParameters.getBoolean(Constants.PARALLEL_METHOD_EXECUTION).orElse(false); + this.parallelExecutionEnabled = configurationParameters.getBoolean(Constants.PARALLEL_EXECUTION_ENABLED) // + .orElse(Constants.PARALLEL_EXECUTION_ENABLED_DEFAULT); + this.classes = configurationParameters.getBoolean(Constants.PARALLEL_CLASS_EXECUTION) // + .orElse(Constants.PARALLEL_CLASS_EXECUTION_DEFAULT); + this.methods = configurationParameters.getBoolean(Constants.PARALLEL_METHOD_EXECUTION) // + .orElse(Constants.PARALLEL_METHOD_EXECUTION_DEFAULT); } public void executeAllChildren(CancellationToken cancellationToken) { From 0c8f652001d4b8674713d6978f713c18e14ef697 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 15 Aug 2026 17:24:28 +0200 Subject: [PATCH 063/117] Extract defaults for Jupiter Constants --- .../java/org/junit/jupiter/api/Constants.java | 58 ++++++++++++++++--- .../config/DefaultJupiterConfiguration.java | 19 ++++-- ...arallelExecutionConfigurationStrategy.java | 16 ++++- .../org/junit/jupiter/api/ConstantTests.java | 8 +++ 4 files changed, 86 insertions(+), 15 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java index 146a22256081..2dfaef20236d 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java @@ -12,6 +12,7 @@ import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.INTERNAL; +import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import static org.junit.platform.configuration.api.ConfigurationParameter.Value; @@ -40,6 +41,12 @@ @API(status = STABLE, since = "6.1") public final class Constants { + /** + * TODO: + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final String INCLUDE_ALL_EXTENSIONS_PATTERN = "*"; + /** * Property name used to include patterns for auto-detecting extensions: {@value} * @@ -73,8 +80,7 @@ public final class Constants { * *

Note: A class that matches both an inclusion and exclusion pattern will be excluded. */ - // TODO: ClassNamePatternFilterUtils.ALL_PATTERN isn't part of the public API - @ConfigurationParameter(defaultValue = @Value(stringValue = "*")) + @ConfigurationParameter(defaultValue = @Value(stringValue = INCLUDE_ALL_EXTENSIONS_PATTERN)) public static final String EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.include"; /** @@ -113,6 +119,12 @@ public final class Constants { @ConfigurationParameter(type = String.class) public static final String EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.exclude"; + /** + * TODO: + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final boolean EXTENSIONS_AUTODETECTION_ENABLED_DEFAULT = false; + /** * Property name used to enable auto-detection and registration of extensions via * Java's {@link java.util.ServiceLoader} mechanism: {@value} @@ -120,16 +132,22 @@ public final class Constants { *

The default behavior is not to perform auto-detection. */ // TODO: Handle @link references. - @ConfigurationParameter(defaultValue = @Value(booleanValue = false)) + @ConfigurationParameter(defaultValue = @Value(booleanValue = EXTENSIONS_AUTODETECTION_ENABLED_DEFAULT)) public static final String EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.enabled"; + /** + * TODO: + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final boolean CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_DEFAULT = true; + /** * Property name used to enable auto-closing of {@link AutoCloseable} instances: {@value} * *

By default, auto-closing is enabled. * */ - @ConfigurationParameter(defaultValue = @Value(booleanValue = true)) + @ConfigurationParameter(defaultValue = @Value(booleanValue = CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_DEFAULT)) public static final String CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME = "junit.jupiter.extensions.store.close.autocloseable.enabled"; /** @@ -185,13 +203,19 @@ public final class Constants { @ConfigurationParameter(type = DisplayNameGenerator.class, defaultValue = @Value(classValue = Standard.class)) public static final String DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME = DisplayNameGenerator.DEFAULT_GENERATOR_PROPERTY_NAME; + /** + * TODO: + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final boolean EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_DEFAULT = false; + /** * Property name used to enable dumping the stack of all * {@linkplain Thread threads} to {@code System.out} when a timeout has occurred: {@value} * *

This behavior is disabled by default. */ - @ConfigurationParameter(defaultValue = @Value(booleanValue = false)) + @ConfigurationParameter(defaultValue = @Value(booleanValue = EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_DEFAULT)) public static final String EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME = PreInterruptCallback.THREAD_DUMP_ENABLED_PROPERTY_NAME; /** @@ -203,13 +227,19 @@ public final class Constants { @ConfigurationParameter(type = Lifecycle.class, defaultValue = @Value(stringValue = "per_method")) public static final String DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME = Lifecycle.DEFAULT_LIFECYCLE_PROPERTY_NAME; + /** + * TODO: + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final boolean PARALLEL_EXECUTION_ENABLED_DEFAULT = false; + /** * Property name used to enable parallel test execution: {@value} * *

By default, tests are executed sequentially in a single thread. * */ - @ConfigurationParameter(defaultValue = @Value(booleanValue = false)) + @ConfigurationParameter(defaultValue = @Value(booleanValue = PARALLEL_EXECUTION_ENABLED_DEFAULT)) public static final String PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME = "junit.jupiter.execution.parallel.enabled"; /** @@ -288,6 +318,12 @@ public final class Constants { public static final String PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "fixed.max-pool-size"; + /** + * TODO: + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final boolean PARALLEL_CONFIG_FIXED_SATURATE_DEFAULT = true; + /** * Property name used to disable saturation of the underlying fork-join pool * for the {@code fixed} configuration strategy: {@value} @@ -299,9 +335,15 @@ public final class Constants { *

Value must either {@code true} or {@code false}; defaults to {@code true}. * */ - @ConfigurationParameter(defaultValue = @Value(booleanValue = true)) + @ConfigurationParameter(defaultValue = @Value(booleanValue = PARALLEL_CONFIG_FIXED_SATURATE_DEFAULT)) public static final String PARALLEL_CONFIG_FIXED_SATURATE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "fixed.saturate"; + /** + * TODO: + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final double PARALLEL_CONFIG_DYNAMIC_FACTOR_DEFAULT = 1.0; + /** * Property name used to set the factor to be multiplied with the number of * available processors/cores to determine the desired parallelism for the @@ -310,7 +352,7 @@ public final class Constants { *

Value must be a positive decimal number; defaults to {@code 1}. * */ - @ConfigurationParameter(type = BigDecimal.class, defaultValue = @Value(doubleValue = 1.0)) + @ConfigurationParameter(type = BigDecimal.class, defaultValue = @Value(doubleValue = PARALLEL_CONFIG_DYNAMIC_FACTOR_DEFAULT)) public static final String PARALLEL_CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "dynamic.factor"; /** diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java index 8f31f75cf461..44a0884602f9 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java @@ -12,6 +12,7 @@ import static java.util.function.Predicate.isEqual; import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.jupiter.api.Constants.CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_DEFAULT; import static org.junit.jupiter.api.Constants.CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME; @@ -24,11 +25,15 @@ import static org.junit.jupiter.api.Constants.DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME; +import static org.junit.jupiter.api.Constants.EXTENSIONS_AUTODETECTION_ENABLED_DEFAULT; import static org.junit.jupiter.api.Constants.EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME; +import static org.junit.jupiter.api.Constants.EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_DEFAULT; import static org.junit.jupiter.api.Constants.EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME; +import static org.junit.jupiter.api.Constants.INCLUDE_ALL_EXTENSIONS_PATTERN; import static org.junit.jupiter.api.Constants.PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME; +import static org.junit.jupiter.api.Constants.PARALLEL_EXECUTION_ENABLED_DEFAULT; import static org.junit.jupiter.api.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; import static org.junit.jupiter.api.io.CleanupMode.ALWAYS; import static org.junit.jupiter.engine.config.FilteringConfigurationParameterConverter.exclude; @@ -147,7 +152,7 @@ public Predicate> getFilterForAutoDetectedExtensions( private String getExtensionAutoDetectionIncludePattern() { return configurationParameters.get(EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME) // - .orElse(ClassNamePatternFilterUtils.ALL_PATTERN); + .orElse(INCLUDE_ALL_EXTENSIONS_PATTERN); } private String getExtensionAutoDetectionExcludePattern() { @@ -168,22 +173,26 @@ public Optional getRawConfigurationParameter(String key, @Override public boolean isParallelExecutionEnabled() { - return configurationParameters.getBoolean(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME).orElse(false); + return configurationParameters.getBoolean(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME) // + .orElse(PARALLEL_EXECUTION_ENABLED_DEFAULT); } @Override public boolean isClosingStoredAutoCloseablesEnabled() { - return configurationParameters.getBoolean(CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME).orElse(true); + return configurationParameters.getBoolean(CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME) // + .orElse(CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_DEFAULT); } @Override public boolean isExtensionAutoDetectionEnabled() { - return configurationParameters.getBoolean(EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME).orElse(false); + return configurationParameters.getBoolean(EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME) // + .orElse(EXTENSIONS_AUTODETECTION_ENABLED_DEFAULT); } @Override public boolean isThreadDumpOnTimeoutEnabled() { - return configurationParameters.getBoolean(EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME).orElse(false); + return configurationParameters.getBoolean(EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME) // + .orElse(EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_DEFAULT); } @Override diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java index dc9903ad3f7b..9500bc2b6846 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java @@ -48,7 +48,7 @@ public ParallelExecutionConfiguration createConfiguration(ConfigurationParameter Integer::valueOf).orElse(parallelism + 256); boolean saturate = configurationParameters.get(CONFIG_FIXED_SATURATE_PROPERTY_NAME, - Boolean::valueOf).orElse(true); + Boolean::valueOf).orElse(CONFIG_FIXED_SATURATE_DEFAULT); return new DefaultParallelExecutionConfiguration(parallelism, parallelism, maxPoolSize, parallelism, KEEP_ALIVE_SECONDS, __ -> saturate); @@ -64,7 +64,7 @@ public ParallelExecutionConfiguration createConfiguration(ConfigurationParameter @Override public ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters) { BigDecimal factor = configurationParameters.get(CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME, - BigDecimal::new).orElse(BigDecimal.ONE); + BigDecimal::new).orElse(CONFIG_DYNAMIC_FACTOR_DEFAULT); Preconditions.condition(factor.compareTo(BigDecimal.ZERO) > 0, () -> "Factor '%s' specified via configuration parameter '%s' must be greater than 0".formatted(factor, @@ -147,6 +147,12 @@ public ParallelExecutionConfiguration createConfiguration(ConfigurationParameter @API(status = MAINTAINED, since = "1.13.3") public static final String CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME = "fixed.max-pool-size"; + /** + * TODO: + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final boolean CONFIG_FIXED_SATURATE_DEFAULT = true; + /** * Property name used to disable saturation of the underlying fork-join pool * for the {@link #FIXED} configuration strategy. @@ -163,6 +169,12 @@ public ParallelExecutionConfiguration createConfiguration(ConfigurationParameter @API(status = MAINTAINED, since = "1.13.3") public static final String CONFIG_FIXED_SATURATE_PROPERTY_NAME = "fixed.saturate"; + /** + * TODO: + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final BigDecimal CONFIG_DYNAMIC_FACTOR_DEFAULT = BigDecimal.ONE; + /** * Property name of the factor used to determine the desired parallelism for the * {@link #DYNAMIC} configuration strategy. diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/ConstantTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/ConstantTests.java index d2fd4395d8c0..d06ed3e13a72 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/ConstantTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/ConstantTests.java @@ -25,6 +25,7 @@ void constantsAreConsistent() { assertThat(Constants.PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME).isEqualTo(Constants.PARALLEL_CONFIG_PREFIX + DefaultParallelExecutionConfigurationStrategy.CONFIG_STRATEGY_PROPERTY_NAME); + assertThat(Constants.PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME).isEqualTo(Constants.PARALLEL_CONFIG_PREFIX + DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_PARALLELISM_PROPERTY_NAME); assertThat(Constants.PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME).isEqualTo( @@ -32,11 +33,18 @@ void constantsAreConsistent() { + DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME); assertThat(Constants.PARALLEL_CONFIG_FIXED_SATURATE_PROPERTY_NAME).isEqualTo(Constants.PARALLEL_CONFIG_PREFIX + DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_SATURATE_PROPERTY_NAME); + assertThat(Constants.PARALLEL_CONFIG_FIXED_SATURATE_DEFAULT).isEqualTo( + DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_SATURATE_DEFAULT); + assertThat(Constants.PARALLEL_CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME).isEqualTo(Constants.PARALLEL_CONFIG_PREFIX + DefaultParallelExecutionConfigurationStrategy.CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME); assertThat(Constants.PARALLEL_CONFIG_CUSTOM_CLASS_PROPERTY_NAME).isEqualTo(Constants.PARALLEL_CONFIG_PREFIX + DefaultParallelExecutionConfigurationStrategy.CONFIG_CUSTOM_CLASS_PROPERTY_NAME); + assertThat(Constants.PARALLEL_CONFIG_DYNAMIC_FACTOR_DEFAULT).isEqualTo( + DefaultParallelExecutionConfigurationStrategy.CONFIG_DYNAMIC_FACTOR_DEFAULT.doubleValue()); assertThat(Constants.DEACTIVATE_ALL_CONDITIONS_PATTERN).isEqualTo(ClassNamePatternFilterUtils.ALL_PATTERN); + assertThat(Constants.INCLUDE_ALL_EXTENSIONS_PATTERN).isEqualTo(ClassNamePatternFilterUtils.ALL_PATTERN); + } } From a9f104aa26c6114158e3a98183da57c9acb3a787 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 15 Aug 2026 17:30:33 +0200 Subject: [PATCH 064/117] Extract defaults for Launcher Constants --- .../platform/launcher/LauncherConstants.java | 49 ++++++++++++++++--- .../core/EngineExecutionOrchestrator.java | 11 +++-- .../launcher/core/LauncherFactory.java | 4 +- ...reamInterceptingTestExecutionListener.java | 8 ++- 4 files changed, 59 insertions(+), 13 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 9fc74b42932d..5232bcf6d1b6 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 @@ -32,6 +32,12 @@ @API(status = STABLE, since = "1.7") public class LauncherConstants { + /** + * TODO: + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final boolean CAPTURE_STDOUT_DEFAULT = false; + /** * Property name used to enable capturing output to {@link System#out}: * {@value} @@ -47,9 +53,15 @@ public class LauncherConstants { * @see ReportEntry * @see TestExecutionListener#reportingEntryPublished(TestIdentifier, ReportEntry) */ - @ConfigurationParameter(defaultValue = @Value(booleanValue = false)) + @ConfigurationParameter(defaultValue = @Value(booleanValue = CAPTURE_STDOUT_DEFAULT)) public static final String CAPTURE_STDOUT_PROPERTY_NAME = "junit.platform.output.capture.stdout"; + /** + * TODO: + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final boolean CAPTURE_STDERR_DEFAULT = false; + /** * Property name used to enable capturing output to {@link System#err}: * {@value} @@ -65,7 +77,7 @@ public class LauncherConstants { * @see ReportEntry * @see TestExecutionListener#reportingEntryPublished(TestIdentifier, ReportEntry) */ - @ConfigurationParameter(defaultValue = @Value(booleanValue = false)) + @ConfigurationParameter(defaultValue = @Value(booleanValue = CAPTURE_STDERR_DEFAULT)) public static final String CAPTURE_STDERR_PROPERTY_NAME = "junit.platform.output.capture.stderr"; /** @@ -158,6 +170,12 @@ public class LauncherConstants { */ public static final String DEACTIVATE_ALL_LISTENERS_PATTERN = ClassNamePatternFilterUtils.ALL_PATTERN; + /** + * TODO: + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final boolean ENABLE_LAUNCHER_INTERCEPTORS_DEFAULT = false; + /** * Property name used to enable support for * {@link LauncherInterceptor} instances to be registered via the @@ -173,9 +191,15 @@ public class LauncherConstants { * @see LauncherInterceptor */ @API(status = MAINTAINED, since = "1.13.3") - @ConfigurationParameter(defaultValue = @Value(booleanValue = false)) + @ConfigurationParameter(defaultValue = @Value(booleanValue = ENABLE_LAUNCHER_INTERCEPTORS_DEFAULT)) public static final String ENABLE_LAUNCHER_INTERCEPTORS = "junit.platform.launcher.interceptors.enabled"; + /** + * TODO: + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final boolean DRY_RUN_DEFAULT = false; + /** * Property name used to enable dry-run mode for test execution. * @@ -190,9 +214,15 @@ public class LauncherConstants { *

Value must be either {@code true} or {@code false}; defaults to {@code false}. */ @API(status = MAINTAINED, since = "1.13.3") - @ConfigurationParameter(defaultValue = @Value(booleanValue = false)) + @ConfigurationParameter(defaultValue = @Value(booleanValue = DRY_RUN_DEFAULT)) public static final String DRY_RUN_PROPERTY_NAME = "junit.platform.execution.dryRun.enabled"; + /** + * TODO: + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final boolean STACKTRACE_PRUNING_ENABLED_DEFAULT = true; + /** * Property name used to enable or disable stack trace pruning. * @@ -201,7 +231,7 @@ public class LauncherConstants { * @see org.junit.platform.launcher.core.EngineExecutionOrchestrator */ @API(status = MAINTAINED, since = "1.13.3") - @ConfigurationParameter(defaultValue = @Value(booleanValue = true)) + @ConfigurationParameter(defaultValue = @Value(booleanValue = STACKTRACE_PRUNING_ENABLED_DEFAULT)) public static final String STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME = "junit.platform.stacktrace.pruning.enabled"; /** @@ -258,6 +288,7 @@ public class LauncherConstants { * @since 1.13 * @see org.junit.platform.engine.DiscoveryIssue.Severity */ + // TODO: Reference enum values as strings? @API(status = EXPERIMENTAL, since = "6.0") @ConfigurationParameter(type = Severity.class, defaultValue = @Value(stringValue = "error")) public static final String CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME = "junit.platform.discovery.issue.severity.critical"; @@ -285,6 +316,12 @@ public class LauncherConstants { @ConfigurationParameter(/*type = LauncherPhase.class,*/ defaultValue = @Value(stringValue = "discovery")) public static final String DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME = "junit.platform.discovery.issue.failure.phase"; + /** + * TODO: + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final boolean MEMORY_CLEANUP_ENABLED_DEFAULT = false; + /** * Property name used to enable the experimental memory cleanup * mode. @@ -309,7 +346,7 @@ public class LauncherConstants { * @see #MEMORY_CLEANUP_EXCLUDED_ENGINES_PROPERTY_NAME */ @API(status = EXPERIMENTAL, since = "6.1") - @ConfigurationParameter(defaultValue = @Value(booleanValue = false)) + @ConfigurationParameter(defaultValue = @Value(booleanValue = MEMORY_CLEANUP_ENABLED_DEFAULT)) public static final String MEMORY_CLEANUP_ENABLED_PROPERTY_NAME = "junit.platform.execution.memory.cleanup.enabled"; /** 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 7a00d4f4512c..8958068debc5 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 @@ -11,7 +11,10 @@ package org.junit.platform.launcher.core; import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.launcher.LauncherConstants.DRY_RUN_DEFAULT; import static org.junit.platform.launcher.LauncherConstants.DRY_RUN_PROPERTY_NAME; +import static org.junit.platform.launcher.LauncherConstants.MEMORY_CLEANUP_ENABLED_DEFAULT; +import static org.junit.platform.launcher.LauncherConstants.MEMORY_CLEANUP_ENABLED_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME; import static org.junit.platform.launcher.core.LauncherPhase.getDiscoveryIssueFailurePhase; @@ -33,7 +36,6 @@ import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; -import org.junit.platform.launcher.LauncherConstants; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; @@ -114,7 +116,8 @@ private void execute(InternalTestPlan internalTestPlan, EngineExecutionListener } private Boolean isDryRun(InternalTestPlan internalTestPlan) { - return internalTestPlan.getConfigurationParameters().getBoolean(DRY_RUN_PROPERTY_NAME).orElse(false); + return internalTestPlan.getConfigurationParameters().getBoolean(DRY_RUN_PROPERTY_NAME) // + .orElse(DRY_RUN_DEFAULT); } private void dryRun(TestPlan testPlan, TestExecutionListener listener) { @@ -153,8 +156,8 @@ private static EngineExecutionListener buildEngineExecutionListener( private static boolean isMemoryCleanupEnabled(TestPlan testPlan) { return testPlan.getConfigurationParameters() // - .getBoolean(LauncherConstants.MEMORY_CLEANUP_ENABLED_PROPERTY_NAME) // - .orElse(false); + .getBoolean(MEMORY_CLEANUP_ENABLED_PROPERTY_NAME) // + .orElse(MEMORY_CLEANUP_ENABLED_DEFAULT); } private void withInterceptedStreams(ConfigurationParameters configurationParameters, diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java index 3c7472cc4190..eaab11cbedc1 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java @@ -13,6 +13,7 @@ import static org.apiguardian.api.API.Status.STABLE; import static org.junit.platform.launcher.LauncherConstants.DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.ENABLE_LAUNCHER_INTERCEPTORS; +import static org.junit.platform.launcher.LauncherConstants.ENABLE_LAUNCHER_INTERCEPTORS_DEFAULT; import java.util.ArrayList; import java.util.LinkedHashSet; @@ -150,7 +151,8 @@ private static DefaultLauncher createDefaultLauncher(LauncherConfig config, private static List collectLauncherInterceptors( LauncherConfigurationParameters configurationParameters) { List interceptors = new ArrayList<>(); - if (configurationParameters.getBoolean(ENABLE_LAUNCHER_INTERCEPTORS).orElse(false)) { + if (configurationParameters.getBoolean(ENABLE_LAUNCHER_INTERCEPTORS) // + .orElse(ENABLE_LAUNCHER_INTERCEPTORS_DEFAULT)) { ServiceLoaderRegistry.load(LauncherInterceptor.class).forEach(interceptors::add); } interceptors.add(ClasspathAlignmentCheckingLauncherInterceptor.INSTANCE); diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListener.java index b79c93acc5ea..beabacbadc2f 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListener.java @@ -12,7 +12,9 @@ import static org.junit.platform.launcher.LauncherConstants.CAPTURE_MAX_BUFFER_DEFAULT; import static org.junit.platform.launcher.LauncherConstants.CAPTURE_MAX_BUFFER_PROPERTY_NAME; +import static org.junit.platform.launcher.LauncherConstants.CAPTURE_STDERR_DEFAULT; import static org.junit.platform.launcher.LauncherConstants.CAPTURE_STDERR_PROPERTY_NAME; +import static org.junit.platform.launcher.LauncherConstants.CAPTURE_STDOUT_DEFAULT; import static org.junit.platform.launcher.LauncherConstants.CAPTURE_STDOUT_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.STDERR_REPORT_ENTRY_KEY; import static org.junit.platform.launcher.LauncherConstants.STDOUT_REPORT_ENTRY_KEY; @@ -42,8 +44,10 @@ class StreamInterceptingTestExecutionListener implements EagerTestExecutionListe static Optional create(ConfigurationParameters configurationParameters, BiConsumer reporter) { - boolean captureStdout = configurationParameters.getBoolean(CAPTURE_STDOUT_PROPERTY_NAME).orElse(false); - boolean captureStderr = configurationParameters.getBoolean(CAPTURE_STDERR_PROPERTY_NAME).orElse(false); + boolean captureStdout = configurationParameters.getBoolean(CAPTURE_STDOUT_PROPERTY_NAME) // + .orElse(CAPTURE_STDOUT_DEFAULT); + boolean captureStderr = configurationParameters.getBoolean(CAPTURE_STDERR_PROPERTY_NAME) // + .orElse(CAPTURE_STDERR_DEFAULT); if (!captureStdout && !captureStderr) { return Optional.empty(); } From de709d0bc8e358af0e6914d32f13758c1acfc7a8 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 15 Aug 2026 17:37:07 +0200 Subject: [PATCH 065/117] Document defaults for Launcher Constants --- .../junit/platform/launcher/LauncherConstants.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 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 5232bcf6d1b6..88e487230ff9 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 @@ -33,7 +33,7 @@ public class LauncherConstants { /** - * TODO: + * Default value for {@value #CAPTURE_STDOUT_PROPERTY_NAME} is {@value}. */ @API(status = MAINTAINED, since = "6.2.0") public static final boolean CAPTURE_STDOUT_DEFAULT = false; @@ -57,7 +57,7 @@ public class LauncherConstants { public static final String CAPTURE_STDOUT_PROPERTY_NAME = "junit.platform.output.capture.stdout"; /** - * TODO: + * Default value for {@value #CAPTURE_STDERR_PROPERTY_NAME} is {@value}. */ @API(status = MAINTAINED, since = "6.2.0") public static final boolean CAPTURE_STDERR_DEFAULT = false; @@ -82,7 +82,7 @@ public class LauncherConstants { /** * Default maximum number of bytes for buffering to use per thread and - * output type if output capturing is enabled. + * output type if output capturing is enabled is {@value}. * * @see #CAPTURE_MAX_BUFFER_PROPERTY_NAME */ @@ -171,7 +171,7 @@ public class LauncherConstants { public static final String DEACTIVATE_ALL_LISTENERS_PATTERN = ClassNamePatternFilterUtils.ALL_PATTERN; /** - * TODO: + * Default value for {@value #ENABLE_LAUNCHER_INTERCEPTORS} is {@value}. */ @API(status = MAINTAINED, since = "6.2.0") public static final boolean ENABLE_LAUNCHER_INTERCEPTORS_DEFAULT = false; @@ -195,7 +195,7 @@ public class LauncherConstants { public static final String ENABLE_LAUNCHER_INTERCEPTORS = "junit.platform.launcher.interceptors.enabled"; /** - * TODO: + * Default value for {@value #DRY_RUN_PROPERTY_NAME} is {@value}. */ @API(status = MAINTAINED, since = "6.2.0") public static final boolean DRY_RUN_DEFAULT = false; @@ -218,7 +218,7 @@ public class LauncherConstants { public static final String DRY_RUN_PROPERTY_NAME = "junit.platform.execution.dryRun.enabled"; /** - * TODO: + * Default value for {@value #STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME} is {@value}. */ @API(status = MAINTAINED, since = "6.2.0") public static final boolean STACKTRACE_PRUNING_ENABLED_DEFAULT = true; @@ -317,7 +317,7 @@ public class LauncherConstants { public static final String DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME = "junit.platform.discovery.issue.failure.phase"; /** - * TODO: + * Default value for {@value #MEMORY_CLEANUP_ENABLED_PROPERTY_NAME} is {@value}. */ @API(status = MAINTAINED, since = "6.2.0") public static final boolean MEMORY_CLEANUP_ENABLED_DEFAULT = false; From d0dbadf97cd4e5db2efc69cae64828cbd1f78a5c Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 15 Aug 2026 17:37:55 +0200 Subject: [PATCH 066/117] Document defaults for Vintage Constants --- .../src/main/java/org/junit/vintage/engine/Constants.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java index ff6826107962..dbe0d03dc2e5 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java @@ -28,7 +28,7 @@ public final class Constants { /** - * TODO: + * Default value for {@value #PARALLEL_EXECUTION_ENABLED} is {@value}. */ @API(status = MAINTAINED, since = "6.2.0") public static final boolean PARALLEL_EXECUTION_ENABLED_DEFAULT = false; @@ -59,7 +59,7 @@ public final class Constants { public static final String PARALLEL_POOL_SIZE = "junit.vintage.execution.parallel.pool-size"; /** - * TODO: + * Default value for {@value #PARALLEL_CLASS_EXECUTION} is {@value}. */ @API(status = MAINTAINED, since = "6.2.0") public static final boolean PARALLEL_CLASS_EXECUTION_DEFAULT = false; @@ -78,7 +78,7 @@ public final class Constants { public static final String PARALLEL_CLASS_EXECUTION = "junit.vintage.execution.parallel.classes"; /** - * TODO: + * Default value for {@value #PARALLEL_METHOD_EXECUTION} is {@value}. */ @API(status = MAINTAINED, since = "6.2.0") public static final boolean PARALLEL_METHOD_EXECUTION_DEFAULT = false; @@ -97,7 +97,7 @@ public final class Constants { public static final String PARALLEL_METHOD_EXECUTION = "junit.vintage.execution.parallel.methods"; /** - * TODO: + * Default value for {@value #DISCOVERY_ISSUE_REPORTING_ENABLED_PROPERTY_NAME} is {@value}. */ @API(status = MAINTAINED, since = "6.2.0") public static final boolean DISCOVERY_ISSUE_REPORTING_ENABLED_DEFAULT = true; From d43b0407b49b56c79f1dab67fbbf863651388ca0 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 15 Aug 2026 17:40:29 +0200 Subject: [PATCH 067/117] Document defaults for Jupiter Constants --- .../main/java/org/junit/jupiter/api/Constants.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java index 2dfaef20236d..2295968913e1 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java @@ -42,7 +42,7 @@ public final class Constants { /** - * TODO: + * Default value for {@value #EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME} is {@value}. */ @API(status = MAINTAINED, since = "6.2.0") public static final String INCLUDE_ALL_EXTENSIONS_PATTERN = "*"; @@ -120,7 +120,7 @@ public final class Constants { public static final String EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.exclude"; /** - * TODO: + * Default value for {@value #EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME} is {@value}. */ @API(status = MAINTAINED, since = "6.2.0") public static final boolean EXTENSIONS_AUTODETECTION_ENABLED_DEFAULT = false; @@ -136,7 +136,7 @@ public final class Constants { public static final String EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.enabled"; /** - * TODO: + * Default value for {@value #CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME} is {@value}. */ @API(status = MAINTAINED, since = "6.2.0") public static final boolean CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_DEFAULT = true; @@ -204,7 +204,7 @@ public final class Constants { public static final String DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME = DisplayNameGenerator.DEFAULT_GENERATOR_PROPERTY_NAME; /** - * TODO: + * Default value for {@value #EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME} is {@value}. */ @API(status = MAINTAINED, since = "6.2.0") public static final boolean EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_DEFAULT = false; @@ -228,7 +228,7 @@ public final class Constants { public static final String DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME = Lifecycle.DEFAULT_LIFECYCLE_PROPERTY_NAME; /** - * TODO: + * Default value for {@value #PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME} is {@value}. */ @API(status = MAINTAINED, since = "6.2.0") public static final boolean PARALLEL_EXECUTION_ENABLED_DEFAULT = false; @@ -319,7 +319,7 @@ public final class Constants { + "fixed.max-pool-size"; /** - * TODO: + * Default value for {@value #PARALLEL_CONFIG_FIXED_SATURATE_PROPERTY_NAME} is {@value}. */ @API(status = MAINTAINED, since = "6.2.0") public static final boolean PARALLEL_CONFIG_FIXED_SATURATE_DEFAULT = true; @@ -339,7 +339,7 @@ public final class Constants { public static final String PARALLEL_CONFIG_FIXED_SATURATE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "fixed.saturate"; /** - * TODO: + * Default value for {@value #PARALLEL_CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME} is {@value}. */ @API(status = MAINTAINED, since = "6.2.0") public static final double PARALLEL_CONFIG_DYNAMIC_FACTOR_DEFAULT = 1.0; From 43b8a69e74e28d6d07ffef7365ef19a5f6daaf30 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 16 Aug 2026 01:13:49 +0200 Subject: [PATCH 068/117] Reference enum values as strings --- .../java/org/junit/jupiter/api/Constants.java | 45 ++++++++++--------- .../org/junit/jupiter/api/TestInstance.java | 9 +++- .../java/org/junit/jupiter/api/Timeout.java | 28 ++++++++---- .../api/extension/PreInterruptCallback.java | 6 +++ .../TestInstantiationAwareExtension.java | 10 +++-- .../org/junit/jupiter/api/io/TempDir.java | 8 +++- .../junit/jupiter/api/parallel/Execution.java | 8 +++- .../config/DefaultJupiterConfiguration.java | 24 +++++----- .../EnumConfigurationParameterConverter.java | 26 +++++++---- .../engine/extension/TimeoutExtension.java | 10 ++++- ...arallelExecutionConfigurationStrategy.java | 22 ++++++++- ...ierarchicalTestExecutorServiceFactory.java | 12 +++-- .../platform/launcher/LauncherConstants.java | 14 +++--- .../core/DiscoveryIssueCollector.java | 6 ++- .../org/junit/jupiter/api/ConstantTests.java | 5 +++ 15 files changed, 163 insertions(+), 70 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java index 2295968913e1..cafe41e317fa 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java @@ -203,19 +203,13 @@ public final class Constants { @ConfigurationParameter(type = DisplayNameGenerator.class, defaultValue = @Value(classValue = Standard.class)) public static final String DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME = DisplayNameGenerator.DEFAULT_GENERATOR_PROPERTY_NAME; - /** - * Default value for {@value #EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME} is {@value}. - */ - @API(status = MAINTAINED, since = "6.2.0") - public static final boolean EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_DEFAULT = false; - /** * Property name used to enable dumping the stack of all * {@linkplain Thread threads} to {@code System.out} when a timeout has occurred: {@value} * *

This behavior is disabled by default. */ - @ConfigurationParameter(defaultValue = @Value(booleanValue = EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_DEFAULT)) + @ConfigurationParameter(defaultValue = @Value(booleanValue = PreInterruptCallback.THREAD_DUMP_ENABLED_DEFAULT)) public static final String EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME = PreInterruptCallback.THREAD_DUMP_ENABLED_PROPERTY_NAME; /** @@ -223,8 +217,7 @@ public final class Constants { * * @see Lifecycle#DEFAULT_LIFECYCLE_PROPERTY_NAME */ - // TODO: Reference enum values as strings? - @ConfigurationParameter(type = Lifecycle.class, defaultValue = @Value(stringValue = "per_method")) + @ConfigurationParameter(type = Lifecycle.class, defaultValue = @Value(stringValue = Lifecycle.DEFAULT_LIFECYCLE_PATTERN_DEFAULT)) public static final String DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME = Lifecycle.DEFAULT_LIFECYCLE_PROPERTY_NAME; /** @@ -247,8 +240,7 @@ public final class Constants { * * @see Execution#DEFAULT_EXECUTION_MODE_PROPERTY_NAME */ - // TODO: Reference enum values as strings? - @ConfigurationParameter(type = ExecutionMode.class, defaultValue = @Value(stringValue = "same_thread")) + @ConfigurationParameter(type = ExecutionMode.class, defaultValue = @Value(stringValue = Execution.DEFAULT_EXECUTION_MODE_DEFAULT)) public static final String DEFAULT_EXECUTION_MODE_PROPERTY_NAME = Execution.DEFAULT_EXECUTION_MODE_PROPERTY_NAME; /** @@ -267,6 +259,12 @@ public final class Constants { @API(status = INTERNAL, since = "6.1") public static final String PARALLEL_CONFIG_PREFIX = "junit.jupiter.execution.parallel.config."; + /** + * Default value for {@value #PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME} is {@value}. + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final String PARALLEL_CONFIG_EXECUTOR_SERVICE_DEFAULT = "fork_join_pool"; + /** * Property name used to determine the desired parallel executor service * type: {@value} @@ -275,12 +273,18 @@ public final class Constants { * ignoring case. * */ + // TODO: Move PARALLEL_CONFIG_EXECUTOR_SERVICE_DEFAULT to ParallelExecutorServiceType // TODO: ParallelExecutorServiceType is not part of the public API - // TODO: Reference enum values as strings? - @ConfigurationParameter(/* type = ParallelExecutorServiceType.class,*/ defaultValue = @Value(stringValue = "fork_join_pool")) + @ConfigurationParameter(/* type = ParallelExecutorServiceType.class,*/ defaultValue = @Value(stringValue = PARALLEL_CONFIG_EXECUTOR_SERVICE_DEFAULT)) public static final String PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "executor-service"; + /** + * Default value for {@value #PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME} is {@value}. + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final String PARALLEL_CONFIG_STRATEGY_DEFAULT = "dynamic"; + /** * Property name used to select the parallel execution configuration * strategy: {@value} @@ -289,9 +293,9 @@ public final class Constants { * {@code custom}. * */ + // TODO: Move PARALLEL_CONFIG_STRATEGY_DEFAULT to ParallelExecutorServiceType // TODO: DefaultParallelExecutionConfigurationStrategy is not part of the public API - // TODO: Reference enum values as strings? - @ConfigurationParameter(/* type = DefaultParallelExecutionConfigurationStrategy.class,*/ defaultValue = @Value(stringValue = "dynamic")) + @ConfigurationParameter(/* type = DefaultParallelExecutionConfigurationStrategy.class,*/ defaultValue = @Value(stringValue = PARALLEL_CONFIG_STRATEGY_DEFAULT)) public static final String PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "strategy"; /** @@ -469,7 +473,7 @@ public final class Constants { * @see Timeout#TIMEOUT_MODE_PROPERTY_NAME */ // TODO: TimeoutDuration is not part of the public API - @ConfigurationParameter /*(type = TimeoutDuration.class)*/ + @ConfigurationParameter(/*type = TimeoutDuration.class,*/ defaultValue = @Value(stringValue = Timeout.TIMEOUT_MODE_DEFAULT)) public static final String TIMEOUT_MODE_PROPERTY_NAME = Timeout.TIMEOUT_MODE_PROPERTY_NAME; /** @@ -494,8 +498,7 @@ public final class Constants { * @see Timeout * @see Timeout.ThreadMode */ - // TODO: Reference enum values as strings? - @ConfigurationParameter(type = Timeout.ThreadMode.class, defaultValue = @Value(stringValue = "same_thread")) + @ConfigurationParameter(type = Timeout.ThreadMode.class, defaultValue = @Value(stringValue = Timeout.DEFAULT_TIMEOUT_THREAD_MODE_DEFAULT)) public static final String DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME = Timeout.DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME; /** @@ -514,8 +517,7 @@ public final class Constants { * * @see TempDir#DEFAULT_CLEANUP_MODE_PROPERTY_NAME */ - // TODO: Reference enum values as strings? - @ConfigurationParameter(type = CleanupMode.class, defaultValue = @Value(stringValue = "always")) + @ConfigurationParameter(type = CleanupMode.class, defaultValue = @Value(stringValue = TempDir.DEFAULT_TEMP_DIR_CLEANUP_MODE_DEFAULT)) public static final String DEFAULT_TEMP_DIR_CLEANUP_MODE_PROPERTY_NAME = TempDir.DEFAULT_CLEANUP_MODE_PROPERTY_NAME; /** @@ -535,8 +537,7 @@ public final class Constants { * * @see org.junit.jupiter.api.extension.TestInstantiationAwareExtension */ - // TODO: Reference enum values as strings? - @ConfigurationParameter(type = ExtensionContextScope.class, defaultValue = @Value(stringValue = "default")) + @ConfigurationParameter(type = ExtensionContextScope.class, defaultValue = @Value(stringValue = ExtensionContextScope.DEFAULT_SCOPE_DEFAULT)) public static final String DEFAULT_TEST_CLASS_INSTANCE_CONSTRUCTION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME = ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME; private Constants() { diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java index 172e984dac87..18931484d86a 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java @@ -10,6 +10,7 @@ package org.junit.jupiter.api; +import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; @@ -106,6 +107,12 @@ enum Lifecycle { */ PER_METHOD; + /** + * Default value for {@value #DEFAULT_LIFECYCLE_PROPERTY_NAME} is {@value}. + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final String DEFAULT_LIFECYCLE_PATTERN_DEFAULT = "per_method"; + /** * Property name used to set the default test instance lifecycle mode: * {@value} @@ -115,7 +122,7 @@ enum Lifecycle { *

Supported values include names of enum constants defined in * {@link org.junit.jupiter.api.TestInstance.Lifecycle}, ignoring case. * - *

If not specified, the default is "per_method" which corresponds to + *

If not specified, the default is {@value #DEFAULT_LIFECYCLE_PATTERN_DEFAULT} which corresponds to * {@code @TestInstance(Lifecycle.PER_METHOD)}. * * @since 5.0 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java index 2ebd7ff07224..62b3e5c0dd2b 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java @@ -297,6 +297,12 @@ @API(status = STABLE, since = "5.9") String DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.afterall.method.default"; + /** + * Default value for {@value #TIMEOUT_MODE_PROPERTY_NAME} is {@value}. + */ + @API(status = MAINTAINED, since = "6.2.0") + String TIMEOUT_MODE_DEFAULT = "enabled"; + /** * Property name used to configure whether timeouts are applied to tests: * {@value}. @@ -304,20 +310,26 @@ *

The value of this property will be used to toggle whether * {@link Timeout @Timeout} is applied to tests.

* - *

Supported timeout mode values (case insensitive):

+ *

Supported timeout mode values (case-insensitive):

*
    - *
  • {@code ENABLED}: enables timeouts - *
  • {@code DISABLED}: disables timeouts - *
  • {@code DISABLED_ON_DEBUG}: disables timeouts while debugging + *
  • {@code enabled}: enables timeouts + *
  • {@code disabled}: disables timeouts + *
  • {@code disabled_on_debug}: disables timeouts while debugging *
* - *

If not specified, the default is {@code ENABLED}. + *

If not specified, the default is {@value #TIMEOUT_MODE_DEFAULT}. * * @since 5.6 */ @API(status = STABLE, since = "5.9") String TIMEOUT_MODE_PROPERTY_NAME = "junit.jupiter.execution.timeout.mode"; + /** + * Default value for {@value #DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME} is {@value}. + */ + @API(status = MAINTAINED, since = "6.2.0") + String DEFAULT_TIMEOUT_THREAD_MODE_DEFAULT = "same_thread"; + /** * Property name used to set the default thread mode for all testable and * lifecycle methods: {@value}. @@ -326,9 +338,9 @@ * {@link Timeout @Timeout} annotation present on the method or on an * enclosing test class (for testable methods). * - *

The supported values are {@code SAME_THREAD} or - * {@code SEPARATE_THREAD}, ignoring case. If none is provided, - * {@code SAME_THREAD} is used as default. + *

The supported values are {@code same_thread} or + * {@code separate_thread}, ignoring case. If none is provided, + * {@value #DEFAULT_TIMEOUT_THREAD_MODE_DEFAULT} is used as default. * * @since 5.9 * @see #threadMode() diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/PreInterruptCallback.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/PreInterruptCallback.java index 12898eaed4fc..f00ad681451d 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/PreInterruptCallback.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/PreInterruptCallback.java @@ -31,6 +31,12 @@ @API(status = MAINTAINED, since = "5.13.3") public interface PreInterruptCallback extends Extension { + /** + * Default value for {@value #THREAD_DUMP_ENABLED_PROPERTY_NAME} is {@value}. + */ + @API(status = MAINTAINED, since = "6.2.0") + boolean THREAD_DUMP_ENABLED_DEFAULT = false; + /** * Property name used to enable dumping the stack of all * {@linkplain Thread threads} to {@code System.out} when a timeout has occurred. diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java index ff3c609ccdc6..2d0c2818d6b0 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java @@ -173,15 +173,19 @@ enum ExtensionContextScope { */ TEST_METHOD; + /** + * Default value for {@value #DEFAULT_SCOPE_PROPERTY_NAME} is {@value}. + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final String DEFAULT_SCOPE_DEFAULT = "default"; + /** * Property name used to set the default extension context scope: {@value} * *

Supported Values

* *

Supported values include names of enum constants defined in this - * class, ignoring case. - * - * @see #DEFAULT + * class, ignoring case. Defaults to {@value #DEFAULT_SCOPE_DEFAULT}. */ public static final String DEFAULT_SCOPE_PROPERTY_NAME = "junit.jupiter.extensions.testinstantiation.extensioncontextscope.default"; diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java index 63c762f92fec..b46a267f987b 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java @@ -132,13 +132,19 @@ @API(status = MAINTAINED, since = "5.13.3") Class factory() default TempDirFactory.class; + /** + * Default value for {@value #DEFAULT_CLEANUP_MODE_PROPERTY_NAME} is {@value}. + */ + @API(status = MAINTAINED, since = "6.2.0") + String DEFAULT_TEMP_DIR_CLEANUP_MODE_DEFAULT = "always"; + /** * Property name used to configure the default {@link CleanupMode}: {@value} * *

Supported values include names of enum constants defined in * {@link CleanupMode}, ignoring case. * - *

If this configuration parameter is not set, {@link CleanupMode#ALWAYS} + *

If this configuration parameter is not set, {@value #DEFAULT_TEMP_DIR_CLEANUP_MODE_DEFAULT} * will be used as the default. * * @since 5.9 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java index 94426bde372d..e3efabe4148d 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java @@ -61,6 +61,12 @@ @Inherited public @interface Execution { + /** + * Default value for {@value #DEFAULT_EXECUTION_MODE_PROPERTY_NAME} is {@value}. + */ + @API(status = MAINTAINED, since = "6.2.0") + String DEFAULT_EXECUTION_MODE_DEFAULT = "same_thread"; + /** * Property name used to set the default test execution mode: {@value} * @@ -71,7 +77,7 @@ *

Supported values include names of enum constants defined in * {@link ExecutionMode}, ignoring case. * - *

If not specified, the default is "same_thread" which corresponds to + *

If not specified, the default is {@link #DEFAULT_EXECUTION_MODE_DEFAULT} which corresponds to * {@code @Execution(ExecutionMode.SAME_THREAD)}. * * @since 5.4 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java index 44a0884602f9..9ba02cbb7660 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java @@ -29,13 +29,11 @@ import static org.junit.jupiter.api.Constants.EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME; -import static org.junit.jupiter.api.Constants.EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_DEFAULT; import static org.junit.jupiter.api.Constants.EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.INCLUDE_ALL_EXTENSIONS_PATTERN; import static org.junit.jupiter.api.Constants.PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.PARALLEL_EXECUTION_ENABLED_DEFAULT; import static org.junit.jupiter.api.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; -import static org.junit.jupiter.api.io.CleanupMode.ALWAYS; import static org.junit.jupiter.engine.config.FilteringConfigurationParameterConverter.exclude; import static org.junit.platform.engine.support.hierarchical.ParallelHierarchicalTestExecutorServiceFactory.ParallelExecutorServiceType.FORK_JOIN_POOL; import static org.junit.platform.engine.support.hierarchical.ParallelHierarchicalTestExecutorServiceFactory.ParallelExecutorServiceType.WORKER_THREAD_POOL; @@ -54,10 +52,13 @@ import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.PreInterruptCallback; import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDirDeletionStrategy; import org.junit.jupiter.api.io.TempDirFactory; +import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.platform.commons.util.ClassNamePatternFilterUtils; import org.junit.platform.commons.util.Preconditions; @@ -80,10 +81,10 @@ public class DefaultJupiterConfiguration implements JupiterConfiguration { "junit.jupiter.params.arguments.conversion.locale.format" // ); - private static final ConfigurationParameterConverter executionModeConverter = // + private static final EnumConfigurationParameterConverter executionModeConverter = // new EnumConfigurationParameterConverter<>(ExecutionMode.class, "parallel execution mode"); - private static final ConfigurationParameterConverter lifecycleConverter = // + private static final EnumConfigurationParameterConverter lifecycleConverter = // new EnumConfigurationParameterConverter<>(Lifecycle.class, "test instance lifecycle mode"); private static final ConfigurationParameterConverter displayNameGeneratorConverter = // @@ -97,7 +98,7 @@ public class DefaultJupiterConfiguration implements JupiterConfiguration { exclude(isEqual(ClassOrderer.Default.class.getName()), new InstantiatingConfigurationParameterConverter<>(ClassOrderer.class, "class orderer")); - private static final ConfigurationParameterConverter cleanupModeConverter = // + private static final EnumConfigurationParameterConverter cleanupModeConverter = // new EnumConfigurationParameterConverter<>(CleanupMode.class, "cleanup mode"); private static final InstantiatingConfigurationParameterConverter tempDirFactoryConverter = // @@ -106,7 +107,7 @@ public class DefaultJupiterConfiguration implements JupiterConfiguration { private static final InstantiatingConfigurationParameterConverter tempDirDeletionStrategyConverter = // new InstantiatingConfigurationParameterConverter<>(TempDirDeletionStrategy.class, "temp dir deletion strategy"); - private static final ConfigurationParameterConverter extensionContextScopeConverter = // + private static final EnumConfigurationParameterConverter extensionContextScopeConverter = // new EnumConfigurationParameterConverter<>(ExtensionContextScope.class, "extension context scope"); private final ConfigurationParameters configurationParameters; @@ -192,13 +193,13 @@ public boolean isExtensionAutoDetectionEnabled() { @Override public boolean isThreadDumpOnTimeoutEnabled() { return configurationParameters.getBoolean(EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME) // - .orElse(EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_DEFAULT); + .orElse(PreInterruptCallback.THREAD_DUMP_ENABLED_DEFAULT); } @Override public ExecutionMode getDefaultExecutionMode() { return executionModeConverter.getOrDefault(configurationParameters, DEFAULT_EXECUTION_MODE_PROPERTY_NAME, - ExecutionMode.SAME_THREAD); + Execution.DEFAULT_EXECUTION_MODE_DEFAULT); } @Override @@ -210,7 +211,7 @@ public ExecutionMode getDefaultClassesExecutionMode() { @Override public Lifecycle getDefaultTestInstanceLifecycle() { return lifecycleConverter.getOrDefault(configurationParameters, DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, - Lifecycle.PER_METHOD); + Lifecycle.DEFAULT_LIFECYCLE_PATTERN_DEFAULT); } @Override @@ -238,7 +239,7 @@ public Optional getDefaultTestClassOrderer() { @Override public CleanupMode getDefaultTempDirCleanupMode() { return cleanupModeConverter.getOrDefault(configurationParameters, DEFAULT_TEMP_DIR_CLEANUP_MODE_PROPERTY_NAME, - ALWAYS); + TempDir.DEFAULT_TEMP_DIR_CLEANUP_MODE_DEFAULT); } @Override @@ -255,12 +256,11 @@ public Supplier getDefaultTempDirDeletionStrategySuppli return () -> supplier.get().orElse(TempDirDeletionStrategy.Standard.INSTANCE); } - @SuppressWarnings("deprecation") @Override public ExtensionContextScope getDefaultTestInstantiationExtensionContextScope() { return extensionContextScopeConverter.getOrDefault(configurationParameters, DEFAULT_TEST_CLASS_INSTANCE_CONSTRUCTION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME, - ExtensionContextScope.DEFAULT); + ExtensionContextScope.DEFAULT_SCOPE_DEFAULT); } @Override diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java index 5762d8974367..fd3c37f2c39c 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java @@ -40,22 +40,30 @@ public EnumConfigurationParameterConverter(Class enumType, String enumDisplay @Override public Optional get(ConfigurationParameters configParams, String key) { - return configParams.get(key) // - .map(value -> convert(key, value)); + var result = configParams.get(key).map(value -> convert(key, value)); + result.ifPresent(value -> logUsageMessage(key, value)); + return result; } public Optional get(ExtensionContext extensionContext, String key) { - return extensionContext.getConfigurationParameter(key, value -> convert(key, value)); + var result = extensionContext.getConfigurationParameter(key, value -> convert(key, value)); + result.ifPresent(value -> logUsageMessage(key, value)); + return result; + } + + public E getOrDefault(ConfigurationParameters configParams, String key, String defaultValue) { + return get(configParams, key).orElseGet(() -> convert(key, defaultValue)); + } + + private void logUsageMessage(String key, E result) { + logger.config(() -> "Using %s '%s' set via the '%s' configuration parameter." // + .formatted(enumDisplayName, result, key)); } private E convert(String key, String value) { - String constantName = null; + String constantName = value.strip().toUpperCase(Locale.ROOT); try { - constantName = value.strip().toUpperCase(Locale.ROOT); - E result = Enum.valueOf(enumType, constantName); - logger.config(() -> "Using %s '%s' set via the '%s' configuration parameter.".formatted(enumDisplayName, - result, key)); - return result; + return Enum.valueOf(enumType, constantName); } catch (Exception ex) { throw new JUnitException("Invalid %s '%s' set via the '%s' configuration parameter.".formatted( diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExtension.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExtension.java index 5b0789838fe1..3390b99c0981 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExtension.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExtension.java @@ -10,11 +10,12 @@ package org.junit.jupiter.engine.extension; -import static org.junit.jupiter.api.Timeout.ThreadMode.SAME_THREAD; +import static org.junit.jupiter.api.Timeout.DEFAULT_TIMEOUT_THREAD_MODE_DEFAULT; import static org.junit.jupiter.api.extension.PreInterruptCallback.THREAD_DUMP_ENABLED_PROPERTY_NAME; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; +import java.util.Locale; import java.util.Optional; import java.util.function.Function; @@ -204,11 +205,16 @@ private ThreadMode resolveTimeoutThreadMode(ExtensionContext extensionContext, TimeoutConfiguration timeoutConfiguration) { ThreadMode annotationThreadMode = getAnnotationThreadMode(extensionContext); if (annotationThreadMode == null || annotationThreadMode == ThreadMode.INFERRED) { - return timeoutConfiguration.getDefaultTimeoutThreadMode().orElse(SAME_THREAD); + return timeoutConfiguration.getDefaultTimeoutThreadMode() // + .orElseGet(TimeoutExtension::getDefaultThreadMode); } return annotationThreadMode; } + private static ThreadMode getDefaultThreadMode() { + return ThreadMode.valueOf(DEFAULT_TIMEOUT_THREAD_MODE_DEFAULT.toUpperCase(Locale.ROOT)); + } + private @Nullable ThreadMode getAnnotationThreadMode(ExtensionContext extensionContext) { return extensionContext.getStore(NAMESPACE).get(TESTABLE_METHOD_TIMEOUT_THREAD_MODE_KEY, ThreadMode.class); } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java index 9500bc2b6846..f7474375201f 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java @@ -115,6 +115,12 @@ public ParallelExecutionConfiguration createConfiguration(ConfigurationParameter private static final int KEEP_ALIVE_SECONDS = 30; + /** + * Default value for {@value #CONFIG_STRATEGY_PROPERTY_NAME} is {@value}. + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final String CONFIG_STRATEGY_DEFAULT = "dynamic"; + /** * Property name used to determine the desired configuration strategy. * @@ -233,8 +239,20 @@ static ParallelExecutionConfiguration toConfiguration(ConfigurationParameters co } static ParallelExecutionConfigurationStrategy getStrategy(ConfigurationParameters configurationParameters) { - return valueOf( - configurationParameters.get(CONFIG_STRATEGY_PROPERTY_NAME).orElse("dynamic").toUpperCase(Locale.ROOT)); + return configurationParameters.get(CONFIG_STRATEGY_PROPERTY_NAME, value -> { + try { + return valueOf(value.toUpperCase(Locale.ROOT)); + } + catch (Exception e) { + throw new JUnitException( + "Invalid ParallelExecutionConfigurationStrategy '%s' set via the '%s' configuration parameter.".formatted( + value, CONFIG_STRATEGY_PROPERTY_NAME)); + } + }).orElseGet(DefaultParallelExecutionConfigurationStrategy::getDefaultParallelExecutionConfigurationStrategy); + } + + private static DefaultParallelExecutionConfigurationStrategy getDefaultParallelExecutionConfigurationStrategy() { + return valueOf(CONFIG_STRATEGY_DEFAULT.toUpperCase(Locale.ROOT)); } } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelHierarchicalTestExecutorServiceFactory.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelHierarchicalTestExecutorServiceFactory.java index 195c7f8eeef2..967b21788082 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelHierarchicalTestExecutorServiceFactory.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelHierarchicalTestExecutorServiceFactory.java @@ -32,13 +32,19 @@ @API(status = MAINTAINED, since = "6.1") public final class ParallelHierarchicalTestExecutorServiceFactory { + /** + * Default value for {@value #EXECUTOR_SERVICE_PROPERTY_NAME} is {@value}. + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final String EXECUTOR_SERVICE_DEFAULT = "fork_join_pool"; + /** * Property name used to determine the desired * {@link ParallelExecutorServiceType ParallelExecutorServiceType}. * *

Value must be - * {@link ParallelExecutorServiceType#FORK_JOIN_POOL FORK_JOIN_POOL} or - * {@link ParallelExecutorServiceType#WORKER_THREAD_POOL WORKER_THREAD_POOL}, + * {@link ParallelExecutorServiceType#FORK_JOIN_POOL fork_join_pool} or + * {@link ParallelExecutorServiceType#WORKER_THREAD_POOL worker_thread_pool}, * ignoring case. */ public static final String EXECUTOR_SERVICE_PROPERTY_NAME = "executor-service"; @@ -65,7 +71,7 @@ public final class ParallelHierarchicalTestExecutorServiceFactory { */ public static HierarchicalTestExecutorService create(ConfigurationParameters configurationParameters) { var type = configurationParameters.get(EXECUTOR_SERVICE_PROPERTY_NAME, ParallelExecutorServiceType::parse) // - .orElse(ParallelExecutorServiceType.FORK_JOIN_POOL); + .orElseGet(() -> ParallelExecutorServiceType.parse(EXECUTOR_SERVICE_DEFAULT)); var configuration = DefaultParallelExecutionConfigurationStrategy.toConfiguration(configurationParameters); return create(type, configuration); } 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 88e487230ff9..b51f1d2fbe57 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 @@ -265,6 +265,12 @@ public class LauncherConstants { @API(status = MAINTAINED, since = "1.13.3") public static final String OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER = "{uniqueNumber}"; + /** + * Default value for {@value #CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME} is {@value}. + */ + @API(status = MAINTAINED, since = "6.2.0") + public static final String CRITICAL_DISCOVERY_ISSUE_SEVERITY_DEFAULT = "error"; + /** * Property name used to configure the critical severity of issues * encountered during test discovery. @@ -282,15 +288,14 @@ public class LauncherConstants { * {@link org.junit.platform.engine.DiscoveryIssue.Severity Severity}, * ignoring case. * - *

If not specified, the default is "error" which corresponds to + *

If not specified, the default is {@value #CRITICAL_DISCOVERY_ISSUE_SEVERITY_DEFAULT} which corresponds to * {@code Severity.ERROR)}. * * @since 1.13 * @see org.junit.platform.engine.DiscoveryIssue.Severity */ - // TODO: Reference enum values as strings? @API(status = EXPERIMENTAL, since = "6.0") - @ConfigurationParameter(type = Severity.class, defaultValue = @Value(stringValue = "error")) + @ConfigurationParameter(type = Severity.class, defaultValue = @Value(stringValue = CRITICAL_DISCOVERY_ISSUE_SEVERITY_DEFAULT)) public static final String CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME = "junit.platform.discovery.issue.severity.critical"; /** @@ -312,8 +317,7 @@ public class LauncherConstants { */ @API(status = EXPERIMENTAL, since = "6.0") // TODO: LauncherPhase is not part of the public API - // TODO: Reference enum values as strings? - @ConfigurationParameter(/*type = LauncherPhase.class,*/ defaultValue = @Value(stringValue = "discovery")) + @ConfigurationParameter /*(type = LauncherPhase.class)*/ public static final String DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME = "junit.platform.discovery.issue.failure.phase"; /** diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueCollector.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueCollector.java index de4b8957bf17..b21023a0a527 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueCollector.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueCollector.java @@ -60,6 +60,10 @@ private static Severity getCriticalSeverity(ConfigurationParameters configuratio value, LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME)); } }) // - .orElse(Severity.ERROR); + .orElseGet(DiscoveryIssueCollector::getDefaultSeverity); + } + + private static Severity getDefaultSeverity() { + return Severity.valueOf(LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_DEFAULT.toUpperCase(Locale.ROOT)); } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/ConstantTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/ConstantTests.java index d06ed3e13a72..f03c55145398 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/ConstantTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/ConstantTests.java @@ -22,10 +22,15 @@ public class ConstantTests { void constantsAreConsistent() { assertThat(Constants.PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME).isEqualTo(Constants.PARALLEL_CONFIG_PREFIX + ParallelHierarchicalTestExecutorServiceFactory.EXECUTOR_SERVICE_PROPERTY_NAME); + assertThat(Constants.PARALLEL_CONFIG_EXECUTOR_SERVICE_DEFAULT).isEqualTo( + ParallelHierarchicalTestExecutorServiceFactory.EXECUTOR_SERVICE_DEFAULT); assertThat(Constants.PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME).isEqualTo(Constants.PARALLEL_CONFIG_PREFIX + DefaultParallelExecutionConfigurationStrategy.CONFIG_STRATEGY_PROPERTY_NAME); + assertThat(Constants.PARALLEL_CONFIG_STRATEGY_DEFAULT).isEqualTo( + DefaultParallelExecutionConfigurationStrategy.CONFIG_STRATEGY_DEFAULT); + assertThat(Constants.PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME).isEqualTo(Constants.PARALLEL_CONFIG_PREFIX + DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_PARALLELISM_PROPERTY_NAME); assertThat(Constants.PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME).isEqualTo( From d663d1167a974d886f0f9d4d6d5c62f450f9f650 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 16 Aug 2026 01:22:57 +0200 Subject: [PATCH 069/117] Remove TODO --- .../junit-platform-configuration-processor.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts b/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts index 2abb3c592344..3c2f92cc1447 100644 --- a/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts +++ b/junit-platform-configuration-processor/junit-platform-configuration-processor.gradle.kts @@ -15,7 +15,6 @@ dependencies { compileOnlyApi(libs.jspecify) shadowed(libs.jakarta.json.api) - // TODO: Check if shadowed artifact is standalone shadowed(libs.jakarta.json.implementation) } From 3fc5cfeaa1b9adcd41e430752cffc320952808d5 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 16 Aug 2026 01:26:38 +0200 Subject: [PATCH 070/117] Document DefaultParallelExecutionConfigurationStrategy defaults --- .../DefaultParallelExecutionConfigurationStrategy.java | 8 ++++---- .../test/java/org/junit/jupiter/api/ConstantTests.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java index f7474375201f..1b27665aa363 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java @@ -64,7 +64,7 @@ public ParallelExecutionConfiguration createConfiguration(ConfigurationParameter @Override public ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters) { BigDecimal factor = configurationParameters.get(CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME, - BigDecimal::new).orElse(CONFIG_DYNAMIC_FACTOR_DEFAULT); + BigDecimal::new).orElse(BigDecimal.valueOf(CONFIG_DYNAMIC_FACTOR_DEFAULT)); Preconditions.condition(factor.compareTo(BigDecimal.ZERO) > 0, () -> "Factor '%s' specified via configuration parameter '%s' must be greater than 0".formatted(factor, @@ -154,7 +154,7 @@ public ParallelExecutionConfiguration createConfiguration(ConfigurationParameter public static final String CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME = "fixed.max-pool-size"; /** - * TODO: + * Default value for {@value #CONFIG_FIXED_SATURATE_PROPERTY_NAME} is {@value}. */ @API(status = MAINTAINED, since = "6.2.0") public static final boolean CONFIG_FIXED_SATURATE_DEFAULT = true; @@ -176,10 +176,10 @@ public ParallelExecutionConfiguration createConfiguration(ConfigurationParameter public static final String CONFIG_FIXED_SATURATE_PROPERTY_NAME = "fixed.saturate"; /** - * TODO: + * Default value for {@value #CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME} is {@value}. */ @API(status = MAINTAINED, since = "6.2.0") - public static final BigDecimal CONFIG_DYNAMIC_FACTOR_DEFAULT = BigDecimal.ONE; + public static final double CONFIG_DYNAMIC_FACTOR_DEFAULT = 1.0; /** * Property name of the factor used to determine the desired parallelism for the diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/ConstantTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/ConstantTests.java index f03c55145398..18e68ea6b58c 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/ConstantTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/ConstantTests.java @@ -46,7 +46,7 @@ void constantsAreConsistent() { assertThat(Constants.PARALLEL_CONFIG_CUSTOM_CLASS_PROPERTY_NAME).isEqualTo(Constants.PARALLEL_CONFIG_PREFIX + DefaultParallelExecutionConfigurationStrategy.CONFIG_CUSTOM_CLASS_PROPERTY_NAME); assertThat(Constants.PARALLEL_CONFIG_DYNAMIC_FACTOR_DEFAULT).isEqualTo( - DefaultParallelExecutionConfigurationStrategy.CONFIG_DYNAMIC_FACTOR_DEFAULT.doubleValue()); + DefaultParallelExecutionConfigurationStrategy.CONFIG_DYNAMIC_FACTOR_DEFAULT); assertThat(Constants.DEACTIVATE_ALL_CONDITIONS_PATTERN).isEqualTo(ClassNamePatternFilterUtils.ALL_PATTERN); assertThat(Constants.INCLUDE_ALL_EXTENSIONS_PATTERN).isEqualTo(ClassNamePatternFilterUtils.ALL_PATTERN); From 0beb3c5eab939d8b27f0a13e75591f458e461b86 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 16 Aug 2026 01:31:09 +0200 Subject: [PATCH 071/117] Make LauncherPhase a public but internal --- .../org/junit/platform/launcher/LauncherConstants.java | 4 ++-- .../org/junit/platform/launcher/core/LauncherPhase.java | 7 +++++-- 2 files changed, 7 insertions(+), 4 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 b51f1d2fbe57..f3583f94a91a 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 @@ -22,6 +22,7 @@ import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.launcher.core.LauncherPhase; /** * Collection of constants related to {@link Launcher}. @@ -316,8 +317,7 @@ public class LauncherConstants { * @see #CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME */ @API(status = EXPERIMENTAL, since = "6.0") - // TODO: LauncherPhase is not part of the public API - @ConfigurationParameter /*(type = LauncherPhase.class)*/ + @ConfigurationParameter(type = LauncherPhase.class) public static final String DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME = "junit.platform.discovery.issue.failure.phase"; /** 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 index 31d9b347da9c..c1450791cfcf 100644 --- 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 @@ -10,12 +10,14 @@ package org.junit.platform.launcher.core; +import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.launcher.LauncherConstants.DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME; import java.util.Locale; import java.util.Optional; import java.util.function.Function; +import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.engine.ConfigurationParameters; @@ -25,7 +27,8 @@ * * @since 1.13 */ -enum LauncherPhase { +@API(status = INTERNAL, since = "1.13") +public enum LauncherPhase { DISCOVERY, EXECUTION; @@ -45,6 +48,6 @@ static Optional getDiscoveryIssueFailurePhase(ConfigurationParame @Override public String toString() { - return name().toLowerCase(Locale.ENGLISH); + return name().toLowerCase(Locale.ROOT); } } From a7d1ad7bf5f30458694f2b448e6b407f181df896 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 16 Aug 2026 01:46:03 +0200 Subject: [PATCH 072/117] Reuse constants from org.junit.platform.engine --- .../junit-jupiter-api.gradle.kts | 1 + .../src/main/java/module-info.java | 1 + .../java/org/junit/jupiter/api/Constants.java | 43 ++++--------------- .../org/junit/jupiter/api/ConstantTests.java | 9 ---- .../junit-jupiter-api.expected.txt | 3 +- 5 files changed, 13 insertions(+), 44 deletions(-) diff --git a/junit-jupiter-api/junit-jupiter-api.gradle.kts b/junit-jupiter-api/junit-jupiter-api.gradle.kts index c52c9cf784e6..05150d0a10fc 100644 --- a/junit-jupiter-api/junit-jupiter-api.gradle.kts +++ b/junit-jupiter-api/junit-jupiter-api.gradle.kts @@ -20,6 +20,7 @@ dependencies { compileOnly(kotlin("stdlib")) compileOnly(projects.junitPlatformConfigurationApi) + compileOnly(projects.junitPlatformEngine) testFixturesImplementation(libs.assertj) testFixturesImplementation(testFixtures(projects.junitPlatformCommons)) diff --git a/junit-jupiter-api/src/main/java/module-info.java b/junit-jupiter-api/src/main/java/module-info.java index 583a9701aa62..520f3db170f2 100644 --- a/junit-jupiter-api/src/main/java/module-info.java +++ b/junit-jupiter-api/src/main/java/module-info.java @@ -22,6 +22,7 @@ requires transitive org.opentest4j; requires static org.junit.platform.configuration.api; + requires static org.junit.platform.engine; requires static kotlin.stdlib; exports org.junit.jupiter.api; diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java index cafe41e317fa..5be509bd0e3a 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java @@ -30,6 +30,10 @@ import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.platform.configuration.api.ConfigurationParameter; +import org.junit.platform.engine.support.hierarchical.DefaultParallelExecutionConfigurationStrategy; +import org.junit.platform.engine.support.hierarchical.ParallelExecutionConfigurationStrategy; +import org.junit.platform.engine.support.hierarchical.ParallelHierarchicalTestExecutorServiceFactory; +import org.junit.platform.engine.support.hierarchical.ParallelHierarchicalTestExecutorServiceFactory.ParallelExecutorServiceType; /** * Collection of configuration constants for the Jupiter test engine. @@ -259,12 +263,6 @@ public final class Constants { @API(status = INTERNAL, since = "6.1") public static final String PARALLEL_CONFIG_PREFIX = "junit.jupiter.execution.parallel.config."; - /** - * Default value for {@value #PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME} is {@value}. - */ - @API(status = MAINTAINED, since = "6.2.0") - public static final String PARALLEL_CONFIG_EXECUTOR_SERVICE_DEFAULT = "fork_join_pool"; - /** * Property name used to determine the desired parallel executor service * type: {@value} @@ -273,18 +271,10 @@ public final class Constants { * ignoring case. * */ - // TODO: Move PARALLEL_CONFIG_EXECUTOR_SERVICE_DEFAULT to ParallelExecutorServiceType - // TODO: ParallelExecutorServiceType is not part of the public API - @ConfigurationParameter(/* type = ParallelExecutorServiceType.class,*/ defaultValue = @Value(stringValue = PARALLEL_CONFIG_EXECUTOR_SERVICE_DEFAULT)) + @ConfigurationParameter(type = ParallelExecutorServiceType.class, defaultValue = @Value(stringValue = ParallelHierarchicalTestExecutorServiceFactory.EXECUTOR_SERVICE_DEFAULT)) public static final String PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "executor-service"; - /** - * Default value for {@value #PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME} is {@value}. - */ - @API(status = MAINTAINED, since = "6.2.0") - public static final String PARALLEL_CONFIG_STRATEGY_DEFAULT = "dynamic"; - /** * Property name used to select the parallel execution configuration * strategy: {@value} @@ -293,9 +283,7 @@ public final class Constants { * {@code custom}. * */ - // TODO: Move PARALLEL_CONFIG_STRATEGY_DEFAULT to ParallelExecutorServiceType - // TODO: DefaultParallelExecutionConfigurationStrategy is not part of the public API - @ConfigurationParameter(/* type = DefaultParallelExecutionConfigurationStrategy.class,*/ defaultValue = @Value(stringValue = PARALLEL_CONFIG_STRATEGY_DEFAULT)) + @ConfigurationParameter(type = DefaultParallelExecutionConfigurationStrategy.class, defaultValue = @Value(stringValue = DefaultParallelExecutionConfigurationStrategy.CONFIG_STRATEGY_DEFAULT)) public static final String PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "strategy"; /** @@ -322,12 +310,6 @@ public final class Constants { public static final String PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "fixed.max-pool-size"; - /** - * Default value for {@value #PARALLEL_CONFIG_FIXED_SATURATE_PROPERTY_NAME} is {@value}. - */ - @API(status = MAINTAINED, since = "6.2.0") - public static final boolean PARALLEL_CONFIG_FIXED_SATURATE_DEFAULT = true; - /** * Property name used to disable saturation of the underlying fork-join pool * for the {@code fixed} configuration strategy: {@value} @@ -339,15 +321,9 @@ public final class Constants { *

Value must either {@code true} or {@code false}; defaults to {@code true}. * */ - @ConfigurationParameter(defaultValue = @Value(booleanValue = PARALLEL_CONFIG_FIXED_SATURATE_DEFAULT)) + @ConfigurationParameter(defaultValue = @Value(booleanValue = DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_SATURATE_DEFAULT)) public static final String PARALLEL_CONFIG_FIXED_SATURATE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "fixed.saturate"; - /** - * Default value for {@value #PARALLEL_CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME} is {@value}. - */ - @API(status = MAINTAINED, since = "6.2.0") - public static final double PARALLEL_CONFIG_DYNAMIC_FACTOR_DEFAULT = 1.0; - /** * Property name used to set the factor to be multiplied with the number of * available processors/cores to determine the desired parallelism for the @@ -356,7 +332,7 @@ public final class Constants { *

Value must be a positive decimal number; defaults to {@code 1}. * */ - @ConfigurationParameter(type = BigDecimal.class, defaultValue = @Value(doubleValue = PARALLEL_CONFIG_DYNAMIC_FACTOR_DEFAULT)) + @ConfigurationParameter(type = BigDecimal.class, defaultValue = @Value(doubleValue = DefaultParallelExecutionConfigurationStrategy.CONFIG_DYNAMIC_FACTOR_DEFAULT)) public static final String PARALLEL_CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "dynamic.factor"; /** @@ -365,8 +341,7 @@ public final class Constants { * {@value} * */ - // TODO: ParallelExecutionConfigurationStrategy is not part of the public API - @ConfigurationParameter /*( type = ParallelExecutionConfigurationStrategy.class)*/ + @ConfigurationParameter(type = ParallelExecutionConfigurationStrategy.class) public static final String PARALLEL_CONFIG_CUSTOM_CLASS_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "custom.class"; /** diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/ConstantTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/ConstantTests.java index 18e68ea6b58c..61adfb3991cf 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/ConstantTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/ConstantTests.java @@ -22,15 +22,10 @@ public class ConstantTests { void constantsAreConsistent() { assertThat(Constants.PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME).isEqualTo(Constants.PARALLEL_CONFIG_PREFIX + ParallelHierarchicalTestExecutorServiceFactory.EXECUTOR_SERVICE_PROPERTY_NAME); - assertThat(Constants.PARALLEL_CONFIG_EXECUTOR_SERVICE_DEFAULT).isEqualTo( - ParallelHierarchicalTestExecutorServiceFactory.EXECUTOR_SERVICE_DEFAULT); assertThat(Constants.PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME).isEqualTo(Constants.PARALLEL_CONFIG_PREFIX + DefaultParallelExecutionConfigurationStrategy.CONFIG_STRATEGY_PROPERTY_NAME); - assertThat(Constants.PARALLEL_CONFIG_STRATEGY_DEFAULT).isEqualTo( - DefaultParallelExecutionConfigurationStrategy.CONFIG_STRATEGY_DEFAULT); - assertThat(Constants.PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME).isEqualTo(Constants.PARALLEL_CONFIG_PREFIX + DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_PARALLELISM_PROPERTY_NAME); assertThat(Constants.PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME).isEqualTo( @@ -38,15 +33,11 @@ void constantsAreConsistent() { + DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME); assertThat(Constants.PARALLEL_CONFIG_FIXED_SATURATE_PROPERTY_NAME).isEqualTo(Constants.PARALLEL_CONFIG_PREFIX + DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_SATURATE_PROPERTY_NAME); - assertThat(Constants.PARALLEL_CONFIG_FIXED_SATURATE_DEFAULT).isEqualTo( - DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_SATURATE_DEFAULT); assertThat(Constants.PARALLEL_CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME).isEqualTo(Constants.PARALLEL_CONFIG_PREFIX + DefaultParallelExecutionConfigurationStrategy.CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME); assertThat(Constants.PARALLEL_CONFIG_CUSTOM_CLASS_PROPERTY_NAME).isEqualTo(Constants.PARALLEL_CONFIG_PREFIX + DefaultParallelExecutionConfigurationStrategy.CONFIG_CUSTOM_CLASS_PROPERTY_NAME); - assertThat(Constants.PARALLEL_CONFIG_DYNAMIC_FACTOR_DEFAULT).isEqualTo( - DefaultParallelExecutionConfigurationStrategy.CONFIG_DYNAMIC_FACTOR_DEFAULT); assertThat(Constants.DEACTIVATE_ALL_CONDITIONS_PATTERN).isEqualTo(ClassNamePatternFilterUtils.ALL_PATTERN); assertThat(Constants.INCLUDE_ALL_EXTENSIONS_PATTERN).isEqualTo(ClassNamePatternFilterUtils.ALL_PATTERN); diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt index 856a65ab6108..4e8acf343862 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt @@ -13,7 +13,8 @@ requires org.apiguardian.api static transitive requires org.jspecify static transitive requires org.junit.platform.commons transitive requires org.junit.platform.configuration.api static +requires org.junit.platform.engine static requires org.opentest4j transitive qualified exports org.junit.jupiter.api.timeout to org.junit.jupiter.engine qualified opens org.junit.jupiter.api.condition to org.junit.platform.commons -qualified opens org.junit.jupiter.api.util to org.junit.platform.commons +qualified opens org.junit.jupiter.api.util to org.junit.platform.commons \ No newline at end of file From fe24aa67b452a521611c8a45c3da2fce11d6e961 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 16 Aug 2026 02:00:37 +0200 Subject: [PATCH 073/117] Give up on TimeoutDuration for now --- .../java/org/junit/jupiter/api/Constants.java | 33 +++++++------------ 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java index 5be509bd0e3a..dce026e26fc8 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java @@ -350,8 +350,7 @@ public final class Constants { * * @see Timeout#DEFAULT_TIMEOUT_PROPERTY_NAME */ - // TODO: TimeoutDuration is not part of the public API - @ConfigurationParameter /*(type = TimeoutDuration.class)*/ + @ConfigurationParameter(type = String.class) public static final String DEFAULT_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TIMEOUT_PROPERTY_NAME; /** @@ -359,8 +358,7 @@ public final class Constants { * * @see Timeout#DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME */ - // TODO: TimeoutDuration is not part of the public API - @ConfigurationParameter /*(type = TimeoutDuration.class)*/ + @ConfigurationParameter(type = String.class) public static final String DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -369,8 +367,7 @@ public final class Constants { * * @see Timeout#DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME */ - // TODO: TimeoutDuration is not part of the public API - @ConfigurationParameter /*(type = TimeoutDuration.class)*/ + @ConfigurationParameter(type = String.class) public static final String DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -379,8 +376,7 @@ public final class Constants { * * @see Timeout#DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME */ - // TODO: TimeoutDuration is not part of the public API - @ConfigurationParameter /*(type = TimeoutDuration.class)*/ + @ConfigurationParameter(type = String.class) public static final String DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -389,8 +385,7 @@ public final class Constants { * * @see Timeout#DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME */ - // TODO: TimeoutDuration is not part of the public API - @ConfigurationParameter /*(type = TimeoutDuration.class)*/ + @ConfigurationParameter(type = String.class) public static final String DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -398,8 +393,7 @@ public final class Constants { * * @see Timeout#DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME */ - // TODO: TimeoutDuration is not part of the public API - @ConfigurationParameter /*(type = TimeoutDuration.class)*/ + @ConfigurationParameter(type = String.class) public static final String DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -408,8 +402,7 @@ public final class Constants { * * @see Timeout#DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME */ - // TODO: TimeoutDuration is not part of the public API - @ConfigurationParameter /*(type = TimeoutDuration.class)*/ + @ConfigurationParameter(type = String.class) public static final String DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -418,8 +411,7 @@ public final class Constants { * * @see Timeout#DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME */ - // TODO: TimeoutDuration is not part of the public API - @ConfigurationParameter /*(type = TimeoutDuration.class)*/ + @ConfigurationParameter(type = String.class) public static final String DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -428,8 +420,7 @@ public final class Constants { * * @see Timeout#DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME */ - // TODO: TimeoutDuration is not part of the public API - @ConfigurationParameter /*(type = TimeoutDuration.class)*/ + @ConfigurationParameter(type = String.class) public static final String DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -438,8 +429,7 @@ public final class Constants { * * @see Timeout#DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME */ - // TODO: TimeoutDuration is not part of the public API - @ConfigurationParameter /*(type = TimeoutDuration.class)*/ + @ConfigurationParameter(type = String.class) public static final String DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -447,8 +437,7 @@ public final class Constants { * * @see Timeout#TIMEOUT_MODE_PROPERTY_NAME */ - // TODO: TimeoutDuration is not part of the public API - @ConfigurationParameter(/*type = TimeoutDuration.class,*/ defaultValue = @Value(stringValue = Timeout.TIMEOUT_MODE_DEFAULT)) + @ConfigurationParameter(defaultValue = @Value(stringValue = Timeout.TIMEOUT_MODE_DEFAULT)) public static final String TIMEOUT_MODE_PROPERTY_NAME = Timeout.TIMEOUT_MODE_PROPERTY_NAME; /** From da22b65408ff2bbbdefd73e3cd765898b2600e9b Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 16 Aug 2026 02:13:05 +0200 Subject: [PATCH 074/117] Break paragraphs on first @tag. --- .../ConfigurationParameterHandler.java | 2 +- ...ationMetadataAnnotationProcessorTests.java | 16 ++++++++++++ .../testcases/DocumentedWithAtSee.java | 25 +++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithAtSee.java diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java index 2aca3925ef81..53a7c578f887 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java @@ -85,7 +85,7 @@ private void processElement(Element element) { } // TODO: Creating patterns over and over is not very efficient // TODO: Handle {@link ...}, {@linkplain ...}, check how does Spring do that? - var matcher = Pattern.compile("

|").matcher(docComment); + var matcher = Pattern.compile("

||[^{]@[a-z]+").matcher(docComment); var firstParagraph = !matcher.find() ? docComment : docComment.substring(0, matcher.start()); return firstParagraph // // Replace newlines with space diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index b0b00534c57c..a89e221dce37 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -32,6 +32,7 @@ import org.junit.platform.configuration.testcases.Deprecation; import org.junit.platform.configuration.testcases.DeprecationWithDetails; import org.junit.platform.configuration.testcases.Documented; +import org.junit.platform.configuration.testcases.DocumentedWithAtSee; import org.junit.platform.configuration.testcases.DocumentedWithAtValue; import org.junit.platform.configuration.testcases.DocumentedWithHeader; import org.junit.platform.configuration.testcases.DocumentedWithMultiLines; @@ -160,6 +161,21 @@ void documentedWithHeader() { }"""); } + @Test + void documentedWithAtSee() { + compiler.compileWithoutError(DocumentedWithAtSee.class); + assertMetaDataIsEqualTo(""" + { + "properties": [ + { + "name": "org.example.property", + "description": "A brief description of this property.", + "sourceType": "org.junit.platform.configuration.testcases.DocumentedWithAtSee" + } + ] + }"""); + } + @Test void deprecation() { // TODO: Class level? Inheritance? Meta? diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithAtSee.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithAtSee.java new file mode 100644 index 000000000000..36b095cd6194 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithAtSee.java @@ -0,0 +1,25 @@ +/* + * Copyright 2026 the original author or 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.configuration.testcases; + +import org.junit.platform.configuration.api.ConfigurationParameter; + +public final class DocumentedWithAtSee { + + /** + * A brief description of this property. + * + * @see DocumentedWithAtSee + */ + @ConfigurationParameter + public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; + +} From a19c93d9c712e9529a130f34d399ca2618e5edbc Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 16 Aug 2026 02:47:31 +0200 Subject: [PATCH 075/117] Pretty print junit-platform-configuration-metadata.json --- ...ConfigurationMetadataAnnotationProcessor.java | 16 ++++++++++------ ...gurationMetadataAnnotationProcessorTests.java | 13 ++----------- ...JunitPlatformConfigurationProcessorTests.java | 12 ++++++++++-- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index 6e904454327c..75971cb4429a 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -10,13 +10,11 @@ package org.junit.platform.configuration.processor; -import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; -import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; -import java.io.OutputStreamWriter; +import java.util.Map; import java.util.Set; import javax.annotation.processing.AbstractProcessor; @@ -31,6 +29,7 @@ import org.jspecify.annotations.Nullable; import jakarta.json.Json; +import jakarta.json.stream.JsonGenerator; /// Writes all configuration parameters marked with /// {@link org.junit.platform.configuration.api.ConfigurationParameter} to @@ -93,9 +92,14 @@ public boolean process(Set annotations, RoundEnvironment private void writeMetaData() { var converter = new JsonConverter(); - try (var out = new BufferedWriter(new OutputStreamWriter(openOutputStream(), UTF_8))) { - var value = converter.toJsonObject(requireNonNull(metaData)); - Json.createWriter(out).write(value); + var config = Map.of(JsonGenerator.PRETTY_PRINTING, true); + var writerFactory = Json.createWriterFactory(config); + + try (var out = openOutputStream()) { + var writer = writerFactory.createWriter(out); + writer.write(converter.toJsonObject(requireNonNull(metaData))); + // Json writer doesn't add a trailing new line. + out.write('\n'); } catch (Exception ex) { var message = "Failed to write metadata to [%s]".formatted(METADATA_PATH); diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index a89e221dce37..7664441183d9 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -11,7 +11,6 @@ package org.junit.platform.configuration.processor; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.io.IOException; import java.io.UncheckedIOException; @@ -19,13 +18,11 @@ import java.nio.file.Path; import java.util.Locale; -import org.assertj.core.api.ThrowableAssert; import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.configuration.testcases.DefaultDifferentSets; import org.junit.platform.configuration.testcases.DefaultMultipleValues; import org.junit.platform.configuration.testcases.Defaults; @@ -205,7 +202,7 @@ void deprecationWithDetails() { "deprecation": { "reason": "This property was migrated to com.example", "replacement": "com.example.property", - "since":"2.0.0" + "since": "2.0.0" } } ] @@ -357,7 +354,7 @@ void mustHaveExactlyOneDefaultValue() { } private void assertMetaDataIsEqualTo(@Language("JSON") String json) { - assertThat(metaData()).isEqualToIgnoringWhitespace(json); + assertThat(metaData()).isEqualToNormalizingWhitespace(json); } private String metaData() throws UncheckedIOException { @@ -371,10 +368,4 @@ private String metaData() throws UncheckedIOException { } } - private static void asserPreconditionViolation(ThrowableAssert.ThrowingCallable throwingCallable, String message) { - assertThatThrownBy(throwingCallable) // - .hasRootCauseExactlyInstanceOf(PreconditionViolationException.class) // - .hasRootCauseMessage(message); - } - } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenJunitPlatformConfigurationProcessorTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenJunitPlatformConfigurationProcessorTests.java index 08aa7f8da8ea..cfc0ec5aeecd 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenJunitPlatformConfigurationProcessorTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenJunitPlatformConfigurationProcessorTests.java @@ -53,7 +53,15 @@ void processesAnnotationsIntoMetaData(@TempDir Path workspace, @FilePrefix("mave assertTrue(output.contains("[INFO] BUILD SUCCESS")); var metaData = workspace.resolve("target/classes/META-INF/junit-platform-configuration-metadata.json"); - assertThat(metaData).exists().content().isEqualTo(""" - {"properties":[{"name":"org.example.property","sourceType":"org.example.Constants"}]}"""); + assertThat(metaData).exists().content().isEqualToNormalizingWhitespace(""" + { + "properties": [ + { + "name": "org.example.property", + "sourceType": "org.example.Constants" + } + ] + } + """); } } From 5ef7650f340a5b91664484c4d11110e55ac5cc99 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 17 Aug 2026 16:52:20 +0200 Subject: [PATCH 076/117] Strip @code and @link(plain) tags. --- .../ConfigurationParameterHandler.java | 8 ++- ...ationMetadataAnnotationProcessorTests.java | 49 +++++++++++++++++++ .../testcases/DocumentedWithAtCode.java | 23 +++++++++ .../testcases/DocumentedWithAtLink.java | 23 +++++++++ .../testcases/DocumentedWithAtLinkPlain.java | 23 +++++++++ 5 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithAtCode.java create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithAtLink.java create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithAtLinkPlain.java diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java index 53a7c578f887..e6ee61f510b8 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java @@ -83,8 +83,6 @@ private void processElement(Element element) { if (docComment == null) { return null; } - // TODO: Creating patterns over and over is not very efficient - // TODO: Handle {@link ...}, {@linkplain ...}, check how does Spring do that? var matcher = Pattern.compile("

||[^{]@[a-z]+").matcher(docComment); var firstParagraph = !matcher.find() ? docComment : docComment.substring(0, matcher.start()); return firstParagraph // @@ -94,6 +92,12 @@ private void processElement(Element element) { .replaceAll(" +", " ") // // Replace the `: {@value}` conventional syntax. .replaceAll(": \\{@value}\\.?", ".") // + // Replace the `{@code example}` syntax. + .replaceAll("\\{@code (.+?)}", "$1") // + // Replace the `{@link(plain) reference}` syntax. + .replaceAll("\\{@link(?:plain)? ([^ ]+?)}", "$1") // + // Replace the `{@link(plain) reference plain}` syntax. + .replaceAll("\\{@link(?:plain)? [^ ]+ (.+?)}", "$1") // .trim(); } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index 7664441183d9..a4271b8e546e 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -29,6 +29,9 @@ import org.junit.platform.configuration.testcases.Deprecation; import org.junit.platform.configuration.testcases.DeprecationWithDetails; import org.junit.platform.configuration.testcases.Documented; +import org.junit.platform.configuration.testcases.DocumentedWithAtCode; +import org.junit.platform.configuration.testcases.DocumentedWithAtLink; +import org.junit.platform.configuration.testcases.DocumentedWithAtLinkPlain; import org.junit.platform.configuration.testcases.DocumentedWithAtSee; import org.junit.platform.configuration.testcases.DocumentedWithAtValue; import org.junit.platform.configuration.testcases.DocumentedWithHeader; @@ -173,6 +176,52 @@ void documentedWithAtSee() { }"""); } + @Test + void documentedWithAtCode() { + compiler.compileWithoutError(DocumentedWithAtCode.class); + assertMetaDataIsEqualTo(""" + { + "properties": [ + { + "name": "org.example.property", + "description": "Some example code in the first paragraph.", + "sourceType": "org.junit.platform.configuration.testcases.DocumentedWithAtCode" + } + ] + }"""); + } + + @Test + void documentedWithAtLink() { + compiler.compileWithoutError(DocumentedWithAtLink.class); + assertMetaDataIsEqualTo(""" + { + "properties": [ + { + "name": "org.example.property", + "description": "Some DocumentedWithAtLink and document with at link in the first paragraph.", + "sourceType": "org.junit.platform.configuration.testcases.DocumentedWithAtLink" + } + ] + }"""); + } + + @Test + void documentedWithAtLinkPlain() { + compiler.compileWithoutError(DocumentedWithAtLinkPlain.class); + assertMetaDataIsEqualTo( + """ + { + "properties": [ + { + "name": "org.example.property", + "description": "Some document with at linkplain and DocumentedWithAtLinkPlain in the first paragraph.", + "sourceType": "org.junit.platform.configuration.testcases.DocumentedWithAtLinkPlain" + } + ] + }"""); + } + @Test void deprecation() { // TODO: Class level? Inheritance? Meta? diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithAtCode.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithAtCode.java new file mode 100644 index 000000000000..c2564c1930b8 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithAtCode.java @@ -0,0 +1,23 @@ +/* + * Copyright 2026 the original author or 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.configuration.testcases; + +import org.junit.platform.configuration.api.ConfigurationParameter; + +public final class DocumentedWithAtCode { + + /** + * Some {@code example code} in the first paragraph. + */ + @ConfigurationParameter + public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; + +} diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithAtLink.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithAtLink.java new file mode 100644 index 000000000000..be31a5d06a33 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithAtLink.java @@ -0,0 +1,23 @@ +/* + * Copyright 2026 the original author or 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.configuration.testcases; + +import org.junit.platform.configuration.api.ConfigurationParameter; + +public final class DocumentedWithAtLink { + + /** + * Some {@link DocumentedWithAtLink} and {@link DocumentedWithAtLink document with at link} in the first paragraph. + */ + @ConfigurationParameter + public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; + +} diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithAtLinkPlain.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithAtLinkPlain.java new file mode 100644 index 000000000000..2092f07a42af --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DocumentedWithAtLinkPlain.java @@ -0,0 +1,23 @@ +/* + * Copyright 2026 the original author or 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.configuration.testcases; + +import org.junit.platform.configuration.api.ConfigurationParameter; + +public final class DocumentedWithAtLinkPlain { + + /** + * Some {@link DocumentedWithAtLinkPlain document with at linkplain} and {@link DocumentedWithAtLinkPlain} in the first paragraph. + */ + @ConfigurationParameter + public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; + +} From e009920b7f0d1ef30953a4b589096782f8018e53 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 17 Aug 2026 16:55:21 +0200 Subject: [PATCH 077/117] Remove TODO. --- .../src/main/java/org/junit/jupiter/api/Constants.java | 1 - 1 file changed, 1 deletion(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java index dce026e26fc8..ba9b1dab842f 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java @@ -135,7 +135,6 @@ public final class Constants { * *

The default behavior is not to perform auto-detection. */ - // TODO: Handle @link references. @ConfigurationParameter(defaultValue = @Value(booleanValue = EXTENSIONS_AUTODETECTION_ENABLED_DEFAULT)) public static final String EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.enabled"; From bb2e42deb8989fd281d9d604277f8f17b5fbc7e0 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 17 Aug 2026 16:55:38 +0200 Subject: [PATCH 078/117] Remove TODO. --- .../org/junit/platform/configuration/processor/TestCompiler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/TestCompiler.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/TestCompiler.java index f619ccbf1e46..e7f1d15c6aab 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/TestCompiler.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/TestCompiler.java @@ -35,7 +35,6 @@ class TestCompiler { private final Path sourceDirectory; - // TODO: Use an in memory solution, faster private final Path outputDirectory; private final Processor processor; From 94288101c16d09d2b75a6b970531cbbf88049f16 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 3 Sep 2026 00:39:48 +0200 Subject: [PATCH 079/117] Polish documentation --- .../processor/ConfigurationMetadataAnnotationProcessor.java | 4 +++- .../processor/ConfigurationParameterHandler.java | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index 75971cb4429a..ba210e652e36 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -54,7 +54,9 @@ /// /// The first paragraph from the doc string will be used to describe the /// property. If the first paragraph ends with {@code : {@value}.} or -/// {@code : {@value}} it will be replaced with a {@code : {@value}.}. +/// {@code : {@value}} it will be replaced with {@code .}. Likewise +/// {@code {@code}} and {@code {@link}} tags are replaced with plain +/// text versions. /// @API(status = API.Status.EXPERIMENTAL) @SupportedAnnotationTypes("org.junit.platform.configuration.api.ConfigurationParameter") diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java index e6ee61f510b8..9a4f119564c7 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java @@ -83,6 +83,7 @@ private void processElement(Element element) { if (docComment == null) { return null; } + // matches either a new paragraph, header or Javadoc tag without content (e.g. @see). var matcher = Pattern.compile("

||[^{]@[a-z]+").matcher(docComment); var firstParagraph = !matcher.find() ? docComment : docComment.substring(0, matcher.start()); return firstParagraph // From e7644855182e7b43ce07012685fad1cadf4fa514 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 3 Sep 2026 00:52:48 +0200 Subject: [PATCH 080/117] Document circular-ish dependency --- .../ConfigurationMetadataAnnotationProcessorTests.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index a4271b8e546e..00af7cfd0109 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -45,6 +45,12 @@ import org.junit.platform.configuration.testcases.TypeString; import org.junit.platform.configuration.testcases.Without; +/** + * The dependencies of platform-tests depend on the + * {@link ConfigurationMetadataAnnotationProcessor}. As a consequence, it is + * likely that building the project will find problems with the processor + * before this test does. + */ class ConfigurationMetadataAnnotationProcessorTests { final String expectedMetadataPath = "META-INF/junit-platform-configuration-metadata.json"; From 52ed6fa628be92db11eb1675aa431f3206ac6dda Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 3 Sep 2026 00:52:56 +0200 Subject: [PATCH 081/117] Clean up TODOs --- .../ConfigurationMetadataAnnotationProcessorTests.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index 00af7cfd0109..dcca77122e86 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -230,8 +230,6 @@ void documentedWithAtLinkPlain() { @Test void deprecation() { - // TODO: Class level? Inheritance? Meta? - // TODO: Warning level? compiler.compileWithoutError(Deprecation.class); assertMetaDataIsEqualTo(""" { From 5fccf19c4b36fa1f49aba9ef0e89f905e3366307 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 3 Sep 2026 01:13:26 +0200 Subject: [PATCH 082/117] Update Release notes --- .../ROOT/partials/release-notes/release-notes-6.2.0-M1.adoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/documentation/modules/ROOT/partials/release-notes/release-notes-6.2.0-M1.adoc b/documentation/modules/ROOT/partials/release-notes/release-notes-6.2.0-M1.adoc index 2ca0b5e52cde..3dcbfe45c62d 100644 --- a/documentation/modules/ROOT/partials/release-notes/release-notes-6.2.0-M1.adoc +++ b/documentation/modules/ROOT/partials/release-notes/release-notes-6.2.0-M1.adoc @@ -41,6 +41,10 @@ repository on GitHub. * `ConsoleLauncher` with `--details=verbose` now reports on each test atomically and indents its output correctly when tests are executed in parallel. Previously, the reported output from different tests could interleave. +* Test Engine authors can use the + `@org.junit.platform.configuration.api.ConfigurationParameter` annotation together + with the `junit-platform-configuration-processor` to generate configuration + metadata files in the format of link:https://docs.spring.io/spring-boot/specification/configuration-metadata/format.html[Spring Boots Configuration Metadata] [[v6.2.0-M1-junit-jupiter]] === JUnit Jupiter From ad9b4b1d8d4f4712bed528461b8feee65b893635 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 4 Sep 2026 15:43:40 +0200 Subject: [PATCH 083/117] Sketch out user guide documentation --- documentation/antora.yml | 2 ++ documentation/documentation.gradle.kts | 3 +++ .../ROOT/pages/advanced-topics/engines.adoc | 24 +++++++++++++++++++ .../example/ConfigurationParametersDemo.java | 18 ++++++++++++++ 4 files changed, 47 insertions(+) create mode 100644 documentation/src/test/java/example/ConfigurationParametersDemo.java diff --git a/documentation/antora.yml b/documentation/antora.yml index d809a8ee47a5..5c8357a4deb9 100644 --- a/documentation/antora.yml +++ b/documentation/antora.yml @@ -83,6 +83,7 @@ asciidoc: OutputDirectoryCreator: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/OutputDirectoryCreator.html[OutputDirectoryCreator]' PackageSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/PackageSelector.html[PackageSelector]' ParallelExecutionConfigurationStrategy: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.html[ParallelExecutionConfigurationStrategy]' + PlatformConfigurationParameter: '{javadoc-root}/org.junit.platform.configuration.api/org/junit/platform/configuration/api/ConfigurationParameter.html[ConfigurationParameter]' UniqueIdSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/UniqueIdSelector.html[UniqueIdSelector]' UriSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/UriSelector.html[UriSelector]' TestEngine: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/TestEngine.html[TestEngine]' @@ -305,6 +306,7 @@ asciidoc: MockitoExtension: 'https://github.com/mockito/mockito/blob/release/2.x/subprojects/junit-jupiter/src/main/java/org/mockito/junit/jupiter/MockitoExtension.java[MockitoExtension]' ServiceLoader: '{jdk-javadoc-base-url}/java.base/java/util/ServiceLoader.html[ServiceLoader]' SpringExtension: 'https://github.com/spring-projects/spring-framework/tree/HEAD/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java[SpringExtension]' + SpringBootConfigurationMetadata: 'https://docs.spring.io/spring-boot/specification/configuration-metadata/format.html[Spring Boots Configuration Metadata]' StackOverflow: 'https://stackoverflow.com/questions/tagged/junit5[Stack Overflow]' Truth: 'https://truth.dev/[Truth]' OpenTestReporting: 'https://github.com/ota4j-team/open-test-reporting[Open Test Reporting]' diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index dd0a6b02fe79..0aa082e5de5d 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -109,7 +109,10 @@ dependencies { // in the generation of build provenance attestation. mavenizedProjects.forEach { attestation(it) } + annotationProcessor(projects.junitPlatformConfigurationProcessor) + testImplementation(projects.junitJupiterMigrationsupport) + testImplementation(projects.junitPlatformConfigurationApi) testImplementation(projects.junitPlatformConsole) testImplementation(projects.junitPlatformSuite) testImplementation(projects.junitPlatformTestkit) diff --git a/documentation/modules/ROOT/pages/advanced-topics/engines.adoc b/documentation/modules/ROOT/pages/advanced-topics/engines.adoc index 392fc526b9b4..7c85dc5fd538 100644 --- a/documentation/modules/ROOT/pages/advanced-topics/engines.adoc +++ b/documentation/modules/ROOT/pages/advanced-topics/engines.adoc @@ -147,3 +147,27 @@ be used as a `Predicate` or `Consumer`. Please refer to the implementations of t Moreover, xref:advanced-topics/testkit.adoc#engine-discovery[Engine Test Kit] provides a way to write tests for reported discovery issues. + +[[configuration-parameters]] +== Configuration Parameters overview + +To assist IDEs with code completion test engines may generate machine-readable overview +of configuration parameters by annotating their configuration parameters with +`{PlatformConfigurationParameter}`. + +[tabs] +==== +Java:: ++ +-- +[source,java,indent=0] +---- +include::example$java/example/ConfigurationParametersDemo.java[tags=user_guide] +---- +-- +==== + +Processing the annotations with `junit-platform-configuration-processor` will produce +a `junit-platform-configuration-metadata.json` in the format of +{SpringBootConfigurationMetadata}. + diff --git a/documentation/src/test/java/example/ConfigurationParametersDemo.java b/documentation/src/test/java/example/ConfigurationParametersDemo.java new file mode 100644 index 000000000000..8292c54e381c --- /dev/null +++ b/documentation/src/test/java/example/ConfigurationParametersDemo.java @@ -0,0 +1,18 @@ +package example; + +import org.junit.platform.configuration.api.ConfigurationParameter; +import org.junit.platform.configuration.api.ConfigurationParameter.Value; + +public class ConfigurationParametersDemo { + + enum ExecutionMode { + FIXED, DYNAMIC, CUSTOM + } + // tag::user_guide[] + /** + * A brief description of this property. + */ + @ConfigurationParameter(type = ExecutionMode.class, defaultValue = @Value(stringValue = "fixed")) + public static final String EXECUTION_MODE_PROPERTY_NAME = "org.example.execution-mode"; + // end::user_guide[] +} From e0234100136ef118ae9951d61021f14c29f02eff Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 4 Sep 2026 15:45:07 +0200 Subject: [PATCH 084/117] Extract link --- .../ROOT/partials/release-notes/release-notes-6.2.0-M1.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/modules/ROOT/partials/release-notes/release-notes-6.2.0-M1.adoc b/documentation/modules/ROOT/partials/release-notes/release-notes-6.2.0-M1.adoc index 3dcbfe45c62d..f504cd179924 100644 --- a/documentation/modules/ROOT/partials/release-notes/release-notes-6.2.0-M1.adoc +++ b/documentation/modules/ROOT/partials/release-notes/release-notes-6.2.0-M1.adoc @@ -44,7 +44,7 @@ repository on GitHub. * Test Engine authors can use the `@org.junit.platform.configuration.api.ConfigurationParameter` annotation together with the `junit-platform-configuration-processor` to generate configuration - metadata files in the format of link:https://docs.spring.io/spring-boot/specification/configuration-metadata/format.html[Spring Boots Configuration Metadata] + metadata files in the format of {SpringBootConfigurationMetadata}. [[v6.2.0-M1-junit-jupiter]] === JUnit Jupiter From 57e55c00f6d1742591a0d3aac9b773a5dc4c41cf Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Wed, 9 Sep 2026 15:03:19 +0200 Subject: [PATCH 085/117] Sketch out more documentation --- documentation/documentation.gradle.kts | 8 ++++-- .../ROOT/pages/advanced-topics/engines.adoc | 24 ----------------- .../junit-platform-configuration.adoc | 27 +++++++++++++++++++ .../example/ConfigurationParametersDemo.java | 17 ++++++++++-- .../support/tests/ModularUserGuideTests.java | 2 ++ 5 files changed, 50 insertions(+), 28 deletions(-) create mode 100644 documentation/modules/ROOT/pages/advanced-topics/junit-platform-configuration.adoc diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index 0aa082e5de5d..e6bc7894496d 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -109,10 +109,10 @@ dependencies { // in the generation of build provenance attestation. mavenizedProjects.forEach { attestation(it) } - annotationProcessor(projects.junitPlatformConfigurationProcessor) + testAnnotationProcessor(projects.junitPlatformConfigurationProcessor) + testCompileOnly(projects.junitPlatformConfigurationApi) testImplementation(projects.junitJupiterMigrationsupport) - testImplementation(projects.junitPlatformConfigurationApi) testImplementation(projects.junitPlatformConsole) testImplementation(projects.junitPlatformSuite) testImplementation(projects.junitPlatformTestkit) @@ -230,6 +230,10 @@ tasks { options.release.set(25) } + compileTestJava { + options.compilerArgs.add("-Xlint:-module,-processing") // -module: due to qualified exports, -processing: not all annotations need to be processed + } + named("checkstyleTools") { config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleMain.xml")) } diff --git a/documentation/modules/ROOT/pages/advanced-topics/engines.adoc b/documentation/modules/ROOT/pages/advanced-topics/engines.adoc index 7c85dc5fd538..392fc526b9b4 100644 --- a/documentation/modules/ROOT/pages/advanced-topics/engines.adoc +++ b/documentation/modules/ROOT/pages/advanced-topics/engines.adoc @@ -147,27 +147,3 @@ be used as a `Predicate` or `Consumer`. Please refer to the implementations of t Moreover, xref:advanced-topics/testkit.adoc#engine-discovery[Engine Test Kit] provides a way to write tests for reported discovery issues. - -[[configuration-parameters]] -== Configuration Parameters overview - -To assist IDEs with code completion test engines may generate machine-readable overview -of configuration parameters by annotating their configuration parameters with -`{PlatformConfigurationParameter}`. - -[tabs] -==== -Java:: -+ --- -[source,java,indent=0] ----- -include::example$java/example/ConfigurationParametersDemo.java[tags=user_guide] ----- --- -==== - -Processing the annotations with `junit-platform-configuration-processor` will produce -a `junit-platform-configuration-metadata.json` in the format of -{SpringBootConfigurationMetadata}. - diff --git a/documentation/modules/ROOT/pages/advanced-topics/junit-platform-configuration.adoc b/documentation/modules/ROOT/pages/advanced-topics/junit-platform-configuration.adoc new file mode 100644 index 000000000000..40807edb5bc1 --- /dev/null +++ b/documentation/modules/ROOT/pages/advanced-topics/junit-platform-configuration.adoc @@ -0,0 +1,27 @@ += Documenting Configuration Parameters + +Authors of test engines and JUnit Jupiter extensions may generate a machine-readable +overview of configuration parameters by annotating their configuration parameters +with `{PlatformConfigurationParameter}` from the `junit-platform-configuration-api` +artifact. + +Processing the annotations with `junit-platform-configuration-processor` will produce a +`junit-platform-configuration-metadata.json` file in the format of +{SpringBootConfigurationMetadata}. This file can then be used to generate documentation +or provide code-completion by IDEs. + +.A small example +[tabs] +==== +Java:: ++ +-- +[source,java,indent=0] +---- +include::example$java/example/ConfigurationParametersDemo.java[tags=user_guide] +---- +-- +==== + +The first paragraph from the doc string will be used to describe the property. If the +first paragraph contains Javadoc tags these will be sensibly replaced. diff --git a/documentation/src/test/java/example/ConfigurationParametersDemo.java b/documentation/src/test/java/example/ConfigurationParametersDemo.java index 8292c54e381c..303670bedf14 100644 --- a/documentation/src/test/java/example/ConfigurationParametersDemo.java +++ b/documentation/src/test/java/example/ConfigurationParametersDemo.java @@ -1,3 +1,13 @@ +/* + * Copyright 2026 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + package example; import org.junit.platform.configuration.api.ConfigurationParameter; @@ -10,9 +20,12 @@ enum ExecutionMode { } // tag::user_guide[] /** - * A brief description of this property. + * Property name used to set the default test execution mode: {@value}. + *

+ * Test can be executed with either a fixed number of threads, a number based on + * the available cores or a custom strategy. */ @ConfigurationParameter(type = ExecutionMode.class, defaultValue = @Value(stringValue = "fixed")) - public static final String EXECUTION_MODE_PROPERTY_NAME = "org.example.execution-mode"; + public static final String DEFAULT_EXECUTION_MODE_PROPERTY_NAME = "org.example.execution-mode"; // end::user_guide[] } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java index 15ea6439b81e..554d1a6313f6 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java @@ -49,6 +49,8 @@ class ModularUserGuideTests { open module documentation { exports example.testkit; // just here to ensure documentation example sources are compiled + requires static org.junit.platform.configuration.api; + requires org.junit.jupiter.api; requires org.junit.jupiter.migrationsupport; requires org.junit.jupiter.params; From 91a64d802f977200a359e8a756eff77f120c9359 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 12 Sep 2026 11:50:33 +0200 Subject: [PATCH 086/117] Polish documentation --- documentation/modules/ROOT/nav.adoc | 1 + .../ROOT/pages/advanced-topics/engines.adoc | 2 + .../junit-platform-configuration.adoc | 37 ++++++++++++------- .../example/ConfigurationParametersDemo.java | 8 ++-- 4 files changed, 29 insertions(+), 19 deletions(-) diff --git a/documentation/modules/ROOT/nav.adoc b/documentation/modules/ROOT/nav.adoc index 568e8e4dc25a..a8f9102bd5fa 100644 --- a/documentation/modules/ROOT/nav.adoc +++ b/documentation/modules/ROOT/nav.adoc @@ -60,6 +60,7 @@ ** xref:advanced-topics/testkit.adoc[] ** xref:advanced-topics/launcher-api.adoc[] ** xref:advanced-topics/engines.adoc[] +** xref:advanced-topics/junit-platform-configuration.adoc[] * xref:api-evolution.adoc[] * xref:release-notes.adoc[] * xref:appendix.adoc[] diff --git a/documentation/modules/ROOT/pages/advanced-topics/engines.adoc b/documentation/modules/ROOT/pages/advanced-topics/engines.adoc index 392fc526b9b4..29e0029bf5be 100644 --- a/documentation/modules/ROOT/pages/advanced-topics/engines.adoc +++ b/documentation/modules/ROOT/pages/advanced-topics/engines.adoc @@ -128,6 +128,8 @@ compatibility with build tools and IDEs: report already started `TestDescriptors` as aborted in case they have not been executed completely. If a `TestEngine` supports cancellation, it should clean up any resources that it has created just like if execution had finished regularly. +* A `TestEngine` _may_ xref:advanced-topics/junit-platform-configuration.adoc[document its configuration parameters] + in `META-INF/junit-platform-configuration-metadata.json`. [[discovery-issues]] == Reporting Discovery Issues diff --git a/documentation/modules/ROOT/pages/advanced-topics/junit-platform-configuration.adoc b/documentation/modules/ROOT/pages/advanced-topics/junit-platform-configuration.adoc index 40807edb5bc1..c492ce9af3d5 100644 --- a/documentation/modules/ROOT/pages/advanced-topics/junit-platform-configuration.adoc +++ b/documentation/modules/ROOT/pages/advanced-topics/junit-platform-configuration.adoc @@ -5,23 +5,32 @@ overview of configuration parameters by annotating their configuration parameter with `{PlatformConfigurationParameter}` from the `junit-platform-configuration-api` artifact. +[source,java,indent=0] +.A small example +---- +include::example$java/example/ConfigurationParametersDemo.java[tags=user_guide] +---- + Processing the annotations with `junit-platform-configuration-processor` will produce a -`junit-platform-configuration-metadata.json` file in the format of +`META-INF/junit-platform-configuration-metadata.json` file in the format of {SpringBootConfigurationMetadata}. This file can then be used to generate documentation -or provide code-completion by IDEs. +or provide code-completion by IDEs. An example is shown below. -.A small example -[tabs] -==== -Java:: -+ --- -[source,java,indent=0] +[source,json,indent=0] +[subs=attributes+] +.A minimal `META-INF/junit-platform-configuration-metadata.json` ---- -include::example$java/example/ConfigurationParametersDemo.java[tags=user_guide] +{ + "properties": [ + { + "name": "org.example.execution-mode", + "description": "Property name used to set the default test execution mode.", + "sourceType": "example.ConfigurationParametersDemo", + "defaultValue": "fixed" + } + ] +} ---- --- -==== -The first paragraph from the doc string will be used to describe the property. If the -first paragraph contains Javadoc tags these will be sensibly replaced. +The first paragraph from the java documentation will be used to describe the property. If +the first paragraph contains Javadoc tags these will be sensibly replaced. diff --git a/documentation/src/test/java/example/ConfigurationParametersDemo.java b/documentation/src/test/java/example/ConfigurationParametersDemo.java index 303670bedf14..767515c24c55 100644 --- a/documentation/src/test/java/example/ConfigurationParametersDemo.java +++ b/documentation/src/test/java/example/ConfigurationParametersDemo.java @@ -10,15 +10,13 @@ package example; +import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.platform.configuration.api.ConfigurationParameter; import org.junit.platform.configuration.api.ConfigurationParameter.Value; +// tag::user_guide[] public class ConfigurationParametersDemo { - enum ExecutionMode { - FIXED, DYNAMIC, CUSTOM - } - // tag::user_guide[] /** * Property name used to set the default test execution mode: {@value}. *

@@ -27,5 +25,5 @@ enum ExecutionMode { */ @ConfigurationParameter(type = ExecutionMode.class, defaultValue = @Value(stringValue = "fixed")) public static final String DEFAULT_EXECUTION_MODE_PROPERTY_NAME = "org.example.execution-mode"; - // end::user_guide[] } +// end::user_guide[] From 9afd204abd17fadb84167ca9c2e4332e964d20b3 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 12 Sep 2026 12:43:19 +0200 Subject: [PATCH 087/117] Extract more default values and add test consistency --- .../java/org/junit/jupiter/api/Constants.java | 27 ++----------- .../java/org/junit/jupiter/api/Defaults.java | 39 +++++++++++++++++++ .../config/DefaultJupiterConfiguration.java | 6 +-- .../DefaultJupiterConfigurationTests.java | 11 ++++++ 4 files changed, 57 insertions(+), 26 deletions(-) create mode 100644 junit-jupiter-api/src/main/java/org/junit/jupiter/api/Defaults.java diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java index ba9b1dab842f..9da6b58578d9 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java @@ -19,7 +19,6 @@ import java.math.BigDecimal; import org.apiguardian.api.API; -import org.junit.jupiter.api.DisplayNameGenerator.Standard; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.PreInterruptCallback; import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; @@ -123,34 +122,22 @@ public final class Constants { @ConfigurationParameter(type = String.class) public static final String EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.exclude"; - /** - * Default value for {@value #EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME} is {@value}. - */ - @API(status = MAINTAINED, since = "6.2.0") - public static final boolean EXTENSIONS_AUTODETECTION_ENABLED_DEFAULT = false; - /** * Property name used to enable auto-detection and registration of extensions via * Java's {@link java.util.ServiceLoader} mechanism: {@value} * *

The default behavior is not to perform auto-detection. */ - @ConfigurationParameter(defaultValue = @Value(booleanValue = EXTENSIONS_AUTODETECTION_ENABLED_DEFAULT)) + @ConfigurationParameter(defaultValue = @Value(booleanValue = Defaults.EXTENSIONS_AUTODETECTION_ENABLED_DEFAULT)) public static final String EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.enabled"; - /** - * Default value for {@value #CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME} is {@value}. - */ - @API(status = MAINTAINED, since = "6.2.0") - public static final boolean CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_DEFAULT = true; - /** * Property name used to enable auto-closing of {@link AutoCloseable} instances: {@value} * *

By default, auto-closing is enabled. * */ - @ConfigurationParameter(defaultValue = @Value(booleanValue = CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_DEFAULT)) + @ConfigurationParameter(defaultValue = @Value(booleanValue = Defaults.CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_DEFAULT)) public static final String CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME = "junit.jupiter.extensions.store.close.autocloseable.enabled"; /** @@ -203,7 +190,7 @@ public final class Constants { * * @see DisplayNameGenerator#DEFAULT_GENERATOR_PROPERTY_NAME */ - @ConfigurationParameter(type = DisplayNameGenerator.class, defaultValue = @Value(classValue = Standard.class)) + @ConfigurationParameter(type = DisplayNameGenerator.class, defaultValue = @Value(classValue = DisplayNameGenerator.Standard.class)) public static final String DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME = DisplayNameGenerator.DEFAULT_GENERATOR_PROPERTY_NAME; /** @@ -223,19 +210,13 @@ public final class Constants { @ConfigurationParameter(type = Lifecycle.class, defaultValue = @Value(stringValue = Lifecycle.DEFAULT_LIFECYCLE_PATTERN_DEFAULT)) public static final String DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME = Lifecycle.DEFAULT_LIFECYCLE_PROPERTY_NAME; - /** - * Default value for {@value #PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME} is {@value}. - */ - @API(status = MAINTAINED, since = "6.2.0") - public static final boolean PARALLEL_EXECUTION_ENABLED_DEFAULT = false; - /** * Property name used to enable parallel test execution: {@value} * *

By default, tests are executed sequentially in a single thread. * */ - @ConfigurationParameter(defaultValue = @Value(booleanValue = PARALLEL_EXECUTION_ENABLED_DEFAULT)) + @ConfigurationParameter(defaultValue = @Value(booleanValue = Defaults.PARALLEL_EXECUTION_ENABLED_DEFAULT)) public static final String PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME = "junit.jupiter.execution.parallel.enabled"; /** diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Defaults.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Defaults.java new file mode 100644 index 000000000000..70145a2b69f1 --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Defaults.java @@ -0,0 +1,39 @@ +/* + * Copyright 2026 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; + +/** + * Defaults for configuration properties. + */ +@API(status = INTERNAL, since = "6.2") +public final class Defaults { + + /** + * Default value for {@value Constants#EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME} is {@value}. + */ + public static final boolean EXTENSIONS_AUTODETECTION_ENABLED_DEFAULT = false; + /** + * Default value for {@value Constants#CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME} is {@value}. + */ + public static final boolean CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_DEFAULT = true; + /** + * Default value for {@value Constants#PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME} is {@value}. + */ + public static final boolean PARALLEL_EXECUTION_ENABLED_DEFAULT = false; + + private Defaults() { + /* no-op */ + } +} diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java index 9ba02cbb7660..9a0841fb913b 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java @@ -12,7 +12,6 @@ import static java.util.function.Predicate.isEqual; import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.jupiter.api.Constants.CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_DEFAULT; import static org.junit.jupiter.api.Constants.CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME; @@ -25,15 +24,16 @@ import static org.junit.jupiter.api.Constants.DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME; -import static org.junit.jupiter.api.Constants.EXTENSIONS_AUTODETECTION_ENABLED_DEFAULT; import static org.junit.jupiter.api.Constants.EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.INCLUDE_ALL_EXTENSIONS_PATTERN; import static org.junit.jupiter.api.Constants.PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME; -import static org.junit.jupiter.api.Constants.PARALLEL_EXECUTION_ENABLED_DEFAULT; import static org.junit.jupiter.api.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; +import static org.junit.jupiter.api.Defaults.CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_DEFAULT; +import static org.junit.jupiter.api.Defaults.EXTENSIONS_AUTODETECTION_ENABLED_DEFAULT; +import static org.junit.jupiter.api.Defaults.PARALLEL_EXECUTION_ENABLED_DEFAULT; import static org.junit.jupiter.engine.config.FilteringConfigurationParameterConverter.exclude; import static org.junit.platform.engine.support.hierarchical.ParallelHierarchicalTestExecutorServiceFactory.ParallelExecutorServiceType.FORK_JOIN_POOL; import static org.junit.platform.engine.support.hierarchical.ParallelHierarchicalTestExecutorServiceFactory.ParallelExecutorServiceType.WORKER_THREAD_POOL; diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java index 835b100a0c22..055558760630 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java @@ -39,6 +39,7 @@ import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.api.io.TempDirDeletionStrategy; import org.junit.jupiter.api.io.TempDirFactory; import org.junit.jupiter.engine.descriptor.CustomDisplayNameGenerator; import org.junit.jupiter.params.ParameterizedTest; @@ -152,6 +153,16 @@ void shouldGetStandardAsDefaultTempDirFactorySupplierWithoutConfigParamSet() { assertThat(supplier.get()).isSameAs(TempDirFactory.Standard.INSTANCE); } + @Test + void shouldGetStandardAsDefaultTempDirDeletionStrategyWithoutConfigParamSet() { + var configuration = new DefaultJupiterConfiguration(configurationParameters(Map.of()), + dummyOutputDirectoryCreator(), mock()); + + var defaultTempDirDeletionStrategySupplier = configuration.getDefaultTempDirDeletionStrategySupplier(); + + assertThat(defaultTempDirDeletionStrategySupplier.get()).isInstanceOf(TempDirDeletionStrategy.Standard.class); + } + @Test void doesNotReportAnyIssuesIfConfigurationParametersAreEmpty() { List issues = new ArrayList<>(); From be8b36aaf71b2a41fdfc0ba84c4ba47194c9433b Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 12 Sep 2026 13:14:28 +0200 Subject: [PATCH 088/117] Update release notes --- .../partials/release-notes/release-notes-6.2.0-M1.adoc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/modules/ROOT/partials/release-notes/release-notes-6.2.0-M1.adoc b/documentation/modules/ROOT/partials/release-notes/release-notes-6.2.0-M1.adoc index f504cd179924..d82979fba34a 100644 --- a/documentation/modules/ROOT/partials/release-notes/release-notes-6.2.0-M1.adoc +++ b/documentation/modules/ROOT/partials/release-notes/release-notes-6.2.0-M1.adoc @@ -41,10 +41,10 @@ repository on GitHub. * `ConsoleLauncher` with `--details=verbose` now reports on each test atomically and indents its output correctly when tests are executed in parallel. Previously, the reported output from different tests could interleave. -* Test Engine authors can use the - `@org.junit.platform.configuration.api.ConfigurationParameter` annotation together - with the `junit-platform-configuration-processor` to generate configuration - metadata files in the format of {SpringBootConfigurationMetadata}. +* Test Engine and Jupiter Extension authors may generate a machine-readable overview + of configuration parameters. See + xref:advanced-topics/junit-platform-configuration.adoc[Documenting Configuration + Parameters] for more information. [[v6.2.0-M1-junit-jupiter]] === JUnit Jupiter From 688f77fbabe7d58ca2d4c9ee9ffc2128384f89ba Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 12 Sep 2026 13:17:41 +0200 Subject: [PATCH 089/117] Update release notes --- .../ROOT/partials/release-notes/release-notes-6.2.0-M1.adoc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/documentation/modules/ROOT/partials/release-notes/release-notes-6.2.0-M1.adoc b/documentation/modules/ROOT/partials/release-notes/release-notes-6.2.0-M1.adoc index d82979fba34a..2e531db75c6e 100644 --- a/documentation/modules/ROOT/partials/release-notes/release-notes-6.2.0-M1.adoc +++ b/documentation/modules/ROOT/partials/release-notes/release-notes-6.2.0-M1.adoc @@ -45,6 +45,7 @@ repository on GitHub. of configuration parameters. See xref:advanced-topics/junit-platform-configuration.adoc[Documenting Configuration Parameters] for more information. +* Platform configuration parameters are documented in `META-INF/junit-platform-configuration-metadata.json` [[v6.2.0-M1-junit-jupiter]] === JUnit Jupiter @@ -75,7 +76,7 @@ repository on GitHub. and `@DisabledOnOs`. * Failures caused by `@Timeout` expirations now include a hint about enabling xref:writing-tests/timeouts.adoc#debugging-thread-dump[thread dumps]. - +* Jupiter configuration parameters are documented in `META-INF/junit-platform-configuration-metadata.json` [[v6.2.0-M1-junit-vintage]] === JUnit Vintage @@ -93,4 +94,4 @@ repository on GitHub. [[v6.2.0-M1-junit-vintage-new-features-and-improvements]] ==== New Features and Improvements -* ❓ +* Vintage configuration parameters are documented in `META-INF/junit-platform-configuration-metadata.json` From 5bb0670d2242b18029ee74b4d030f8c1e1309412 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 12 Sep 2026 13:31:44 +0200 Subject: [PATCH 090/117] Generate Asciidoc reference from configuration parameter metadata (#5981) Co-authored-by: M.P. Korstanje --- documentation/documentation.gradle.kts | 11 ++- .../configuration-parameters.adoc | 10 +++ .../GenerateConfigurationParametersLists.kt | 83 +++++++++++++++++++ 3 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 gradle/plugins/common/src/main/kotlin/junitbuild/documentation/GenerateConfigurationParametersLists.kt diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index e6bc7894496d..f86f4df0354b 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -1,3 +1,4 @@ +import junitbuild.documentation.GenerateConfigurationParametersLists import junitbuild.exec.CaptureJavaExecOutput import junitbuild.exec.ClasspathSystemPropertyProvider import junitbuild.exec.GenerateStandaloneConsoleLauncherShadowedArtifactsFile @@ -89,7 +90,7 @@ dependencies { compileOnlyApi(libs.jspecify) // Pull in all "modular projects" to ensure that they are included - // in reports generated by the ApiReportGenerator. + // in reports generated by the ApiReportGenerator and GenerateConfigurationParametersLists modularProjects.forEach { apiReport(it) } apiReport(libs.openTestReporting.tooling.spi) @@ -289,6 +290,13 @@ tasks { outputFile = standaloneConsoleLauncherShadowedArtifactsFile } + val generateConfigurationParametersLists = register("generateConfigurationParametersLists", GenerateConfigurationParametersLists::class) { + metadataJars.from(apiReportClasspath.get().incoming.artifactView { + componentFilter { it is ProjectComponentIdentifier } + }.artifacts.artifactFiles) + outputFile = generatedAsciiDocPath.map { it.file("configuration-parameters-lists.adoc") } + } + plantUml { fileFormat = "SVG" outputs.cacheIf { true } @@ -304,6 +312,7 @@ tasks { generateConsoleLauncherEnginesOptions, generateApiTables, generateStandaloneConsoleLauncherShadowedArtifactsFile, + generateConfigurationParametersLists, plantUmlOutputDirectory ) } diff --git a/documentation/modules/ROOT/pages/running-tests/configuration-parameters.adoc b/documentation/modules/ROOT/pages/running-tests/configuration-parameters.adoc index 8688d0e27862..3d8a0db6b2d8 100644 --- a/documentation/modules/ROOT/pages/running-tests/configuration-parameters.adoc +++ b/documentation/modules/ROOT/pages/running-tests/configuration-parameters.adoc @@ -74,3 +74,13 @@ Examples: `org.example.MyCustomImpl`. - `org.example.MyCustomImpl, org.example.TheirCustomImpl`: matches candidate classes whose FQCN is exactly `org.example.MyCustomImpl` or `org.example.TheirCustomImpl`. + +[[reference]] +== Reference + +The following lists enumerate the configuration parameters supported by the JUnit Platform, +Jupiter, and Vintage. + +NOTE: Third-party JUnit Platform test engines and JUnit Jupiter extensions may provide additional configuration parameters. + +include::partial$configuration-parameters-lists.adoc[] diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/documentation/GenerateConfigurationParametersLists.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/documentation/GenerateConfigurationParametersLists.kt new file mode 100644 index 000000000000..1c45970fadd2 --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild/documentation/GenerateConfigurationParametersLists.kt @@ -0,0 +1,83 @@ +package junitbuild.documentation + +import org.gradle.api.DefaultTask +import org.gradle.api.file.ArchiveOperations +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction +import tools.jackson.databind.JsonNode +import tools.jackson.databind.json.JsonMapper +import javax.inject.Inject + +@CacheableTask +abstract class GenerateConfigurationParametersLists @Inject constructor( + private val archives: ArchiveOperations +) : DefaultTask() { + + @get:Classpath + abstract val metadataJars: ConfigurableFileCollection + + @get:OutputFile + abstract val outputFile: RegularFileProperty + + @TaskAction + fun generate() { + val mapper = JsonMapper() + + val propertiesByGroup = GROUPS.associate { it.title to mutableListOf() } + metadataJars.forEach { jar -> + val group = GROUPS.firstOrNull { jar.name.startsWith(it.prefix) } ?: return@forEach + archives.zipTree(jar).matching { include(METADATA_PATH) }.forEach { file -> + val properties = mapper.readTree(file).path("properties") + properties.forEach { propertiesByGroup.getValue(group.title).add(it) } + } + } + + val sb = StringBuilder() + propertiesByGroup.forEach { (title, properties) -> + if (properties.isEmpty()) { + return@forEach + } + sb.appendLine("[[configuration-parameters-${title.lowercase().replace(' ', '-')}]]") + sb.appendLine("=== $title") + sb.appendLine() + properties.sortedBy { it.path("name").asString() }.forEach { property -> + val name = property.path("name").asString() + sb.appendLine("`$name`::") + val description = text(property.path("description").asString("")) + val default = property.path("defaultValue") + val hasDefault = !default.isMissingNode + if (description.isNotEmpty()) { + val terminated = if (hasDefault && !description.endsWith(".")) "$description." else description + sb.appendLine(" $terminated") + } + if (hasDefault) { + sb.appendLine(" Defaults to `${text(default.asString())}`.") + } + if (property.has("deprecation")) { + sb.appendLine(" _(deprecated)_") + } + sb.appendLine() + } + } + + outputFile.get().asFile.writeText(sb.toString()) + } + + private fun text(value: String) = value.replace('\n', ' ').trim() + + private data class Group(val title: String, val prefix: String) + + companion object { + private const val METADATA_PATH = "META-INF/junit-platform-configuration-metadata.json" + + private val GROUPS = listOf( + Group("JUnit Platform", "junit-platform"), + Group("JUnit Jupiter", "junit-jupiter"), + Group("JUnit Vintage", "junit-vintage"), + ) + } +} From 4550c4992d5a33214eec1adc73adbb8e3bf1cd6f Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 13 Sep 2026 14:35:25 +0200 Subject: [PATCH 091/117] Include example generated by annotation processor --- documentation/antora.yml | 2 ++ documentation/documentation.gradle.kts | 2 +- .../advanced-topics/junit-platform-configuration.adoc | 11 +---------- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/documentation/antora.yml b/documentation/antora.yml index 5c8357a4deb9..059a8fd48c57 100644 --- a/documentation/antora.yml +++ b/documentation/antora.yml @@ -17,6 +17,8 @@ ext: into: modules/ROOT/attachments/api - dir: ./src/test into: modules/ROOT/examples + - dir: ./build/classes/java/test/META-INF + into: modules/ROOT/examples - dir: ./build/generated/asciidoc clean: true into: modules/ROOT/partials diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index f86f4df0354b..127def050d03 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -505,6 +505,6 @@ tasks { } generateAntoraResources { - dependsOn(generateAsciidocInputs, fixJavadoc) + dependsOn(generateAsciidocInputs, fixJavadoc, compileTestJava) } } diff --git a/documentation/modules/ROOT/pages/advanced-topics/junit-platform-configuration.adoc b/documentation/modules/ROOT/pages/advanced-topics/junit-platform-configuration.adoc index c492ce9af3d5..afda2101af49 100644 --- a/documentation/modules/ROOT/pages/advanced-topics/junit-platform-configuration.adoc +++ b/documentation/modules/ROOT/pages/advanced-topics/junit-platform-configuration.adoc @@ -20,16 +20,7 @@ or provide code-completion by IDEs. An example is shown below. [subs=attributes+] .A minimal `META-INF/junit-platform-configuration-metadata.json` ---- -{ - "properties": [ - { - "name": "org.example.execution-mode", - "description": "Property name used to set the default test execution mode.", - "sourceType": "example.ConfigurationParametersDemo", - "defaultValue": "fixed" - } - ] -} +include::example$junit-platform-configuration-metadata.json[] ---- The first paragraph from the java documentation will be used to describe the property. If From 1ae68f46ae83cbd0dcf65c2fec7760853b40706d Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 14 Sep 2026 02:26:58 +0200 Subject: [PATCH 092/117] Update documentation/modules/ROOT/pages/advanced-topics/junit-platform-configuration.adoc Co-authored-by: Marc Philipp --- .../pages/advanced-topics/junit-platform-configuration.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/modules/ROOT/pages/advanced-topics/junit-platform-configuration.adoc b/documentation/modules/ROOT/pages/advanced-topics/junit-platform-configuration.adoc index afda2101af49..2bfc45f11019 100644 --- a/documentation/modules/ROOT/pages/advanced-topics/junit-platform-configuration.adoc +++ b/documentation/modules/ROOT/pages/advanced-topics/junit-platform-configuration.adoc @@ -14,7 +14,7 @@ include::example$java/example/ConfigurationParametersDemo.java[tags=user_guide] Processing the annotations with `junit-platform-configuration-processor` will produce a `META-INF/junit-platform-configuration-metadata.json` file in the format of {SpringBootConfigurationMetadata}. This file can then be used to generate documentation -or provide code-completion by IDEs. An example is shown below. +or provide code completion by IDEs. An example is shown below. [source,json,indent=0] [subs=attributes+] From aca015cb01cf2492292f026221f4f9223f382d34 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 14 Sep 2026 02:27:11 +0200 Subject: [PATCH 093/117] Update documentation/modules/ROOT/nav.adoc Co-authored-by: Marc Philipp --- documentation/modules/ROOT/nav.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/modules/ROOT/nav.adoc b/documentation/modules/ROOT/nav.adoc index a8f9102bd5fa..93b6e9b8f51f 100644 --- a/documentation/modules/ROOT/nav.adoc +++ b/documentation/modules/ROOT/nav.adoc @@ -60,7 +60,7 @@ ** xref:advanced-topics/testkit.adoc[] ** xref:advanced-topics/launcher-api.adoc[] ** xref:advanced-topics/engines.adoc[] -** xref:advanced-topics/junit-platform-configuration.adoc[] +** xref:advanced-topics/configuration-parameter-documentation.adoc[] * xref:api-evolution.adoc[] * xref:release-notes.adoc[] * xref:appendix.adoc[] From 9df662312264881ca36dd68679d69ae8230dc613 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 14 Sep 2026 02:29:46 +0200 Subject: [PATCH 094/117] Rename junit-platform-configuration.adoc to onfiguration-parameter-documentation.adoc --- ...guration.adoc => configuration-parameter-documentation.adoc} | 0 documentation/modules/ROOT/pages/advanced-topics/engines.adoc | 2 +- .../ROOT/partials/release-notes/release-notes-6.2.0-M1.adoc | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename documentation/modules/ROOT/pages/advanced-topics/{junit-platform-configuration.adoc => configuration-parameter-documentation.adoc} (100%) diff --git a/documentation/modules/ROOT/pages/advanced-topics/junit-platform-configuration.adoc b/documentation/modules/ROOT/pages/advanced-topics/configuration-parameter-documentation.adoc similarity index 100% rename from documentation/modules/ROOT/pages/advanced-topics/junit-platform-configuration.adoc rename to documentation/modules/ROOT/pages/advanced-topics/configuration-parameter-documentation.adoc diff --git a/documentation/modules/ROOT/pages/advanced-topics/engines.adoc b/documentation/modules/ROOT/pages/advanced-topics/engines.adoc index 29e0029bf5be..19cc8c4a2c57 100644 --- a/documentation/modules/ROOT/pages/advanced-topics/engines.adoc +++ b/documentation/modules/ROOT/pages/advanced-topics/engines.adoc @@ -128,7 +128,7 @@ compatibility with build tools and IDEs: report already started `TestDescriptors` as aborted in case they have not been executed completely. If a `TestEngine` supports cancellation, it should clean up any resources that it has created just like if execution had finished regularly. -* A `TestEngine` _may_ xref:advanced-topics/junit-platform-configuration.adoc[document its configuration parameters] +* A `TestEngine` _may_ xref:advanced-topics/configuration-parameter-documentation.adoc[document its configuration parameters] in `META-INF/junit-platform-configuration-metadata.json`. [[discovery-issues]] diff --git a/documentation/modules/ROOT/partials/release-notes/release-notes-6.2.0-M1.adoc b/documentation/modules/ROOT/partials/release-notes/release-notes-6.2.0-M1.adoc index 2e531db75c6e..04d311f20e93 100644 --- a/documentation/modules/ROOT/partials/release-notes/release-notes-6.2.0-M1.adoc +++ b/documentation/modules/ROOT/partials/release-notes/release-notes-6.2.0-M1.adoc @@ -43,7 +43,7 @@ repository on GitHub. reported output from different tests could interleave. * Test Engine and Jupiter Extension authors may generate a machine-readable overview of configuration parameters. See - xref:advanced-topics/junit-platform-configuration.adoc[Documenting Configuration + xref:advanced-topics/configuration-parameter-documentation.adoc[Documenting Configuration Parameters] for more information. * Platform configuration parameters are documented in `META-INF/junit-platform-configuration-metadata.json` From 747779b8e1ce72af21db8979fce9705360f70824 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 14 Sep 2026 02:30:17 +0200 Subject: [PATCH 095/117] Extract ExecutionMode for example --- .../src/test/java/example/ConfigurationParametersDemo.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/documentation/src/test/java/example/ConfigurationParametersDemo.java b/documentation/src/test/java/example/ConfigurationParametersDemo.java index 767515c24c55..525c00086da1 100644 --- a/documentation/src/test/java/example/ConfigurationParametersDemo.java +++ b/documentation/src/test/java/example/ConfigurationParametersDemo.java @@ -10,7 +10,6 @@ package example; -import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.platform.configuration.api.ConfigurationParameter; import org.junit.platform.configuration.api.ConfigurationParameter.Value; @@ -27,3 +26,7 @@ public class ConfigurationParametersDemo { public static final String DEFAULT_EXECUTION_MODE_PROPERTY_NAME = "org.example.execution-mode"; } // end::user_guide[] + +enum ExecutionMode { + FIXED, DYNAMIC, CUSTOM; +} From 990b117d0aebe6a1345f7cb844d00aa45c914bf7 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 14 Sep 2026 02:54:25 +0200 Subject: [PATCH 096/117] Document new component in appendix --- documentation/modules/ROOT/pages/appendix.adoc | 4 ++++ documentation/src/plantuml/component-diagram.puml | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/documentation/modules/ROOT/pages/appendix.adoc b/documentation/modules/ROOT/pages/appendix.adoc index 34601760f32d..91cc99a5cecd 100644 --- a/documentation/modules/ROOT/pages/appendix.adoc +++ b/documentation/modules/ROOT/pages/appendix.adoc @@ -54,6 +54,10 @@ xref:running-tests/build-support.adoc#gradle-bom[Gradle]. Common APIs and support utilities for the JUnit Platform. Any API annotated with `@API(status = INTERNAL)` is intended solely for usage within the JUnit framework itself. _Any usage of internal APIs by external parties is not supported!_ + `junit-platform-configuration-api`:: + Annotations for documenting configuration parameters for test engines and JUnit Jupiter extensions. + `junit-platform-configuration-processor`:: + Annotations processor to generate a machine-readable overview of configuration parameters. `junit-platform-console`:: Support for discovering and executing tests on the JUnit Platform from the console. See xref:running-tests/console-launcher.adoc[] for details. diff --git a/documentation/src/plantuml/component-diagram.puml b/documentation/src/plantuml/component-diagram.puml index a910c17dcef3..4d809ccfa265 100644 --- a/documentation/src/plantuml/component-diagram.puml +++ b/documentation/src/plantuml/component-diagram.puml @@ -18,6 +18,15 @@ package org.junit.vintage { package org.junit.platform { [junit-platform-commons] as commons + [junit-platform-configuration-api] as configuration_api + note top of configuration_api #white + junit-jupiter-engine, junit-vintage-engine, + and junit-platform-launcher have a compile- + time dependency on this artifact. The edges + have been omitted from this diagram for the + sake of readability. + endnote + [junit-platform-configuration-processor] as configuration_processor [junit-platform-console] as console [junit-platform-engine] as engine [junit-platform-launcher] as launcher @@ -67,6 +76,8 @@ jupiter_params ..> jupiter_api jupiter_migration_support ..> jupiter_api jupiter_migration_support ...> junit4 +configuration_api ...> configuration_processor + console ..> launcher console ..> reporting @@ -83,6 +94,7 @@ suite ..> suite_engine suite_engine ..> launcher suite_engine ..> suite_api +suite_engine ..> engine testkit ....> opentest4j testkit ..> launcher From a80336052c63f82b1ae2935e3c2d1c5a487caaa9 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 14 Sep 2026 02:55:10 +0200 Subject: [PATCH 097/117] Rename to ConfigurationMetadata --- ...figurationMetaData.java => ConfigurationMetadata.java} | 2 +- .../ConfigurationMetadataAnnotationProcessor.java | 4 ++-- .../processor/ConfigurationParameterHandler.java | 8 ++++---- .../platform/configuration/processor/JsonConverter.java | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) rename junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/{ConfigurationMetaData.java => ConfigurationMetadata.java} (97%) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadata.java similarity index 97% rename from junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java rename to junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadata.java index aad773dbde34..c258fb1d701b 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetaData.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadata.java @@ -18,7 +18,7 @@ /** * See Spring Boot - Specifications - Configuration Metadata - Metadata Format. */ -final class ConfigurationMetaData { +final class ConfigurationMetadata { private final List properties = new ArrayList<>(); diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index ba210e652e36..37ab9fca6248 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -62,13 +62,13 @@ @SupportedAnnotationTypes("org.junit.platform.configuration.api.ConfigurationParameter") public final class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor { private static final String METADATA_PATH = "META-INF/junit-platform-configuration-metadata.json"; - private @Nullable ConfigurationMetaData metaData; + private @Nullable ConfigurationMetadata metaData; private @Nullable ConfigurationParameterHandler configurationParameterHandler; @Override public synchronized void init(ProcessingEnvironment environment) { super.init(environment); - this.metaData = new ConfigurationMetaData(); + this.metaData = new ConfigurationMetadata(); this.configurationParameterHandler = new ConfigurationParameterHandler(metaData, processingEnv.getElementUtils(), processingEnv.getMessager()); } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java index 9a4f119564c7..d426a980b5db 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java @@ -25,16 +25,16 @@ import org.jspecify.annotations.Nullable; import org.junit.platform.configuration.api.ConfigurationParameter; -import org.junit.platform.configuration.processor.ConfigurationMetaData.Deprecation; -import org.junit.platform.configuration.processor.ConfigurationMetaData.Property; +import org.junit.platform.configuration.processor.ConfigurationMetadata.Deprecation; +import org.junit.platform.configuration.processor.ConfigurationMetadata.Property; final class ConfigurationParameterHandler { - private final ConfigurationMetaData metaData; + private final ConfigurationMetadata metaData; private final Elements elementUtils; private final Messager messager; - ConfigurationParameterHandler(ConfigurationMetaData metaData, Elements elementUtils, Messager messager) { + ConfigurationParameterHandler(ConfigurationMetadata metaData, Elements elementUtils, Messager messager) { this.metaData = metaData; this.elementUtils = elementUtils; this.messager = messager; diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java index 90323286a838..150afcbfee95 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java @@ -14,9 +14,9 @@ import java.util.Map; import java.util.function.Function; -import org.junit.platform.configuration.processor.ConfigurationMetaData.Deprecation; -import org.junit.platform.configuration.processor.ConfigurationMetaData.Deprecation.Level; -import org.junit.platform.configuration.processor.ConfigurationMetaData.Property; +import org.junit.platform.configuration.processor.ConfigurationMetadata.Deprecation; +import org.junit.platform.configuration.processor.ConfigurationMetadata.Deprecation.Level; +import org.junit.platform.configuration.processor.ConfigurationMetadata.Property; import jakarta.json.Json; import jakarta.json.JsonArray; @@ -29,7 +29,7 @@ final class JsonConverter { private final Map config = Map.of(); private final JsonBuilderFactory factory = Json.createBuilderFactory(config); - JsonObject toJsonObject(ConfigurationMetaData metaData) { + JsonObject toJsonObject(ConfigurationMetadata metaData) { var builder = factory.createObjectBuilder(); var properties = metaData.properties(); From 572e59541f5b913531a6674dbb841fd132f1c5bb Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 14 Sep 2026 02:57:30 +0200 Subject: [PATCH 098/117] Remove unused level --- .../processor/ConfigurationMetadata.java | 14 -------------- .../processor/ConfigurationParameterHandler.java | 4 ++-- .../configuration/processor/JsonConverter.java | 10 ---------- 3 files changed, 2 insertions(+), 26 deletions(-) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadata.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadata.java index c258fb1d701b..eb2c9d2685ec 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadata.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadata.java @@ -42,25 +42,11 @@ record Property( // } record Deprecation( // - @Nullable Level level, // @Nullable String reason, // @Nullable String replacement, // @Nullable String since // ) { - enum Level { - WARNING("warning"), ERROR("error"); - - private final String value; - - Level(String value) { - this.value = value; - } - - String value() { - return value; - } - } } } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java index d426a980b5db..00671669f9e9 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java @@ -145,11 +145,11 @@ private record Default(String defaultType, Object value) { private @Nullable Deprecation processDeprecation(ConfigurationParameterAnnotatedField field) { var values = field.deprecationValues(); if (!values.isEmpty()) { - return new Deprecation(null, values.get("reason"), values.get("replacement"), values.get("since")); + return new Deprecation(values.get("reason"), values.get("replacement"), values.get("since")); } // Fallback, look for @Deprecated if (field.isDeprecated()) { - return new Deprecation(null, null, null, null); + return new Deprecation(null, null, null); } return null; } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java index 150afcbfee95..84957a097f36 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java @@ -15,7 +15,6 @@ import java.util.function.Function; import org.junit.platform.configuration.processor.ConfigurationMetadata.Deprecation; -import org.junit.platform.configuration.processor.ConfigurationMetadata.Deprecation.Level; import org.junit.platform.configuration.processor.ConfigurationMetadata.Property; import jakarta.json.Json; @@ -109,11 +108,6 @@ else if (defaultValue instanceof String v) { private JsonObject toJsonObject(Deprecation deprecation) { var builder = factory.createObjectBuilder(); - var level = deprecation.level(); - if (level != null) { - builder.add("level", toJsonValue(level)); - } - var reason = deprecation.reason(); if (reason != null) { builder.add("reason", reason); @@ -132,10 +126,6 @@ private JsonObject toJsonObject(Deprecation deprecation) { return builder.build(); } - private String toJsonValue(Level level) { - return level.value(); - } - private JsonArray toJsonArray(List properties, Function converter) { var builder = factory.createArrayBuilder(); properties.forEach(element -> builder.add(converter.apply(element))); From 422988821ce6e775542a9042cc0500deb9054f0b Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 14 Sep 2026 02:58:11 +0200 Subject: [PATCH 099/117] Delete gradle/incremental.annotation.processors --- .../resources/META-INF/gradle/incremental.annotation.processors | 1 - 1 file changed, 1 deletion(-) delete mode 100644 junit-platform-configuration-processor/src/main/resources/META-INF/gradle/incremental.annotation.processors diff --git a/junit-platform-configuration-processor/src/main/resources/META-INF/gradle/incremental.annotation.processors b/junit-platform-configuration-processor/src/main/resources/META-INF/gradle/incremental.annotation.processors deleted file mode 100644 index 3019ff07037d..000000000000 --- a/junit-platform-configuration-processor/src/main/resources/META-INF/gradle/incremental.annotation.processors +++ /dev/null @@ -1 +0,0 @@ -org.junit.platform.configuration.processor.ConfigurationMetadataAnnotationProcessor,aggregating From 186c38e4fb24d5998756077d55fa6ea388ca4b5d Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 14 Sep 2026 02:58:57 +0200 Subject: [PATCH 100/117] Use since = "6.2" --- .../main/java/org/junit/jupiter/api/Constants.java | 2 +- .../java/org/junit/jupiter/api/TestInstance.java | 2 +- .../main/java/org/junit/jupiter/api/Timeout.java | 4 ++-- .../api/extension/PreInterruptCallback.java | 2 +- .../extension/TestInstantiationAwareExtension.java | 2 +- .../java/org/junit/jupiter/api/io/TempDir.java | 2 +- .../org/junit/jupiter/api/parallel/Execution.java | 2 +- ...aultParallelExecutionConfigurationStrategy.java | 6 +++--- ...llelHierarchicalTestExecutorServiceFactory.java | 2 +- .../junit/platform/launcher/LauncherConstants.java | 14 +++++++------- .../java/org/junit/vintage/engine/Constants.java | 8 ++++---- 11 files changed, 23 insertions(+), 23 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java index 9da6b58578d9..24868553f534 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java @@ -47,7 +47,7 @@ public final class Constants { /** * Default value for {@value #EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME} is {@value}. */ - @API(status = MAINTAINED, since = "6.2.0") + @API(status = MAINTAINED, since = "6.2") public static final String INCLUDE_ALL_EXTENSIONS_PATTERN = "*"; /** diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java index 18931484d86a..83a830976cc2 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java @@ -110,7 +110,7 @@ enum Lifecycle { /** * Default value for {@value #DEFAULT_LIFECYCLE_PROPERTY_NAME} is {@value}. */ - @API(status = MAINTAINED, since = "6.2.0") + @API(status = MAINTAINED, since = "6.2") public static final String DEFAULT_LIFECYCLE_PATTERN_DEFAULT = "per_method"; /** diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java index 62b3e5c0dd2b..f4fde66ea9ed 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java @@ -300,7 +300,7 @@ /** * Default value for {@value #TIMEOUT_MODE_PROPERTY_NAME} is {@value}. */ - @API(status = MAINTAINED, since = "6.2.0") + @API(status = MAINTAINED, since = "6.2") String TIMEOUT_MODE_DEFAULT = "enabled"; /** @@ -327,7 +327,7 @@ /** * Default value for {@value #DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME} is {@value}. */ - @API(status = MAINTAINED, since = "6.2.0") + @API(status = MAINTAINED, since = "6.2") String DEFAULT_TIMEOUT_THREAD_MODE_DEFAULT = "same_thread"; /** diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/PreInterruptCallback.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/PreInterruptCallback.java index f00ad681451d..54a4ae8c5c06 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/PreInterruptCallback.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/PreInterruptCallback.java @@ -34,7 +34,7 @@ public interface PreInterruptCallback extends Extension { /** * Default value for {@value #THREAD_DUMP_ENABLED_PROPERTY_NAME} is {@value}. */ - @API(status = MAINTAINED, since = "6.2.0") + @API(status = MAINTAINED, since = "6.2") boolean THREAD_DUMP_ENABLED_DEFAULT = false; /** diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java index 2d0c2818d6b0..4525852d1a14 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java @@ -176,7 +176,7 @@ enum ExtensionContextScope { /** * Default value for {@value #DEFAULT_SCOPE_PROPERTY_NAME} is {@value}. */ - @API(status = MAINTAINED, since = "6.2.0") + @API(status = MAINTAINED, since = "6.2") public static final String DEFAULT_SCOPE_DEFAULT = "default"; /** diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java index b46a267f987b..2cf878f27e3b 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java @@ -135,7 +135,7 @@ /** * Default value for {@value #DEFAULT_CLEANUP_MODE_PROPERTY_NAME} is {@value}. */ - @API(status = MAINTAINED, since = "6.2.0") + @API(status = MAINTAINED, since = "6.2") String DEFAULT_TEMP_DIR_CLEANUP_MODE_DEFAULT = "always"; /** diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java index e3efabe4148d..59a2b4ceec7c 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java @@ -64,7 +64,7 @@ /** * Default value for {@value #DEFAULT_EXECUTION_MODE_PROPERTY_NAME} is {@value}. */ - @API(status = MAINTAINED, since = "6.2.0") + @API(status = MAINTAINED, since = "6.2") String DEFAULT_EXECUTION_MODE_DEFAULT = "same_thread"; /** diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java index 1b27665aa363..84c34b9f2c0b 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java @@ -118,7 +118,7 @@ public ParallelExecutionConfiguration createConfiguration(ConfigurationParameter /** * Default value for {@value #CONFIG_STRATEGY_PROPERTY_NAME} is {@value}. */ - @API(status = MAINTAINED, since = "6.2.0") + @API(status = MAINTAINED, since = "6.2") public static final String CONFIG_STRATEGY_DEFAULT = "dynamic"; /** @@ -156,7 +156,7 @@ public ParallelExecutionConfiguration createConfiguration(ConfigurationParameter /** * Default value for {@value #CONFIG_FIXED_SATURATE_PROPERTY_NAME} is {@value}. */ - @API(status = MAINTAINED, since = "6.2.0") + @API(status = MAINTAINED, since = "6.2") public static final boolean CONFIG_FIXED_SATURATE_DEFAULT = true; /** @@ -178,7 +178,7 @@ public ParallelExecutionConfiguration createConfiguration(ConfigurationParameter /** * Default value for {@value #CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME} is {@value}. */ - @API(status = MAINTAINED, since = "6.2.0") + @API(status = MAINTAINED, since = "6.2") public static final double CONFIG_DYNAMIC_FACTOR_DEFAULT = 1.0; /** diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelHierarchicalTestExecutorServiceFactory.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelHierarchicalTestExecutorServiceFactory.java index 967b21788082..2b835d20fd35 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelHierarchicalTestExecutorServiceFactory.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelHierarchicalTestExecutorServiceFactory.java @@ -35,7 +35,7 @@ public final class ParallelHierarchicalTestExecutorServiceFactory { /** * Default value for {@value #EXECUTOR_SERVICE_PROPERTY_NAME} is {@value}. */ - @API(status = MAINTAINED, since = "6.2.0") + @API(status = MAINTAINED, since = "6.2") public static final String EXECUTOR_SERVICE_DEFAULT = "fork_join_pool"; /** 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 d3610d8195f2..82197131479e 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 @@ -36,7 +36,7 @@ public class LauncherConstants { /** * Default value for {@value #CAPTURE_STDOUT_PROPERTY_NAME} is {@value}. */ - @API(status = MAINTAINED, since = "6.2.0") + @API(status = MAINTAINED, since = "6.2") public static final boolean CAPTURE_STDOUT_DEFAULT = false; /** @@ -60,7 +60,7 @@ public class LauncherConstants { /** * Default value for {@value #CAPTURE_STDERR_PROPERTY_NAME} is {@value}. */ - @API(status = MAINTAINED, since = "6.2.0") + @API(status = MAINTAINED, since = "6.2") public static final boolean CAPTURE_STDERR_DEFAULT = false; /** @@ -174,7 +174,7 @@ public class LauncherConstants { /** * Default value for {@value #ENABLE_LAUNCHER_INTERCEPTORS} is {@value}. */ - @API(status = MAINTAINED, since = "6.2.0") + @API(status = MAINTAINED, since = "6.2") public static final boolean ENABLE_LAUNCHER_INTERCEPTORS_DEFAULT = false; /** @@ -198,7 +198,7 @@ public class LauncherConstants { /** * Default value for {@value #DRY_RUN_PROPERTY_NAME} is {@value}. */ - @API(status = MAINTAINED, since = "6.2.0") + @API(status = MAINTAINED, since = "6.2") public static final boolean DRY_RUN_DEFAULT = false; /** @@ -221,7 +221,7 @@ public class LauncherConstants { /** * Default value for {@value #STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME} is {@value}. */ - @API(status = MAINTAINED, since = "6.2.0") + @API(status = MAINTAINED, since = "6.2") public static final boolean STACKTRACE_PRUNING_ENABLED_DEFAULT = true; /** @@ -269,7 +269,7 @@ public class LauncherConstants { /** * Default value for {@value #CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME} is {@value}. */ - @API(status = MAINTAINED, since = "6.2.0") + @API(status = MAINTAINED, since = "6.2") public static final String CRITICAL_DISCOVERY_ISSUE_SEVERITY_DEFAULT = "error"; /** @@ -323,7 +323,7 @@ public class LauncherConstants { /** * Default value for {@value #MEMORY_CLEANUP_ENABLED_PROPERTY_NAME} is {@value}. */ - @API(status = MAINTAINED, since = "6.2.0") + @API(status = MAINTAINED, since = "6.2") public static final boolean MEMORY_CLEANUP_ENABLED_DEFAULT = false; /** diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java index dbe0d03dc2e5..dc3b268c01da 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java @@ -30,7 +30,7 @@ public final class Constants { /** * Default value for {@value #PARALLEL_EXECUTION_ENABLED} is {@value}. */ - @API(status = MAINTAINED, since = "6.2.0") + @API(status = MAINTAINED, since = "6.2") public static final boolean PARALLEL_EXECUTION_ENABLED_DEFAULT = false; /** @@ -61,7 +61,7 @@ public final class Constants { /** * Default value for {@value #PARALLEL_CLASS_EXECUTION} is {@value}. */ - @API(status = MAINTAINED, since = "6.2.0") + @API(status = MAINTAINED, since = "6.2") public static final boolean PARALLEL_CLASS_EXECUTION_DEFAULT = false; /** @@ -80,7 +80,7 @@ public final class Constants { /** * Default value for {@value #PARALLEL_METHOD_EXECUTION} is {@value}. */ - @API(status = MAINTAINED, since = "6.2.0") + @API(status = MAINTAINED, since = "6.2") public static final boolean PARALLEL_METHOD_EXECUTION_DEFAULT = false; /** @@ -99,7 +99,7 @@ public final class Constants { /** * Default value for {@value #DISCOVERY_ISSUE_REPORTING_ENABLED_PROPERTY_NAME} is {@value}. */ - @API(status = MAINTAINED, since = "6.2.0") + @API(status = MAINTAINED, since = "6.2") public static final boolean DISCOVERY_ISSUE_REPORTING_ENABLED_DEFAULT = true; /** From d110ac3ec9895ba0e5c36bac752ec9dd568f08f1 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 14 Sep 2026 03:15:34 +0200 Subject: [PATCH 101/117] Return null for consistency --- .../configuration/processor/ConfigurationParameterHandler.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java index 00671669f9e9..f5389d9f6186 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java @@ -114,12 +114,14 @@ private String processSourceType(ConfigurationParameterAnnotatedField field) { if (defaultValues.size() != 1) { messager.printMessage(ERROR, "@ConfigurationParameter must have exactly one default value", field.element(), field.annotationMirror()); + return null; } var entry = defaultValues.entrySet().iterator().next(); var values = entry.getValue(); if (values.size() != 1) { messager.printMessage(ERROR, "@ConfigurationParameter must have exactly one default value", field.element(), field.annotationMirror()); + return null; } var key = entry.getKey(); var value = values.get(0); From e8ba0dfd86a5cd7f7dcd4c5113e4fdbf7940ebe9 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 14 Sep 2026 03:26:14 +0200 Subject: [PATCH 102/117] Remove redundant try-catch-rethrow --- ...faultParallelExecutionConfigurationStrategy.java | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java index 84c34b9f2c0b..edf8e3f6e87b 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java @@ -239,16 +239,9 @@ static ParallelExecutionConfiguration toConfiguration(ConfigurationParameters co } static ParallelExecutionConfigurationStrategy getStrategy(ConfigurationParameters configurationParameters) { - return configurationParameters.get(CONFIG_STRATEGY_PROPERTY_NAME, value -> { - try { - return valueOf(value.toUpperCase(Locale.ROOT)); - } - catch (Exception e) { - throw new JUnitException( - "Invalid ParallelExecutionConfigurationStrategy '%s' set via the '%s' configuration parameter.".formatted( - value, CONFIG_STRATEGY_PROPERTY_NAME)); - } - }).orElseGet(DefaultParallelExecutionConfigurationStrategy::getDefaultParallelExecutionConfigurationStrategy); + return configurationParameters.get(CONFIG_STRATEGY_PROPERTY_NAME, + value -> valueOf(value.toUpperCase(Locale.ROOT))).orElseGet( + DefaultParallelExecutionConfigurationStrategy::getDefaultParallelExecutionConfigurationStrategy); } private static DefaultParallelExecutionConfigurationStrategy getDefaultParallelExecutionConfigurationStrategy() { From 6da86060fdee4b54e02501b4c9ec055c80cb0720 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 14 Sep 2026 03:27:21 +0200 Subject: [PATCH 103/117] Update junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java Co-authored-by: Marc Philipp --- .../src/main/java/org/junit/jupiter/api/parallel/Execution.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java index 59a2b4ceec7c..f09a57d9ece4 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java @@ -77,7 +77,7 @@ *

Supported values include names of enum constants defined in * {@link ExecutionMode}, ignoring case. * - *

If not specified, the default is {@link #DEFAULT_EXECUTION_MODE_DEFAULT} which corresponds to + *

If not specified, the default is {@value #DEFAULT_EXECUTION_MODE_DEFAULT} which corresponds to * {@code @Execution(ExecutionMode.SAME_THREAD)}. * * @since 5.4 From 0d183d79cb53c669415de89fa75f9270b4561aee Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 14 Sep 2026 03:33:40 +0200 Subject: [PATCH 104/117] Update junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java Co-authored-by: Marc Philipp --- .../src/main/java/org/junit/vintage/engine/Constants.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java index dc3b268c01da..80525e7bbd94 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java @@ -74,7 +74,7 @@ public final class Constants { * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") - @ConfigurationParameter(defaultValue = @Value(booleanValue = PARALLEL_EXECUTION_ENABLED_DEFAULT)) + @ConfigurationParameter(defaultValue = @Value(booleanValue = PARALLEL_CLASS_EXECUTION_DEFAULT)) public static final String PARALLEL_CLASS_EXECUTION = "junit.vintage.execution.parallel.classes"; /** From 74fb180e15dfc8253e564ae3433784b0127817de Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 14 Sep 2026 03:49:12 +0200 Subject: [PATCH 105/117] Write bytes as integer to json --- .../junit/platform/configuration/processor/JsonConverter.java | 2 +- .../ConfigurationMetadataAnnotationProcessorTests.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java index 84957a097f36..d494409e27f1 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java @@ -76,7 +76,7 @@ private void addObjectValue(JsonObjectBuilder builder, String name, Object defau builder.add(name, v); } else if (defaultValue instanceof Byte v) { - builder.add(name, "%02X".formatted(v)); + builder.add(name, (int) v); } else if (defaultValue instanceof Integer v) { builder.add(name, v); diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index dcca77122e86..8dee4a6c6bcb 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -309,7 +309,7 @@ void defaults() { "name": "org.example.bytes", "type": "java.lang.Byte", "sourceType": "org.junit.platform.configuration.testcases.Defaults", - "defaultValue": "2A" + "defaultValue": 42 }, { "name": "org.example.ints", From 3a8c32266a42c36c8b1ab90e32f2ff7754a4c437 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Wed, 16 Sep 2026 12:55:51 +0200 Subject: [PATCH 106/117] Reference enum values by reference rather than enumeration --- .../org/junit/jupiter/api/TestInstance.java | 2 +- .../java/org/junit/jupiter/api/Timeout.java | 21 +++++++------------ .../TestInstantiationAwareExtension.java | 4 ++-- .../org/junit/jupiter/api/io/TempDir.java | 2 +- .../junit/jupiter/api/parallel/Execution.java | 2 +- ...arallelExecutionConfigurationStrategy.java | 8 ++++--- ...ierarchicalTestExecutorServiceFactory.java | 9 ++++---- .../platform/launcher/LauncherConstants.java | 2 +- 8 files changed, 23 insertions(+), 27 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java index 83a830976cc2..8116859c4278 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java @@ -111,7 +111,7 @@ enum Lifecycle { * Default value for {@value #DEFAULT_LIFECYCLE_PROPERTY_NAME} is {@value}. */ @API(status = MAINTAINED, since = "6.2") - public static final String DEFAULT_LIFECYCLE_PATTERN_DEFAULT = "per_method"; + public static final String DEFAULT_LIFECYCLE_PATTERN_DEFAULT = "PER_METHOD"; /** * Property name used to set the default test instance lifecycle mode: diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java index f4fde66ea9ed..e90af7fa8cb9 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java @@ -301,7 +301,7 @@ * Default value for {@value #TIMEOUT_MODE_PROPERTY_NAME} is {@value}. */ @API(status = MAINTAINED, since = "6.2") - String TIMEOUT_MODE_DEFAULT = "enabled"; + String TIMEOUT_MODE_DEFAULT = "ENABLED"; /** * Property name used to configure whether timeouts are applied to tests: @@ -310,14 +310,9 @@ *

The value of this property will be used to toggle whether * {@link Timeout @Timeout} is applied to tests.

* - *

Supported timeout mode values (case-insensitive):

- *
    - *
  • {@code enabled}: enables timeouts - *
  • {@code disabled}: disables timeouts - *
  • {@code disabled_on_debug}: disables timeouts while debugging - *
- * - *

If not specified, the default is {@value #TIMEOUT_MODE_DEFAULT}. + *

Value must be one names of enum constants defined in + * {@link ThreadMode}, ignoring case. If not specified, the default is + * {@value #TIMEOUT_MODE_DEFAULT}. * * @since 5.6 */ @@ -328,7 +323,7 @@ * Default value for {@value #DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME} is {@value}. */ @API(status = MAINTAINED, since = "6.2") - String DEFAULT_TIMEOUT_THREAD_MODE_DEFAULT = "same_thread"; + String DEFAULT_TIMEOUT_THREAD_MODE_DEFAULT = "SAME_THREAD"; /** * Property name used to set the default thread mode for all testable and @@ -338,9 +333,9 @@ * {@link Timeout @Timeout} annotation present on the method or on an * enclosing test class (for testable methods). * - *

The supported values are {@code same_thread} or - * {@code separate_thread}, ignoring case. If none is provided, - * {@value #DEFAULT_TIMEOUT_THREAD_MODE_DEFAULT} is used as default. + *

Value must be one names of enum constants defined in + * {@link ThreadMode}, ignoring case. If not specified, the default is + * {@value DEFAULT_TIMEOUT_THREAD_MODE_DEFAULT}. * * @since 5.9 * @see #threadMode() diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java index 4525852d1a14..f12ff6b2b9f5 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java @@ -147,7 +147,7 @@ enum ExtensionContextScope { /** * The extension should receive an {@link ExtensionContext} for the - * the default scope. + * default scope. * *

The default scope is determined by the configuration parameter * {@link #DEFAULT_SCOPE_PROPERTY_NAME}. If not specified, extensions @@ -177,7 +177,7 @@ enum ExtensionContextScope { * Default value for {@value #DEFAULT_SCOPE_PROPERTY_NAME} is {@value}. */ @API(status = MAINTAINED, since = "6.2") - public static final String DEFAULT_SCOPE_DEFAULT = "default"; + public static final String DEFAULT_SCOPE_DEFAULT = "DEFAULT"; /** * Property name used to set the default extension context scope: {@value} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java index 2cf878f27e3b..ee618c0e102d 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java @@ -136,7 +136,7 @@ * Default value for {@value #DEFAULT_CLEANUP_MODE_PROPERTY_NAME} is {@value}. */ @API(status = MAINTAINED, since = "6.2") - String DEFAULT_TEMP_DIR_CLEANUP_MODE_DEFAULT = "always"; + String DEFAULT_TEMP_DIR_CLEANUP_MODE_DEFAULT = "ALWAYS"; /** * Property name used to configure the default {@link CleanupMode}: {@value} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java index f09a57d9ece4..4112304bb222 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java @@ -65,7 +65,7 @@ * Default value for {@value #DEFAULT_EXECUTION_MODE_PROPERTY_NAME} is {@value}. */ @API(status = MAINTAINED, since = "6.2") - String DEFAULT_EXECUTION_MODE_DEFAULT = "same_thread"; + String DEFAULT_EXECUTION_MODE_DEFAULT = "SAME_THREAD"; /** * Property name used to set the default test execution mode: {@value} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java index edf8e3f6e87b..181f18db8d0f 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java @@ -119,13 +119,15 @@ public ParallelExecutionConfiguration createConfiguration(ConfigurationParameter * Default value for {@value #CONFIG_STRATEGY_PROPERTY_NAME} is {@value}. */ @API(status = MAINTAINED, since = "6.2") - public static final String CONFIG_STRATEGY_DEFAULT = "dynamic"; + public static final String CONFIG_STRATEGY_DEFAULT = "DYNAMIC"; /** * Property name used to determine the desired configuration strategy. * - *

Value must be one of {@code dynamic}, {@code fixed}, or - * {@code custom}. + *

Value must be one names of enum constants defined in + * {@linkplain DefaultParallelExecutionConfigurationStrategy this class}, + * ignoring case. If not specified, the default is + * {@value CONFIG_STRATEGY_DEFAULT}. */ public static final String CONFIG_STRATEGY_PROPERTY_NAME = "strategy"; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelHierarchicalTestExecutorServiceFactory.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelHierarchicalTestExecutorServiceFactory.java index 2b835d20fd35..d5102597a021 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelHierarchicalTestExecutorServiceFactory.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelHierarchicalTestExecutorServiceFactory.java @@ -36,16 +36,15 @@ public final class ParallelHierarchicalTestExecutorServiceFactory { * Default value for {@value #EXECUTOR_SERVICE_PROPERTY_NAME} is {@value}. */ @API(status = MAINTAINED, since = "6.2") - public static final String EXECUTOR_SERVICE_DEFAULT = "fork_join_pool"; + public static final String EXECUTOR_SERVICE_DEFAULT = "FORK_JOIN_POOL"; /** * Property name used to determine the desired * {@link ParallelExecutorServiceType ParallelExecutorServiceType}. * - *

Value must be - * {@link ParallelExecutorServiceType#FORK_JOIN_POOL fork_join_pool} or - * {@link ParallelExecutorServiceType#WORKER_THREAD_POOL worker_thread_pool}, - * ignoring case. + *

Value must be one names of enum constants defined in + * {@link ParallelExecutorServiceType}, ignoring case. If not + * specified, the default is {@value #EXECUTOR_SERVICE_DEFAULT} */ public static final String EXECUTOR_SERVICE_PROPERTY_NAME = "executor-service"; 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 82197131479e..1863e47779c9 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 @@ -270,7 +270,7 @@ public class LauncherConstants { * Default value for {@value #CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME} is {@value}. */ @API(status = MAINTAINED, since = "6.2") - public static final String CRITICAL_DISCOVERY_ISSUE_SEVERITY_DEFAULT = "error"; + public static final String CRITICAL_DISCOVERY_ISSUE_SEVERITY_DEFAULT = "ERROR"; /** * Property name used to configure the critical severity of issues From 16bd405bdc35b576a100a017f843c63bae43305a Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 12 Sep 2026 13:43:50 +0200 Subject: [PATCH 107/117] Add configuration parameter hint and group attributes to overview --- .../processor/ConfigurationMetadata.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadata.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadata.java index eb2c9d2685ec..615068c05301 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadata.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadata.java @@ -22,6 +22,8 @@ final class ConfigurationMetadata { private final List properties = new ArrayList<>(); + private final List hints = new ArrayList<>(); + List properties() { return properties; } @@ -49,4 +51,25 @@ record Deprecation( // } + record Hint( + String name, + List values, + List providers + ) { + + } + + record ValueHint( + String value, + String description + ){ + + } + + record ValueProvider( + String name, + Object parameters + ){ + + } } From 56d30f28d6ed532f0e393dc248cfca4c8b114528 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 13 Sep 2026 02:39:27 +0200 Subject: [PATCH 108/117] Render hints --- .../processor/ConfigurationMetadata.java | 38 +++++-- ...figurationMetadataAnnotationProcessor.java | 2 +- .../ConfigurationParameterAnnotatedField.java | 32 +++++- .../ConfigurationParameterHandler.java | 33 +++++- .../processor/JsonConverter.java | 105 +++++++++++++----- ...ationMetadataAnnotationProcessorTests.java | 41 ++++++- .../testcases/TypeEnumWithStringDefault.java | 2 +- 7 files changed, 208 insertions(+), 45 deletions(-) diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadata.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadata.java index 615068c05301..9adaa2b8a7dd 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadata.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadata.java @@ -28,10 +28,18 @@ List properties() { return properties; } + public List hints() { + return hints; + } + void addProperty(Property property) { properties.add(property); } + void addHint(Hint hint) { + hints.add(hint); + } + record Property( // String name, // @Nullable String type, // @@ -51,25 +59,31 @@ record Deprecation( // } - record Hint( - String name, - List values, - List providers + record Hint( // + String name, // + @Nullable List values, // + @Nullable List providers // + ) { + + } + + record ValueHint( // + Object value, // + @Nullable String description // ) { } - record ValueHint( - String value, - String description - ){ + record ValueProvider( // + String name, // + @Nullable Parameters parameters // + ) { } - record ValueProvider( - String name, - Object parameters - ){ + record Parameters( // + String target // + ) { } } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index 37ab9fca6248..e855113e94a2 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -70,7 +70,7 @@ public synchronized void init(ProcessingEnvironment environment) { super.init(environment); this.metaData = new ConfigurationMetadata(); this.configurationParameterHandler = new ConfigurationParameterHandler(metaData, - processingEnv.getElementUtils(), processingEnv.getMessager()); + processingEnv.getElementUtils(), processingEnv.getMessager(), processingEnv.getTypeUtils()); } @Override diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java index 1cc5ecf824de..2838d297eec1 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java @@ -16,14 +16,21 @@ import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.Objects; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; import org.jspecify.annotations.Nullable; @@ -32,13 +39,15 @@ final class ConfigurationParameterAnnotatedField { private final TypeElement enclosingType; private final AnnotationMirror annotationMirror; private final Elements elementUtils; + private final Types typeUtils; ConfigurationParameterAnnotatedField(VariableElement element, Elements elementUtils, TypeElement enclosingType, - AnnotationMirror annotationMirror) { + AnnotationMirror annotationMirror, Types typeUtils) { this.element = element; this.elementUtils = elementUtils; this.enclosingType = enclosingType; this.annotationMirror = annotationMirror; + this.typeUtils = typeUtils; } Element element() { @@ -65,6 +74,27 @@ Map> defaultValues() { return getValuesMap(defaultValue); } + @Nullable + List typeEnumValues() { + // TODO: Refactor + for (Map.Entry entry : annotationMirror.getElementValues().entrySet()) { + if (entry.getKey().getSimpleName().toString().equals("type")) { + AnnotationValue value = entry.getValue(); + TypeMirror typeMirror = (TypeMirror) value.getValue(); + TypeElement typeElement = (TypeElement) typeUtils.asElement(typeMirror); + if (typeElement.getKind() == ElementKind.ENUM) { + return typeElement.getEnclosedElements().stream() // + .filter(element -> element.getKind() == ElementKind.ENUM_CONSTANT) // + .map(Element::getSimpleName) // + .map(Objects::toString) // + // TODO: Also get description + .map(s -> s.toLowerCase(Locale.ROOT)).toList(); + } + } + } + return null; + } + @Nullable String typeValue() { return getStringValuesMap(annotationMirror).get("type"); diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java index f5389d9f6186..bc03b07a4dfd 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java @@ -14,6 +14,7 @@ import static javax.tools.Diagnostic.Kind.ERROR; import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getAnnotationMirror; +import java.util.List; import java.util.regex.Pattern; import javax.annotation.processing.Messager; @@ -22,22 +23,29 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; import org.jspecify.annotations.Nullable; import org.junit.platform.configuration.api.ConfigurationParameter; import org.junit.platform.configuration.processor.ConfigurationMetadata.Deprecation; +import org.junit.platform.configuration.processor.ConfigurationMetadata.Hint; import org.junit.platform.configuration.processor.ConfigurationMetadata.Property; +import org.junit.platform.configuration.processor.ConfigurationMetadata.ValueHint; +import org.junit.platform.configuration.processor.ConfigurationMetadata.ValueProvider; final class ConfigurationParameterHandler { private final ConfigurationMetadata metaData; private final Elements elementUtils; private final Messager messager; + private final Types typeUtils; - ConfigurationParameterHandler(ConfigurationMetadata metaData, Elements elementUtils, Messager messager) { + ConfigurationParameterHandler(ConfigurationMetadata metaData, Elements elementUtils, Messager messager, + Types typeUtils) { this.metaData = metaData; this.elementUtils = elementUtils; this.messager = messager; + this.typeUtils = typeUtils; } void process(RoundEnvironment roundEnv) { @@ -56,7 +64,7 @@ private void processElement(Element element) { } var annotationMirror = requireNonNull(getAnnotationMirror(element, ConfigurationParameter.class)); var field = new ConfigurationParameterAnnotatedField(variableElement, elementUtils, enclosingTypeElement, - annotationMirror); + annotationMirror, typeUtils); if (!field.isStatic() || !field.isFinal() || !(field.constantValue() instanceof String name)) { messager.printMessage(ERROR, "@ConfigurationParameter annotated field must static, final, and have constant string value", element); @@ -71,6 +79,27 @@ private void processElement(Element element) { var type = processType(field, defaultType); var property = new Property(name, type, description, sourceType, defaultValue, deprecation); metaData.addProperty(property); + + // TODO: Refactor + List values = null; + List providers = null; + + var typeEnumValues = field.typeEnumValues(); + if (typeEnumValues != null) { + values = typeEnumValues.stream().map(s -> new ValueHint(s, null)).toList(); + } + else if (Class.class.getName().equals(defaultType) && defaultValue != null) { + providers = List.of( + new ValueProvider("class-reference", new ConfigurationMetadata.Parameters(defaultValue.toString()))); + } + else if (Boolean.class.getName().equals(defaultType) && defaultValue != null) { + values = List.of(new ValueHint(true, null), new ValueHint(false, null)); + } + + if (values != null || providers != null) { + var hint = new Hint(name, values, providers); + metaData.addHint(hint); + } } private @Nullable String processType(ConfigurationParameterAnnotatedField field, @Nullable String defaultType) { diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java index d494409e27f1..217a16ce03d0 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java @@ -15,7 +15,11 @@ import java.util.function.Function; import org.junit.platform.configuration.processor.ConfigurationMetadata.Deprecation; +import org.junit.platform.configuration.processor.ConfigurationMetadata.Hint; +import org.junit.platform.configuration.processor.ConfigurationMetadata.Parameters; import org.junit.platform.configuration.processor.ConfigurationMetadata.Property; +import org.junit.platform.configuration.processor.ConfigurationMetadata.ValueHint; +import org.junit.platform.configuration.processor.ConfigurationMetadata.ValueProvider; import jakarta.json.Json; import jakarta.json.JsonArray; @@ -35,6 +39,10 @@ JsonObject toJsonObject(ConfigurationMetadata metaData) { if (!properties.isEmpty()) { builder.add("properties", toJsonArray(properties, this::toJsonObject)); } + var hints = metaData.hints(); + if (!hints.isEmpty()) { + builder.add("hints", toJsonArray(hints, this::toJsonObject)); + } return builder.build(); } @@ -71,6 +79,77 @@ private JsonObject toJsonObject(Property property) { return builder.build(); } + private JsonObject toJsonObject(Deprecation deprecation) { + var builder = factory.createObjectBuilder(); + + var reason = deprecation.reason(); + if (reason != null) { + builder.add("reason", reason); + } + + var replacement = deprecation.replacement(); + if (replacement != null) { + builder.add("replacement", replacement); + } + + var since = deprecation.since(); + if (since != null) { + builder.add("since", since); + } + + return builder.build(); + } + + private JsonObject toJsonObject(Hint hint) { + var builder = factory.createObjectBuilder(); + builder.add("name", hint.name()); + + var values = hint.values(); + if (values != null) { + builder.add("values", toJsonArray(values, this::toJsonObject)); + } + + var providers = hint.providers(); + if (providers != null) { + builder.add("providers", toJsonArray(providers, this::toJsonObject)); + } + return builder.build(); + } + + private JsonObject toJsonObject(ValueHint valueHint) { + var builder = factory.createObjectBuilder(); + addObjectValue(builder, "value", valueHint.value()); + + var description = valueHint.description(); + if (description != null) { + builder.add("description", description); + } + return builder.build(); + } + + private JsonObject toJsonObject(ValueProvider valueProvider) { + var builder = factory.createObjectBuilder(); + builder.add("name", valueProvider.name()); + + var parameters = valueProvider.parameters(); + if (parameters != null) { + builder.add("parameters", toJsonObject(parameters)); + } + return builder.build(); + } + + private JsonObject toJsonObject(Parameters parameters) { + var builder = factory.createObjectBuilder(); + builder.add("target", parameters.target()); + return builder.build(); + } + + private JsonArray toJsonArray(List properties, Function converter) { + var builder = factory.createArrayBuilder(); + properties.forEach(element -> builder.add(converter.apply(element))); + return builder.build(); + } + private void addObjectValue(JsonObjectBuilder builder, String name, Object defaultValue) { if (defaultValue instanceof Short v) { builder.add(name, v); @@ -105,30 +184,4 @@ else if (defaultValue instanceof String v) { } } - private JsonObject toJsonObject(Deprecation deprecation) { - var builder = factory.createObjectBuilder(); - - var reason = deprecation.reason(); - if (reason != null) { - builder.add("reason", reason); - } - - var replacement = deprecation.replacement(); - if (replacement != null) { - builder.add("replacement", replacement); - } - - var since = deprecation.since(); - if (since != null) { - builder.add("since", since); - } - - return builder.build(); - } - - private JsonArray toJsonArray(List properties, Function converter) { - var builder = factory.createArrayBuilder(); - properties.forEach(element -> builder.add(converter.apply(element))); - return builder.build(); - } } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index 8dee4a6c6bcb..a4be4cf393ea 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -287,7 +287,20 @@ void enumTypeWithStringDefault() { "name": "org.example.property", "type": "org.junit.platform.configuration.testcases.TypeEnumWithStringDefault.ExampleEnum", "sourceType": "org.junit.platform.configuration.testcases.TypeEnumWithStringDefault", - "defaultValue": "A" + "defaultValue": "a" + } + ], + "hints": [ + { + "name": "org.example.property", + "values": [ + { + "value": "a" + }, + { + "value": "b" + } + ] } ] }"""); @@ -359,7 +372,31 @@ void defaults() { "sourceType": "org.junit.platform.configuration.testcases.Defaults", "defaultValue": "org.junit.platform.configuration.testcases.Defaults.Example" } - ] + ], + "hints": [ + { + "name": "org.example.booleans", + "values": [ + { + "value": true + }, + { + "value": false + } + ] + }, + { + "name": "org.example.classes", + "providers": [ + { + "name": "class-reference", + "parameters": { + "target": "org.junit.platform.configuration.testcases.Defaults.Example" + } + } + ] + } + ] }"""); } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnumWithStringDefault.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnumWithStringDefault.java index d6387c89a40c..e86fbfb912b2 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnumWithStringDefault.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnumWithStringDefault.java @@ -15,7 +15,7 @@ public final class TypeEnumWithStringDefault { - @ConfigurationParameter(type = ExampleEnum.class, defaultValue = @Value(stringValue = "A")) + @ConfigurationParameter(type = ExampleEnum.class, defaultValue = @Value(stringValue = "a")) public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; enum ExampleEnum { From f4c5b83cb80912c8c5bcc743f4d0aac66a9211e8 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Wed, 16 Sep 2026 13:18:27 +0200 Subject: [PATCH 109/117] Include description with enum hint values --- .../ConfigurationParameterAnnotatedField.java | 15 ++--- .../ConfigurationParameterHandler.java | 8 ++- ...ationMetadataAnnotationProcessorTests.java | 67 ++++++++++++++++++- .../configuration/testcases/TypeEnum.java | 24 +++++++ .../TypeEnumWithDocumentedEnumValues.java | 33 +++++++++ .../testcases/TypeEnumWithStringDefault.java | 2 +- 6 files changed, 136 insertions(+), 13 deletions(-) create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnum.java create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnumWithDocumentedEnumValues.java diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java index 2838d297eec1..14c46e4f6814 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java @@ -16,9 +16,7 @@ import java.util.Collections; import java.util.List; -import java.util.Locale; import java.util.Map; -import java.util.Objects; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; @@ -75,8 +73,7 @@ Map> defaultValues() { } @Nullable - List typeEnumValues() { - // TODO: Refactor + List typeEnumValues() { for (Map.Entry entry : annotationMirror.getElementValues().entrySet()) { if (entry.getKey().getSimpleName().toString().equals("type")) { AnnotationValue value = entry.getValue(); @@ -85,16 +82,18 @@ List typeEnumValues() { if (typeElement.getKind() == ElementKind.ENUM) { return typeElement.getEnclosedElements().stream() // .filter(element -> element.getKind() == ElementKind.ENUM_CONSTANT) // - .map(Element::getSimpleName) // - .map(Objects::toString) // - // TODO: Also get description - .map(s -> s.toLowerCase(Locale.ROOT)).toList(); + .map(element -> new ReferencedTypeEnumValue(element.getSimpleName().toString(), + elementUtils.getDocComment(element))).toList(); } } } return null; } + record ReferencedTypeEnumValue(String simpleName, String docComment) { + + } + @Nullable String typeValue() { return getStringValuesMap(annotationMirror).get("type"); diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java index bc03b07a4dfd..cf60e6e02c07 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java @@ -86,7 +86,9 @@ private void processElement(Element element) { var typeEnumValues = field.typeEnumValues(); if (typeEnumValues != null) { - values = typeEnumValues.stream().map(s -> new ValueHint(s, null)).toList(); + values = typeEnumValues.stream() // + .map(s -> new ValueHint(s.simpleName(), extractFirstPragraph(s.docComment()))) // + .toList(); } else if (Class.class.getName().equals(defaultType) && defaultValue != null) { providers = List.of( @@ -109,6 +111,10 @@ else if (Boolean.class.getName().equals(defaultType) && defaultValue != null) { private @Nullable String processDescription(ConfigurationParameterAnnotatedField field) { var docComment = field.docComment(); + return extractFirstPragraph(docComment); + } + + private static @Nullable String extractFirstPragraph(@Nullable String docComment) { if (docComment == null) { return null; } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index a4be4cf393ea..8a99ac7f3fae 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -41,6 +41,8 @@ import org.junit.platform.configuration.testcases.NonFinal; import org.junit.platform.configuration.testcases.NonStatic; import org.junit.platform.configuration.testcases.NonString; +import org.junit.platform.configuration.testcases.TypeEnum; +import org.junit.platform.configuration.testcases.TypeEnumWithDocumentedEnumValues; import org.junit.platform.configuration.testcases.TypeEnumWithStringDefault; import org.junit.platform.configuration.testcases.TypeString; import org.junit.platform.configuration.testcases.Without; @@ -277,6 +279,34 @@ void stringType() { }"""); } + @Test + void enumType() { + compiler.compileWithoutError(TypeEnum.class); + assertMetaDataIsEqualTo(""" + { + "properties": [ + { + "name": "org.example.property", + "type": "org.junit.platform.configuration.testcases.TypeEnum.ExampleEnum", + "sourceType": "org.junit.platform.configuration.testcases.TypeEnum" + } + ], + "hints": [ + { + "name": "org.example.property", + "values": [ + { + "value": "A" + }, + { + "value": "B" + } + ] + } + ] + }"""); + } + @Test void enumTypeWithStringDefault() { compiler.compileWithoutError(TypeEnumWithStringDefault.class); @@ -287,7 +317,36 @@ void enumTypeWithStringDefault() { "name": "org.example.property", "type": "org.junit.platform.configuration.testcases.TypeEnumWithStringDefault.ExampleEnum", "sourceType": "org.junit.platform.configuration.testcases.TypeEnumWithStringDefault", - "defaultValue": "a" + "defaultValue": "A" + } + ], + "hints": [ + { + "name": "org.example.property", + "values": [ + { + "value": "A" + }, + { + "value": "B" + } + ] + } + ] + }"""); + } + + + @Test + void enumTypeWithDocumentedEnumValues() { + compiler.compileWithoutError(TypeEnumWithDocumentedEnumValues.class); + assertMetaDataIsEqualTo(""" + { + "properties": [ + { + "name": "org.example.property", + "type": "org.junit.platform.configuration.testcases.TypeEnumWithDocumentedEnumValues.ExampleEnum", + "sourceType": "org.junit.platform.configuration.testcases.TypeEnumWithDocumentedEnumValues" } ], "hints": [ @@ -295,10 +354,12 @@ void enumTypeWithStringDefault() { "name": "org.example.property", "values": [ { - "value": "a" + "value": "A", + "description": "A is the first option." }, { - "value": "b" + "value": "B", + "description": "B is the second option." } ] } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnum.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnum.java new file mode 100644 index 000000000000..5f6cb64fc300 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnum.java @@ -0,0 +1,24 @@ +/* + * Copyright 2026 the original author or 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.configuration.testcases; + +import org.junit.platform.configuration.api.ConfigurationParameter; +import org.junit.platform.configuration.api.ConfigurationParameter.Value; + +public final class TypeEnum { + + @ConfigurationParameter(type = ExampleEnum.class) + public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; + + enum ExampleEnum { + A, B + } +} diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnumWithDocumentedEnumValues.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnumWithDocumentedEnumValues.java new file mode 100644 index 000000000000..1afad135b66e --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnumWithDocumentedEnumValues.java @@ -0,0 +1,33 @@ +/* + * Copyright 2026 the original author or 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.configuration.testcases; + +import org.junit.platform.configuration.api.ConfigurationParameter; +import org.junit.platform.configuration.api.ConfigurationParameter.Value; + +public final class TypeEnumWithDocumentedEnumValues { + + @ConfigurationParameter(type = ExampleEnum.class) + public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; + + enum ExampleEnum { + /** + * A is the first option. + */ + A, + /** + * B is the second option. + * + *

Best used when option A failed. + */ + B + } +} diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnumWithStringDefault.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnumWithStringDefault.java index e86fbfb912b2..d6387c89a40c 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnumWithStringDefault.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnumWithStringDefault.java @@ -15,7 +15,7 @@ public final class TypeEnumWithStringDefault { - @ConfigurationParameter(type = ExampleEnum.class, defaultValue = @Value(stringValue = "a")) + @ConfigurationParameter(type = ExampleEnum.class, defaultValue = @Value(stringValue = "A")) public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; enum ExampleEnum { From 18f2b0ef3a8e1affe5745c206ea8890179513d8d Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Wed, 16 Sep 2026 14:43:08 +0200 Subject: [PATCH 110/117] Use type for hints and use javadoc from enum values --- .../api/ConfigurationParameter.java | 3 + .../processor/AnnotationMirrorUtil.java | 9 ++ .../ConfigurationParameterAnnotatedField.java | 32 ++--- .../ConfigurationParameterHandler.java | 88 +++++++++---- ...ationMetadataAnnotationProcessorTests.java | 118 ++++++++++++------ .../testcases/DefaultClassWithoutType.java | 25 ++++ .../configuration/testcases/Defaults.java | 8 +- .../configuration/testcases/TypeClass.java | 23 ++++ .../configuration/testcases/TypeEnum.java | 1 - .../TypeEnumWithDocumentedEnumValues.java | 1 - 10 files changed, 218 insertions(+), 90 deletions(-) create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultClassWithoutType.java create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeClass.java diff --git a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java index 23c3e058ac7e..46b7affdd02e 100644 --- a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java +++ b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java @@ -129,6 +129,9 @@ /** * The {@link Class} value to use as the default. + * + *

When the default is a class value, the {@link ConfigurationParameter#type()} + * should also be set. */ Class[] classValue() default {}; } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java index 95fa1c13e324..b6732d1fea6f 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java @@ -51,6 +51,15 @@ private AnnotationMirrorUtil() { .orElse(null); } + static @Nullable AnnotationValue getAnnotationValue(AnnotationMirror annotation, String name) { + for (var entry : annotation.getElementValues().entrySet()) { + if (entry.getKey().getSimpleName().toString().equals(name)) { + return entry.getValue(); + } + } + return null; + } + static Map> getValuesMap(AnnotationMirror annotation) { return annotation.getElementValues().entrySet() // .stream() // diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java index 14c46e4f6814..b0bac36625e9 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java @@ -11,6 +11,7 @@ package org.junit.platform.configuration.processor; import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getAnnotationMirror; +import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getAnnotationValue; import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getStringValuesMap; import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getValuesMap; @@ -19,10 +20,7 @@ import java.util.Map; import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; @@ -73,30 +71,14 @@ Map> defaultValues() { } @Nullable - List typeEnumValues() { - for (Map.Entry entry : annotationMirror.getElementValues().entrySet()) { - if (entry.getKey().getSimpleName().toString().equals("type")) { - AnnotationValue value = entry.getValue(); - TypeMirror typeMirror = (TypeMirror) value.getValue(); - TypeElement typeElement = (TypeElement) typeUtils.asElement(typeMirror); - if (typeElement.getKind() == ElementKind.ENUM) { - return typeElement.getEnclosedElements().stream() // - .filter(element -> element.getKind() == ElementKind.ENUM_CONSTANT) // - .map(element -> new ReferencedTypeEnumValue(element.getSimpleName().toString(), - elementUtils.getDocComment(element))).toList(); - } - } + TypeElement typeTypeElement() { + var value = getAnnotationValue(annotationMirror, "type"); + if (value == null) { + return null; } - return null; - } - - record ReferencedTypeEnumValue(String simpleName, String docComment) { - } - - @Nullable - String typeValue() { - return getStringValuesMap(annotationMirror).get("type"); + var typeMirror = (TypeMirror) value.getValue(); + return (TypeElement) typeUtils.asElement(typeMirror); } @Nullable diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java index cf60e6e02c07..460177201bc8 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java @@ -20,6 +20,7 @@ import javax.annotation.processing.Messager; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.util.Elements; @@ -29,6 +30,7 @@ import org.junit.platform.configuration.api.ConfigurationParameter; import org.junit.platform.configuration.processor.ConfigurationMetadata.Deprecation; import org.junit.platform.configuration.processor.ConfigurationMetadata.Hint; +import org.junit.platform.configuration.processor.ConfigurationMetadata.Parameters; import org.junit.platform.configuration.processor.ConfigurationMetadata.Property; import org.junit.platform.configuration.processor.ConfigurationMetadata.ValueHint; import org.junit.platform.configuration.processor.ConfigurationMetadata.ValueProvider; @@ -67,7 +69,8 @@ private void processElement(Element element) { annotationMirror, typeUtils); if (!field.isStatic() || !field.isFinal() || !(field.constantValue() instanceof String name)) { messager.printMessage(ERROR, - "@ConfigurationParameter annotated field must static, final, and have constant string value", element); + "@ConfigurationParameter annotated field must be static, final, and have constant string value", + element); return; } var description = processDescription(field); @@ -80,41 +83,84 @@ private void processElement(Element element) { var property = new Property(name, type, description, sourceType, defaultValue, deprecation); metaData.addProperty(property); - // TODO: Refactor - List values = null; - List providers = null; + var hint = processHint(name, field, defaults); + if (hint != null) { + metaData.addHint(hint); + } + } - var typeEnumValues = field.typeEnumValues(); - if (typeEnumValues != null) { - values = typeEnumValues.stream() // - .map(s -> new ValueHint(s.simpleName(), extractFirstPragraph(s.docComment()))) // - .toList(); + private @Nullable Hint processHint(String name, ConfigurationParameterAnnotatedField field, + @Nullable Default defaults) { + var typeElement = field.typeTypeElement(); + if (typeElement != null) { + var typeElementKind = typeElement.getKind(); + var typeElementName = typeElement.getQualifiedName().toString(); + if (typeElementKind == ElementKind.ENUM) { + return new Hint(name, processEnumValues(typeElement), null); + } + if (typeElementKind == ElementKind.INTERFACE) { + var parameters = new Parameters(typeElementName); + var valueprovider = new ValueProvider("class-reference", parameters); + return new Hint(name, null, List.of(valueprovider)); + } + if (Boolean.class.getName().equals(typeElementName)) { + return new Hint(name, processBooleanValues(), null); + } } - else if (Class.class.getName().equals(defaultType) && defaultValue != null) { - providers = List.of( - new ValueProvider("class-reference", new ConfigurationMetadata.Parameters(defaultValue.toString()))); + if (defaults == null) { + return null; } - else if (Boolean.class.getName().equals(defaultType) && defaultValue != null) { - values = List.of(new ValueHint(true, null), new ValueHint(false, null)); + var defaultType = defaults.defaultType(); + if (Boolean.class.getName().equals(defaultType)) { + return new Hint(name, processBooleanValues(), null); } - if (values != null || providers != null) { - var hint = new Hint(name, values, providers); - metaData.addHint(hint); + if (Class.class.getName().equals(defaultType)) { + if (typeElement == null) { + messager.printMessage(ERROR, + "@ConfigurationParameter must declare a type when the default value is a classValue", + field.element()); + return null; + } } + + return null; + } + + private static List processBooleanValues() { + var values = List.of( // + new ValueHint(true, null), // + new ValueHint(false, null) // + ); + return values; + } + + private List processEnumValues(TypeElement typeElement) { + return typeElement.getEnclosedElements().stream() // + .filter(element -> element.getKind() == ElementKind.ENUM_CONSTANT) // + .map( + element -> new ValueHint(element.getSimpleName().toString(), processDescription(element))).toList(); } private @Nullable String processType(ConfigurationParameterAnnotatedField field, @Nullable String defaultType) { - var type = field.typeValue(); - return type == null ? defaultType : type; + var value = field.typeTypeElement(); + if (value == null) { + return defaultType; + } + return value.getQualifiedName().toString(); + } + + private @Nullable String processDescription(Element element) { + var docComment = elementUtils.getDocComment(element); + return extractFirstParagraph(docComment); } private @Nullable String processDescription(ConfigurationParameterAnnotatedField field) { var docComment = field.docComment(); - return extractFirstPragraph(docComment); + return extractFirstParagraph(docComment); } - private static @Nullable String extractFirstPragraph(@Nullable String docComment) { + private static @Nullable String extractFirstParagraph(@Nullable String docComment) { if (docComment == null) { return null; } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index 8a99ac7f3fae..03f8b66c0af3 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.junit.platform.configuration.testcases.DefaultClassWithoutType; import org.junit.platform.configuration.testcases.DefaultDifferentSets; import org.junit.platform.configuration.testcases.DefaultMultipleValues; import org.junit.platform.configuration.testcases.Defaults; @@ -41,6 +42,7 @@ import org.junit.platform.configuration.testcases.NonFinal; import org.junit.platform.configuration.testcases.NonStatic; import org.junit.platform.configuration.testcases.NonString; +import org.junit.platform.configuration.testcases.TypeClass; import org.junit.platform.configuration.testcases.TypeEnum; import org.junit.platform.configuration.testcases.TypeEnumWithDocumentedEnumValues; import org.junit.platform.configuration.testcases.TypeEnumWithStringDefault; @@ -264,6 +266,34 @@ void deprecationWithDetails() { }"""); } + @Test + void classType() { + compiler.compileWithoutError(TypeClass.class); + assertMetaDataIsEqualTo(""" + { + "properties": [ + { + "name": "org.example.property", + "type": "org.junit.platform.configuration.testcases.TypeClass.Example", + "sourceType": "org.junit.platform.configuration.testcases.TypeClass" + } + ], + "hints": [ + { + "name": "org.example.property", + "providers": [ + { + "name": "class-reference", + "parameters": { + "target": "org.junit.platform.configuration.testcases.TypeClass.Example" + } + } + ] + } + ] + }"""); + } + @Test void stringType() { compiler.compileWithoutError(TypeString.class); @@ -336,35 +366,35 @@ void enumTypeWithStringDefault() { }"""); } - @Test void enumTypeWithDocumentedEnumValues() { compiler.compileWithoutError(TypeEnumWithDocumentedEnumValues.class); - assertMetaDataIsEqualTo(""" - { - "properties": [ - { - "name": "org.example.property", - "type": "org.junit.platform.configuration.testcases.TypeEnumWithDocumentedEnumValues.ExampleEnum", - "sourceType": "org.junit.platform.configuration.testcases.TypeEnumWithDocumentedEnumValues" - } - ], - "hints": [ + assertMetaDataIsEqualTo( + """ { - "name": "org.example.property", - "values": [ - { - "value": "A", - "description": "A is the first option." - }, - { - "value": "B", - "description": "B is the second option." - } + "properties": [ + { + "name": "org.example.property", + "type": "org.junit.platform.configuration.testcases.TypeEnumWithDocumentedEnumValues.ExampleEnum", + "sourceType": "org.junit.platform.configuration.testcases.TypeEnumWithDocumentedEnumValues" + } + ], + "hints": [ + { + "name": "org.example.property", + "values": [ + { + "value": "A", + "description": "A is the first option." + }, + { + "value": "B", + "description": "B is the second option." + } + ] + } ] - } - ] - }"""); + }"""); } @Test @@ -429,9 +459,9 @@ void defaults() { }, { "name": "org.example.classes", - "type": "java.lang.Class", + "type": "org.junit.platform.configuration.testcases.Defaults.Example", "sourceType": "org.junit.platform.configuration.testcases.Defaults", - "defaultValue": "org.junit.platform.configuration.testcases.Defaults.Example" + "defaultValue": "org.junit.platform.configuration.testcases.Defaults.Default" } ], "hints": [ @@ -446,17 +476,17 @@ void defaults() { } ] }, - { - "name": "org.example.classes", - "providers": [ - { - "name": "class-reference", - "parameters": { - "target": "org.junit.platform.configuration.testcases.Defaults.Example" - } - } - ] - } + { + "name": "org.example.classes", + "providers": [ + { + "name": "class-reference", + "parameters": { + "target": "org.junit.platform.configuration.testcases.Defaults.Example" + } + } + ] + } ] }"""); } @@ -467,7 +497,7 @@ void mustBeFinal() { assertThat(result.diagnostics()) // .extracting(diagnostic -> diagnostic.getMessage(Locale.ROOT)) // .contains( - "@ConfigurationParameter annotated field must static, final, and have constant string value"); + "@ConfigurationParameter annotated field must be static, final, and have constant string value"); } @Test @@ -476,7 +506,7 @@ void mustBeStatic() { assertThat(result.diagnostics()) // .extracting(diagnostic -> diagnostic.getMessage(Locale.ROOT)) // .contains( - "@ConfigurationParameter annotated field must static, final, and have constant string value"); + "@ConfigurationParameter annotated field must be static, final, and have constant string value"); } @Test @@ -485,7 +515,7 @@ void mustBeString() { assertThat(result.diagnostics()) // .extracting(diagnostic -> diagnostic.getMessage(Locale.ROOT)) // .contains( - "@ConfigurationParameter annotated field must static, final, and have constant string value"); + "@ConfigurationParameter annotated field must be static, final, and have constant string value"); } @Test @@ -504,6 +534,14 @@ void mustHaveExactlyOneDefaultValue() { .contains("@ConfigurationParameter must have exactly one default value"); } + @Test + void mustHaveTypeWhenDefaultValueIsClass() { + var result = compiler.compile(DefaultClassWithoutType.class); + assertThat(result.diagnostics()) // + .extracting(diagnostic -> diagnostic.getMessage(Locale.ROOT)) // + .contains("@ConfigurationParameter must declare a type when the default value is a classValue"); + } + private void assertMetaDataIsEqualTo(@Language("JSON") String json) { assertThat(metaData()).isEqualToNormalizingWhitespace(json); } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultClassWithoutType.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultClassWithoutType.java new file mode 100644 index 000000000000..bf253a1dda76 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultClassWithoutType.java @@ -0,0 +1,25 @@ +/* + * Copyright 2026 the original author or 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.configuration.testcases; + +import org.junit.platform.configuration.api.ConfigurationParameter; +import org.junit.platform.configuration.api.ConfigurationParameter.Value; + +public final class DefaultClassWithoutType { + + @ConfigurationParameter(defaultValue = @Value(classValue = Default.class)) + public static final String CLASSES_PROPERTY_NAME = "org.example.classes"; + + private static class Default { + + } + +} diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Defaults.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Defaults.java index 9be84a44c1af..7dfe014810a8 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Defaults.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Defaults.java @@ -42,10 +42,14 @@ public final class Defaults { @ConfigurationParameter(defaultValue = @Value(stringValue = "default")) public static final String STRING_PROPERTY_NAME = "org.example.strings"; - @ConfigurationParameter(defaultValue = @Value(classValue = Example.class)) + @ConfigurationParameter(type = Example.class, defaultValue = @Value(classValue = Default.class)) public static final String CLASSES_PROPERTY_NAME = "org.example.classes"; - private record Example() { + private interface Example { + + } + + private final class Default implements Example { } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeClass.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeClass.java new file mode 100644 index 000000000000..36e28908068a --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeClass.java @@ -0,0 +1,23 @@ +/* + * Copyright 2026 the original author or 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.configuration.testcases; + +import org.junit.platform.configuration.api.ConfigurationParameter; + +public final class TypeClass { + + @ConfigurationParameter(type = Example.class) + public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; + + interface Example { + + } +} diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnum.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnum.java index 5f6cb64fc300..d4685f2c9ef0 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnum.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnum.java @@ -11,7 +11,6 @@ package org.junit.platform.configuration.testcases; import org.junit.platform.configuration.api.ConfigurationParameter; -import org.junit.platform.configuration.api.ConfigurationParameter.Value; public final class TypeEnum { diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnumWithDocumentedEnumValues.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnumWithDocumentedEnumValues.java index 1afad135b66e..4f0c411218e0 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnumWithDocumentedEnumValues.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeEnumWithDocumentedEnumValues.java @@ -11,7 +11,6 @@ package org.junit.platform.configuration.testcases; import org.junit.platform.configuration.api.ConfigurationParameter; -import org.junit.platform.configuration.api.ConfigurationParameter.Value; public final class TypeEnumWithDocumentedEnumValues { From 965f0c0dd738c3e7f4f7ea8d0fe1b0b152bc5c09 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Wed, 16 Sep 2026 15:26:35 +0200 Subject: [PATCH 111/117] WIP: Support class reference hints --- .../ConfigurationParameterHandler.java | 126 ++++++++++-------- ...ationMetadataAnnotationProcessorTests.java | 91 +++++++++++++ .../TypeAbstractClassWithDefaultClass.java | 28 ++++ .../testcases/TypeInterface.java | 23 ++++ .../TypeInterfaceWithDefaultClass.java | 28 ++++ 5 files changed, 238 insertions(+), 58 deletions(-) create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeAbstractClassWithDefaultClass.java create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeInterface.java create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeInterfaceWithDefaultClass.java diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java index 460177201bc8..2a6203edb2c1 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java @@ -89,59 +89,6 @@ private void processElement(Element element) { } } - private @Nullable Hint processHint(String name, ConfigurationParameterAnnotatedField field, - @Nullable Default defaults) { - var typeElement = field.typeTypeElement(); - if (typeElement != null) { - var typeElementKind = typeElement.getKind(); - var typeElementName = typeElement.getQualifiedName().toString(); - if (typeElementKind == ElementKind.ENUM) { - return new Hint(name, processEnumValues(typeElement), null); - } - if (typeElementKind == ElementKind.INTERFACE) { - var parameters = new Parameters(typeElementName); - var valueprovider = new ValueProvider("class-reference", parameters); - return new Hint(name, null, List.of(valueprovider)); - } - if (Boolean.class.getName().equals(typeElementName)) { - return new Hint(name, processBooleanValues(), null); - } - } - if (defaults == null) { - return null; - } - var defaultType = defaults.defaultType(); - if (Boolean.class.getName().equals(defaultType)) { - return new Hint(name, processBooleanValues(), null); - } - - if (Class.class.getName().equals(defaultType)) { - if (typeElement == null) { - messager.printMessage(ERROR, - "@ConfigurationParameter must declare a type when the default value is a classValue", - field.element()); - return null; - } - } - - return null; - } - - private static List processBooleanValues() { - var values = List.of( // - new ValueHint(true, null), // - new ValueHint(false, null) // - ); - return values; - } - - private List processEnumValues(TypeElement typeElement) { - return typeElement.getEnclosedElements().stream() // - .filter(element -> element.getKind() == ElementKind.ENUM_CONSTANT) // - .map( - element -> new ValueHint(element.getSimpleName().toString(), processDescription(element))).toList(); - } - private @Nullable String processType(ConfigurationParameterAnnotatedField field, @Nullable String defaultType) { var value = field.typeTypeElement(); if (value == null) { @@ -150,11 +97,6 @@ private List processEnumValues(TypeElement typeElement) { return value.getQualifiedName().toString(); } - private @Nullable String processDescription(Element element) { - var docComment = elementUtils.getDocComment(element); - return extractFirstParagraph(docComment); - } - private @Nullable String processDescription(ConfigurationParameterAnnotatedField field) { var docComment = field.docComment(); return extractFirstParagraph(docComment); @@ -237,4 +179,72 @@ private record Default(String defaultType, Object value) { return null; } + private @Nullable Hint processHint(String name, ConfigurationParameterAnnotatedField field, + @Nullable Default defaults) { + + // Derive hint from ConfigurationParameter.type value + var typeElement = field.typeTypeElement(); + if (typeElement != null) { + var typeElementKind = typeElement.getKind(); + var typeElementName = typeElement.getQualifiedName().toString(); + if (typeElementKind == ElementKind.ENUM) { + return new Hint(name, processEnumValues(typeElement), null); + } + // It is not possible to determine hints for abstract classes + if (typeElementKind == ElementKind.INTERFACE) { + return new Hint(name, null, processClassValues(typeElementName)); + } + if (Boolean.class.getName().equals(typeElementName)) { + return new Hint(name, processBooleanValues(), null); + } + } + + // Derive hints from ConfigurationParameter.defaultValue if available. + if (defaults == null) { + return null; + } + var defaultType = defaults.defaultType(); + if (Boolean.class.getName().equals(defaultType)) { + return new Hint(name, processBooleanValues(), null); + } + if (Class.class.getName().equals(defaultType)) { + if (typeElement == null) { + messager.printMessage(ERROR, + "@ConfigurationParameter must declare a type when the default value is a classValue", + field.element()); + return null; + } + // TODO: Works for abstract classes, but only when there is a default. + // TODO: Consider limiting the allowed type values to primitives, enums and interfaces. + var typeElementName = typeElement.getQualifiedName().toString(); + return new Hint(name, null, processClassValues(typeElementName)); + } + + return null; + } + + private static List processClassValues(String typeElementName) { + var parameters = new Parameters(typeElementName); + var valueprovider = new ValueProvider("class-reference", parameters); + return List.of(valueprovider); + } + + private static List processBooleanValues() { + return List.of( // + new ValueHint(true, null), // + new ValueHint(false, null) // + ); + } + + private List processEnumValues(TypeElement typeElement) { + return typeElement.getEnclosedElements().stream() // + .filter(element -> element.getKind() == ElementKind.ENUM_CONSTANT) // + .map( + element -> new ValueHint(element.getSimpleName().toString(), processDescription(element))).toList(); + } + + private @Nullable String processDescription(Element element) { + var docComment = elementUtils.getDocComment(element); + return extractFirstParagraph(docComment); + } } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index 03f8b66c0af3..d63e8d2d6150 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -42,10 +42,13 @@ import org.junit.platform.configuration.testcases.NonFinal; import org.junit.platform.configuration.testcases.NonStatic; import org.junit.platform.configuration.testcases.NonString; +import org.junit.platform.configuration.testcases.TypeAbstractClassWithDefaultClass; import org.junit.platform.configuration.testcases.TypeClass; import org.junit.platform.configuration.testcases.TypeEnum; import org.junit.platform.configuration.testcases.TypeEnumWithDocumentedEnumValues; import org.junit.platform.configuration.testcases.TypeEnumWithStringDefault; +import org.junit.platform.configuration.testcases.TypeInterface; +import org.junit.platform.configuration.testcases.TypeInterfaceWithDefaultClass; import org.junit.platform.configuration.testcases.TypeString; import org.junit.platform.configuration.testcases.Without; @@ -294,6 +297,94 @@ void classType() { }"""); } + @Test + void classTypeAbstractWithDefaultClass() { + compiler.compileWithoutError(TypeAbstractClassWithDefaultClass.class); + assertMetaDataIsEqualTo( + """ + { + "properties": [ + { + "name": "org.example.classes", + "type": "org.junit.platform.configuration.testcases.TypeAbstractClassWithDefaultClass.Example", + "sourceType": "org.junit.platform.configuration.testcases.TypeAbstractClassWithDefaultClass", + "defaultValue": "org.junit.platform.configuration.testcases.TypeAbstractClassWithDefaultClass.Default" + } + ], + "hints": [ + { + "name": "org.example.classes", + "providers": [ + { + "name": "class-reference", + "parameters": { + "target": "org.junit.platform.configuration.testcases.TypeAbstractClassWithDefaultClass.Example" + } + } + ] + } + ] + }"""); + } + + @Test + void interfaceType() { + compiler.compileWithoutError(TypeInterface.class); + assertMetaDataIsEqualTo(""" + { + "properties": [ + { + "name": "org.example.property", + "type": "org.junit.platform.configuration.testcases.TypeInterface.Example", + "sourceType": "org.junit.platform.configuration.TypeInterface.TypeClass" + } + ], + "hints": [ + { + "name": "org.example.property", + "providers": [ + { + "name": "class-reference", + "parameters": { + "target": "org.junit.platform.configuration.testcases.TypeClass.Example" + } + } + ] + } + ] + }"""); + } + + @Test + void interfaceTypeWithDefaultClass() { + compiler.compileWithoutError(TypeInterfaceWithDefaultClass.class); + assertMetaDataIsEqualTo( + """ + { + "properties": [ + { + "name": "org.example.classes", + "type": "org.junit.platform.configuration.testcases.TypeInterfaceWithDefaultClass.Example", + "sourceType": "org.junit.platform.configuration.testcases.TypeInterfaceWithDefaultClass", + "defaultValue": "org.junit.platform.configuration.testcases.TypeInterfaceWithDefaultClass.Default" + } + ], + "hints": [ + { + "name": "org.example.classes", + "providers": [ + { + "name": "class-reference", + "parameters": { + "target": "org.junit.platform.configuration.testcases.TypeInterfaceWithDefaultClass.Example" + } + } + ] + } + ] + }"""); + } + @Test void stringType() { compiler.compileWithoutError(TypeString.class); diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeAbstractClassWithDefaultClass.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeAbstractClassWithDefaultClass.java new file mode 100644 index 000000000000..cbdd9c7de8a2 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeAbstractClassWithDefaultClass.java @@ -0,0 +1,28 @@ +/* + * Copyright 2026 the original author or 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.configuration.testcases; + +import org.junit.platform.configuration.api.ConfigurationParameter; +import org.junit.platform.configuration.api.ConfigurationParameter.Value; + +public final class TypeAbstractClassWithDefaultClass { + + @ConfigurationParameter(type = Example.class, defaultValue = @Value(classValue = Default.class)) + public static final String CLASSES_PROPERTY_NAME = "org.example.classes"; + + private static abstract class Example { + + } + private static class Default extends Example { + + } + +} diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeInterface.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeInterface.java new file mode 100644 index 000000000000..d4b7c96f1b19 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeInterface.java @@ -0,0 +1,23 @@ +/* + * Copyright 2026 the original author or 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.configuration.testcases; + +import org.junit.platform.configuration.api.ConfigurationParameter; + +public final class TypeInterface { + + @ConfigurationParameter(type = Example.class) + public static final String CLASSES_PROPERTY_NAME = "org.example.classes"; + + private interface Example { + + } +} diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeInterfaceWithDefaultClass.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeInterfaceWithDefaultClass.java new file mode 100644 index 000000000000..19c8fd927ece --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeInterfaceWithDefaultClass.java @@ -0,0 +1,28 @@ +/* + * Copyright 2026 the original author or 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.configuration.testcases; + +import org.junit.platform.configuration.api.ConfigurationParameter; +import org.junit.platform.configuration.api.ConfigurationParameter.Value; + +public final class TypeInterfaceWithDefaultClass { + + @ConfigurationParameter(type = Example.class, defaultValue = @Value(classValue = Default.class)) + public static final String CLASSES_PROPERTY_NAME = "org.example.classes"; + + private interface Example { + + } + private static class Default implements Example { + + } + +} From 57d124a13777ff13bf62c33b27054cb94c27e1a5 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 18 Sep 2026 13:33:10 +0200 Subject: [PATCH 112/117] Fix merge --- .../api/ConfigurationParameter.java | 3 + .../processor/AnnotationMirrorUtil.java | 9 + .../processor/ConfigurationMetadata.java | 37 +++ ...figurationMetadataAnnotationProcessor.java | 2 +- .../ConfigurationParameterAnnotatedField.java | 17 +- .../ConfigurationParameterHandler.java | 101 +++++++- .../processor/JsonConverter.java | 105 ++++++-- ...ationMetadataAnnotationProcessorTests.java | 239 +++++++++++++++++- .../configuration/testcases/Defaults.java | 8 +- 9 files changed, 478 insertions(+), 43 deletions(-) diff --git a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java index b33ff0b6c013..5438eb32f574 100644 --- a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java +++ b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java @@ -129,6 +129,9 @@ /** * The {@link Class} value to use as the default. + * + *

When the default is a class value, the {@link ConfigurationParameter#type()} + * should also be set. */ Class[] classValue() default {}; } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java index 95fa1c13e324..b6732d1fea6f 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java @@ -51,6 +51,15 @@ private AnnotationMirrorUtil() { .orElse(null); } + static @Nullable AnnotationValue getAnnotationValue(AnnotationMirror annotation, String name) { + for (var entry : annotation.getElementValues().entrySet()) { + if (entry.getKey().getSimpleName().toString().equals(name)) { + return entry.getValue(); + } + } + return null; + } + static Map> getValuesMap(AnnotationMirror annotation) { return annotation.getElementValues().entrySet() // .stream() // diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadata.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadata.java index eb2c9d2685ec..9adaa2b8a7dd 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadata.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadata.java @@ -22,14 +22,24 @@ final class ConfigurationMetadata { private final List properties = new ArrayList<>(); + private final List hints = new ArrayList<>(); + List properties() { return properties; } + public List hints() { + return hints; + } + void addProperty(Property property) { properties.add(property); } + void addHint(Hint hint) { + hints.add(hint); + } + record Property( // String name, // @Nullable String type, // @@ -49,4 +59,31 @@ record Deprecation( // } + record Hint( // + String name, // + @Nullable List values, // + @Nullable List providers // + ) { + + } + + record ValueHint( // + Object value, // + @Nullable String description // + ) { + + } + + record ValueProvider( // + String name, // + @Nullable Parameters parameters // + ) { + + } + + record Parameters( // + String target // + ) { + + } } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java index 50b39f7ae3ef..b899b17ef943 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessor.java @@ -43,7 +43,7 @@ public synchronized void init(ProcessingEnvironment environment) { super.init(environment); this.metaData = new ConfigurationMetadata(); this.configurationParameterHandler = new ConfigurationParameterHandler(metaData, - processingEnv.getElementUtils(), processingEnv.getMessager()); + processingEnv.getElementUtils(), processingEnv.getMessager(), processingEnv.getTypeUtils()); } @Override diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java index 1cc5ecf824de..b0bac36625e9 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java @@ -11,6 +11,7 @@ package org.junit.platform.configuration.processor; import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getAnnotationMirror; +import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getAnnotationValue; import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getStringValuesMap; import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getValuesMap; @@ -23,7 +24,9 @@ import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; import org.jspecify.annotations.Nullable; @@ -32,13 +35,15 @@ final class ConfigurationParameterAnnotatedField { private final TypeElement enclosingType; private final AnnotationMirror annotationMirror; private final Elements elementUtils; + private final Types typeUtils; ConfigurationParameterAnnotatedField(VariableElement element, Elements elementUtils, TypeElement enclosingType, - AnnotationMirror annotationMirror) { + AnnotationMirror annotationMirror, Types typeUtils) { this.element = element; this.elementUtils = elementUtils; this.enclosingType = enclosingType; this.annotationMirror = annotationMirror; + this.typeUtils = typeUtils; } Element element() { @@ -66,8 +71,14 @@ Map> defaultValues() { } @Nullable - String typeValue() { - return getStringValuesMap(annotationMirror).get("type"); + TypeElement typeTypeElement() { + var value = getAnnotationValue(annotationMirror, "type"); + if (value == null) { + return null; + } + + var typeMirror = (TypeMirror) value.getValue(); + return (TypeElement) typeUtils.asElement(typeMirror); } @Nullable diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java index f5389d9f6186..2a6203edb2c1 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java @@ -14,30 +14,40 @@ import static javax.tools.Diagnostic.Kind.ERROR; import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getAnnotationMirror; +import java.util.List; import java.util.regex.Pattern; import javax.annotation.processing.Messager; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; import org.jspecify.annotations.Nullable; import org.junit.platform.configuration.api.ConfigurationParameter; import org.junit.platform.configuration.processor.ConfigurationMetadata.Deprecation; +import org.junit.platform.configuration.processor.ConfigurationMetadata.Hint; +import org.junit.platform.configuration.processor.ConfigurationMetadata.Parameters; import org.junit.platform.configuration.processor.ConfigurationMetadata.Property; +import org.junit.platform.configuration.processor.ConfigurationMetadata.ValueHint; +import org.junit.platform.configuration.processor.ConfigurationMetadata.ValueProvider; final class ConfigurationParameterHandler { private final ConfigurationMetadata metaData; private final Elements elementUtils; private final Messager messager; + private final Types typeUtils; - ConfigurationParameterHandler(ConfigurationMetadata metaData, Elements elementUtils, Messager messager) { + ConfigurationParameterHandler(ConfigurationMetadata metaData, Elements elementUtils, Messager messager, + Types typeUtils) { this.metaData = metaData; this.elementUtils = elementUtils; this.messager = messager; + this.typeUtils = typeUtils; } void process(RoundEnvironment roundEnv) { @@ -56,10 +66,11 @@ private void processElement(Element element) { } var annotationMirror = requireNonNull(getAnnotationMirror(element, ConfigurationParameter.class)); var field = new ConfigurationParameterAnnotatedField(variableElement, elementUtils, enclosingTypeElement, - annotationMirror); + annotationMirror, typeUtils); if (!field.isStatic() || !field.isFinal() || !(field.constantValue() instanceof String name)) { messager.printMessage(ERROR, - "@ConfigurationParameter annotated field must static, final, and have constant string value", element); + "@ConfigurationParameter annotated field must be static, final, and have constant string value", + element); return; } var description = processDescription(field); @@ -71,15 +82,27 @@ private void processElement(Element element) { var type = processType(field, defaultType); var property = new Property(name, type, description, sourceType, defaultValue, deprecation); metaData.addProperty(property); + + var hint = processHint(name, field, defaults); + if (hint != null) { + metaData.addHint(hint); + } } private @Nullable String processType(ConfigurationParameterAnnotatedField field, @Nullable String defaultType) { - var type = field.typeValue(); - return type == null ? defaultType : type; + var value = field.typeTypeElement(); + if (value == null) { + return defaultType; + } + return value.getQualifiedName().toString(); } private @Nullable String processDescription(ConfigurationParameterAnnotatedField field) { var docComment = field.docComment(); + return extractFirstParagraph(docComment); + } + + private static @Nullable String extractFirstParagraph(@Nullable String docComment) { if (docComment == null) { return null; } @@ -156,4 +179,72 @@ private record Default(String defaultType, Object value) { return null; } + private @Nullable Hint processHint(String name, ConfigurationParameterAnnotatedField field, + @Nullable Default defaults) { + + // Derive hint from ConfigurationParameter.type value + var typeElement = field.typeTypeElement(); + if (typeElement != null) { + var typeElementKind = typeElement.getKind(); + var typeElementName = typeElement.getQualifiedName().toString(); + if (typeElementKind == ElementKind.ENUM) { + return new Hint(name, processEnumValues(typeElement), null); + } + // It is not possible to determine hints for abstract classes + if (typeElementKind == ElementKind.INTERFACE) { + return new Hint(name, null, processClassValues(typeElementName)); + } + if (Boolean.class.getName().equals(typeElementName)) { + return new Hint(name, processBooleanValues(), null); + } + } + + // Derive hints from ConfigurationParameter.defaultValue if available. + if (defaults == null) { + return null; + } + var defaultType = defaults.defaultType(); + if (Boolean.class.getName().equals(defaultType)) { + return new Hint(name, processBooleanValues(), null); + } + if (Class.class.getName().equals(defaultType)) { + if (typeElement == null) { + messager.printMessage(ERROR, + "@ConfigurationParameter must declare a type when the default value is a classValue", + field.element()); + return null; + } + // TODO: Works for abstract classes, but only when there is a default. + // TODO: Consider limiting the allowed type values to primitives, enums and interfaces. + var typeElementName = typeElement.getQualifiedName().toString(); + return new Hint(name, null, processClassValues(typeElementName)); + } + + return null; + } + + private static List processClassValues(String typeElementName) { + var parameters = new Parameters(typeElementName); + var valueprovider = new ValueProvider("class-reference", parameters); + return List.of(valueprovider); + } + + private static List processBooleanValues() { + return List.of( // + new ValueHint(true, null), // + new ValueHint(false, null) // + ); + } + + private List processEnumValues(TypeElement typeElement) { + return typeElement.getEnclosedElements().stream() // + .filter(element -> element.getKind() == ElementKind.ENUM_CONSTANT) // + .map( + element -> new ValueHint(element.getSimpleName().toString(), processDescription(element))).toList(); + } + + private @Nullable String processDescription(Element element) { + var docComment = elementUtils.getDocComment(element); + return extractFirstParagraph(docComment); + } } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java index d494409e27f1..217a16ce03d0 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/JsonConverter.java @@ -15,7 +15,11 @@ import java.util.function.Function; import org.junit.platform.configuration.processor.ConfigurationMetadata.Deprecation; +import org.junit.platform.configuration.processor.ConfigurationMetadata.Hint; +import org.junit.platform.configuration.processor.ConfigurationMetadata.Parameters; import org.junit.platform.configuration.processor.ConfigurationMetadata.Property; +import org.junit.platform.configuration.processor.ConfigurationMetadata.ValueHint; +import org.junit.platform.configuration.processor.ConfigurationMetadata.ValueProvider; import jakarta.json.Json; import jakarta.json.JsonArray; @@ -35,6 +39,10 @@ JsonObject toJsonObject(ConfigurationMetadata metaData) { if (!properties.isEmpty()) { builder.add("properties", toJsonArray(properties, this::toJsonObject)); } + var hints = metaData.hints(); + if (!hints.isEmpty()) { + builder.add("hints", toJsonArray(hints, this::toJsonObject)); + } return builder.build(); } @@ -71,6 +79,77 @@ private JsonObject toJsonObject(Property property) { return builder.build(); } + private JsonObject toJsonObject(Deprecation deprecation) { + var builder = factory.createObjectBuilder(); + + var reason = deprecation.reason(); + if (reason != null) { + builder.add("reason", reason); + } + + var replacement = deprecation.replacement(); + if (replacement != null) { + builder.add("replacement", replacement); + } + + var since = deprecation.since(); + if (since != null) { + builder.add("since", since); + } + + return builder.build(); + } + + private JsonObject toJsonObject(Hint hint) { + var builder = factory.createObjectBuilder(); + builder.add("name", hint.name()); + + var values = hint.values(); + if (values != null) { + builder.add("values", toJsonArray(values, this::toJsonObject)); + } + + var providers = hint.providers(); + if (providers != null) { + builder.add("providers", toJsonArray(providers, this::toJsonObject)); + } + return builder.build(); + } + + private JsonObject toJsonObject(ValueHint valueHint) { + var builder = factory.createObjectBuilder(); + addObjectValue(builder, "value", valueHint.value()); + + var description = valueHint.description(); + if (description != null) { + builder.add("description", description); + } + return builder.build(); + } + + private JsonObject toJsonObject(ValueProvider valueProvider) { + var builder = factory.createObjectBuilder(); + builder.add("name", valueProvider.name()); + + var parameters = valueProvider.parameters(); + if (parameters != null) { + builder.add("parameters", toJsonObject(parameters)); + } + return builder.build(); + } + + private JsonObject toJsonObject(Parameters parameters) { + var builder = factory.createObjectBuilder(); + builder.add("target", parameters.target()); + return builder.build(); + } + + private JsonArray toJsonArray(List properties, Function converter) { + var builder = factory.createArrayBuilder(); + properties.forEach(element -> builder.add(converter.apply(element))); + return builder.build(); + } + private void addObjectValue(JsonObjectBuilder builder, String name, Object defaultValue) { if (defaultValue instanceof Short v) { builder.add(name, v); @@ -105,30 +184,4 @@ else if (defaultValue instanceof String v) { } } - private JsonObject toJsonObject(Deprecation deprecation) { - var builder = factory.createObjectBuilder(); - - var reason = deprecation.reason(); - if (reason != null) { - builder.add("reason", reason); - } - - var replacement = deprecation.replacement(); - if (replacement != null) { - builder.add("replacement", replacement); - } - - var since = deprecation.since(); - if (since != null) { - builder.add("since", since); - } - - return builder.build(); - } - - private JsonArray toJsonArray(List properties, Function converter) { - var builder = factory.createArrayBuilder(); - properties.forEach(element -> builder.add(converter.apply(element))); - return builder.build(); - } } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index 8dee4a6c6bcb..d63e8d2d6150 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.junit.platform.configuration.testcases.DefaultClassWithoutType; import org.junit.platform.configuration.testcases.DefaultDifferentSets; import org.junit.platform.configuration.testcases.DefaultMultipleValues; import org.junit.platform.configuration.testcases.Defaults; @@ -41,7 +42,13 @@ import org.junit.platform.configuration.testcases.NonFinal; import org.junit.platform.configuration.testcases.NonStatic; import org.junit.platform.configuration.testcases.NonString; +import org.junit.platform.configuration.testcases.TypeAbstractClassWithDefaultClass; +import org.junit.platform.configuration.testcases.TypeClass; +import org.junit.platform.configuration.testcases.TypeEnum; +import org.junit.platform.configuration.testcases.TypeEnumWithDocumentedEnumValues; import org.junit.platform.configuration.testcases.TypeEnumWithStringDefault; +import org.junit.platform.configuration.testcases.TypeInterface; +import org.junit.platform.configuration.testcases.TypeInterfaceWithDefaultClass; import org.junit.platform.configuration.testcases.TypeString; import org.junit.platform.configuration.testcases.Without; @@ -262,6 +269,122 @@ void deprecationWithDetails() { }"""); } + @Test + void classType() { + compiler.compileWithoutError(TypeClass.class); + assertMetaDataIsEqualTo(""" + { + "properties": [ + { + "name": "org.example.property", + "type": "org.junit.platform.configuration.testcases.TypeClass.Example", + "sourceType": "org.junit.platform.configuration.testcases.TypeClass" + } + ], + "hints": [ + { + "name": "org.example.property", + "providers": [ + { + "name": "class-reference", + "parameters": { + "target": "org.junit.platform.configuration.testcases.TypeClass.Example" + } + } + ] + } + ] + }"""); + } + + @Test + void classTypeAbstractWithDefaultClass() { + compiler.compileWithoutError(TypeAbstractClassWithDefaultClass.class); + assertMetaDataIsEqualTo( + """ + { + "properties": [ + { + "name": "org.example.classes", + "type": "org.junit.platform.configuration.testcases.TypeAbstractClassWithDefaultClass.Example", + "sourceType": "org.junit.platform.configuration.testcases.TypeAbstractClassWithDefaultClass", + "defaultValue": "org.junit.platform.configuration.testcases.TypeAbstractClassWithDefaultClass.Default" + } + ], + "hints": [ + { + "name": "org.example.classes", + "providers": [ + { + "name": "class-reference", + "parameters": { + "target": "org.junit.platform.configuration.testcases.TypeAbstractClassWithDefaultClass.Example" + } + } + ] + } + ] + }"""); + } + + @Test + void interfaceType() { + compiler.compileWithoutError(TypeInterface.class); + assertMetaDataIsEqualTo(""" + { + "properties": [ + { + "name": "org.example.property", + "type": "org.junit.platform.configuration.testcases.TypeInterface.Example", + "sourceType": "org.junit.platform.configuration.TypeInterface.TypeClass" + } + ], + "hints": [ + { + "name": "org.example.property", + "providers": [ + { + "name": "class-reference", + "parameters": { + "target": "org.junit.platform.configuration.testcases.TypeClass.Example" + } + } + ] + } + ] + }"""); + } + + @Test + void interfaceTypeWithDefaultClass() { + compiler.compileWithoutError(TypeInterfaceWithDefaultClass.class); + assertMetaDataIsEqualTo( + """ + { + "properties": [ + { + "name": "org.example.classes", + "type": "org.junit.platform.configuration.testcases.TypeInterfaceWithDefaultClass.Example", + "sourceType": "org.junit.platform.configuration.testcases.TypeInterfaceWithDefaultClass", + "defaultValue": "org.junit.platform.configuration.testcases.TypeInterfaceWithDefaultClass.Default" + } + ], + "hints": [ + { + "name": "org.example.classes", + "providers": [ + { + "name": "class-reference", + "parameters": { + "target": "org.junit.platform.configuration.testcases.TypeInterfaceWithDefaultClass.Example" + } + } + ] + } + ] + }"""); + } + @Test void stringType() { compiler.compileWithoutError(TypeString.class); @@ -277,6 +400,34 @@ void stringType() { }"""); } + @Test + void enumType() { + compiler.compileWithoutError(TypeEnum.class); + assertMetaDataIsEqualTo(""" + { + "properties": [ + { + "name": "org.example.property", + "type": "org.junit.platform.configuration.testcases.TypeEnum.ExampleEnum", + "sourceType": "org.junit.platform.configuration.testcases.TypeEnum" + } + ], + "hints": [ + { + "name": "org.example.property", + "values": [ + { + "value": "A" + }, + { + "value": "B" + } + ] + } + ] + }"""); + } + @Test void enumTypeWithStringDefault() { compiler.compileWithoutError(TypeEnumWithStringDefault.class); @@ -289,10 +440,54 @@ void enumTypeWithStringDefault() { "sourceType": "org.junit.platform.configuration.testcases.TypeEnumWithStringDefault", "defaultValue": "A" } + ], + "hints": [ + { + "name": "org.example.property", + "values": [ + { + "value": "A" + }, + { + "value": "B" + } + ] + } ] }"""); } + @Test + void enumTypeWithDocumentedEnumValues() { + compiler.compileWithoutError(TypeEnumWithDocumentedEnumValues.class); + assertMetaDataIsEqualTo( + """ + { + "properties": [ + { + "name": "org.example.property", + "type": "org.junit.platform.configuration.testcases.TypeEnumWithDocumentedEnumValues.ExampleEnum", + "sourceType": "org.junit.platform.configuration.testcases.TypeEnumWithDocumentedEnumValues" + } + ], + "hints": [ + { + "name": "org.example.property", + "values": [ + { + "value": "A", + "description": "A is the first option." + }, + { + "value": "B", + "description": "B is the second option." + } + ] + } + ] + }"""); + } + @Test void defaults() { compiler.compileWithoutError(Defaults.class); @@ -355,11 +550,35 @@ void defaults() { }, { "name": "org.example.classes", - "type": "java.lang.Class", + "type": "org.junit.platform.configuration.testcases.Defaults.Example", "sourceType": "org.junit.platform.configuration.testcases.Defaults", - "defaultValue": "org.junit.platform.configuration.testcases.Defaults.Example" + "defaultValue": "org.junit.platform.configuration.testcases.Defaults.Default" } - ] + ], + "hints": [ + { + "name": "org.example.booleans", + "values": [ + { + "value": true + }, + { + "value": false + } + ] + }, + { + "name": "org.example.classes", + "providers": [ + { + "name": "class-reference", + "parameters": { + "target": "org.junit.platform.configuration.testcases.Defaults.Example" + } + } + ] + } + ] }"""); } @@ -369,7 +588,7 @@ void mustBeFinal() { assertThat(result.diagnostics()) // .extracting(diagnostic -> diagnostic.getMessage(Locale.ROOT)) // .contains( - "@ConfigurationParameter annotated field must static, final, and have constant string value"); + "@ConfigurationParameter annotated field must be static, final, and have constant string value"); } @Test @@ -378,7 +597,7 @@ void mustBeStatic() { assertThat(result.diagnostics()) // .extracting(diagnostic -> diagnostic.getMessage(Locale.ROOT)) // .contains( - "@ConfigurationParameter annotated field must static, final, and have constant string value"); + "@ConfigurationParameter annotated field must be static, final, and have constant string value"); } @Test @@ -387,7 +606,7 @@ void mustBeString() { assertThat(result.diagnostics()) // .extracting(diagnostic -> diagnostic.getMessage(Locale.ROOT)) // .contains( - "@ConfigurationParameter annotated field must static, final, and have constant string value"); + "@ConfigurationParameter annotated field must be static, final, and have constant string value"); } @Test @@ -406,6 +625,14 @@ void mustHaveExactlyOneDefaultValue() { .contains("@ConfigurationParameter must have exactly one default value"); } + @Test + void mustHaveTypeWhenDefaultValueIsClass() { + var result = compiler.compile(DefaultClassWithoutType.class); + assertThat(result.diagnostics()) // + .extracting(diagnostic -> diagnostic.getMessage(Locale.ROOT)) // + .contains("@ConfigurationParameter must declare a type when the default value is a classValue"); + } + private void assertMetaDataIsEqualTo(@Language("JSON") String json) { assertThat(metaData()).isEqualToNormalizingWhitespace(json); } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Defaults.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Defaults.java index 9be84a44c1af..7dfe014810a8 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Defaults.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Defaults.java @@ -42,10 +42,14 @@ public final class Defaults { @ConfigurationParameter(defaultValue = @Value(stringValue = "default")) public static final String STRING_PROPERTY_NAME = "org.example.strings"; - @ConfigurationParameter(defaultValue = @Value(classValue = Example.class)) + @ConfigurationParameter(type = Example.class, defaultValue = @Value(classValue = Default.class)) public static final String CLASSES_PROPERTY_NAME = "org.example.classes"; - private record Example() { + private interface Example { + + } + + private final class Default implements Example { } From c707345a92fc3aca2ae0d41387e68954883d16f8 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 18 Sep 2026 14:07:27 +0200 Subject: [PATCH 113/117] Support class reference hints --- .../src/main/java/module-info.java | 5 ++ .../ConfigurationParameterHandler.java | 4 -- ...ationMetadataAnnotationProcessorTests.java | 58 +++++++++++++------ .../testcases/DefaultClassWithoutType.java | 2 +- .../testcases/TypeAbstractClass.java | 23 ++++++++ .../TypeAbstractClassWithDefaultClass.java | 2 +- .../testcases/TypeInterface.java | 2 +- .../TypeInterfaceWithDefaultClass.java | 2 +- 8 files changed, 71 insertions(+), 27 deletions(-) create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeAbstractClass.java diff --git a/junit-platform-configuration-processor/src/main/java/module-info.java b/junit-platform-configuration-processor/src/main/java/module-info.java index 895455f9aabb..d21042faef2e 100644 --- a/junit-platform-configuration-processor/src/main/java/module-info.java +++ b/junit-platform-configuration-processor/src/main/java/module-info.java @@ -56,6 +56,11 @@ /// {@code .}. Likewise {@code {@code}} and {@code {@link}} tags are replaced /// with plain text versions. /// +///

Hints
+/// +/// When the {@linkplain org.junit.platform.configuration.api.ConfigurationParameter#type() type} +/// or the type of the {@linkplain org.junit.platform.configuration.api.ConfigurationParameter#defaultValue() default value} +/// is a boolean, enum, or interface, hints are automatically generated. module org.junit.platform.configuration.processor { requires static transitive org.jspecify; requires static transitive org.apiguardian.api; diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java index 2a6203edb2c1..615fd22cea6b 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java @@ -214,10 +214,6 @@ private record Default(String defaultType, Object value) { field.element()); return null; } - // TODO: Works for abstract classes, but only when there is a default. - // TODO: Consider limiting the allowed type values to primitives, enums and interfaces. - var typeElementName = typeElement.getQualifiedName().toString(); - return new Hint(name, null, processClassValues(typeElementName)); } return null; diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index d63e8d2d6150..7525e9d007c3 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -42,6 +42,7 @@ import org.junit.platform.configuration.testcases.NonFinal; import org.junit.platform.configuration.testcases.NonStatic; import org.junit.platform.configuration.testcases.NonString; +import org.junit.platform.configuration.testcases.TypeAbstractClass; import org.junit.platform.configuration.testcases.TypeAbstractClassWithDefaultClass; import org.junit.platform.configuration.testcases.TypeClass; import org.junit.platform.configuration.testcases.TypeEnum; @@ -305,25 +306,12 @@ void classTypeAbstractWithDefaultClass() { { "properties": [ { - "name": "org.example.classes", + "name": "org.example.property", "type": "org.junit.platform.configuration.testcases.TypeAbstractClassWithDefaultClass.Example", "sourceType": "org.junit.platform.configuration.testcases.TypeAbstractClassWithDefaultClass", "defaultValue": "org.junit.platform.configuration.testcases.TypeAbstractClassWithDefaultClass.Default" } - ], - "hints": [ - { - "name": "org.example.classes", - "providers": [ - { - "name": "class-reference", - "parameters": { - "target": "org.junit.platform.configuration.testcases.TypeAbstractClassWithDefaultClass.Example" - } - } - ] - } - ] + ] }"""); } @@ -336,7 +324,7 @@ void interfaceType() { { "name": "org.example.property", "type": "org.junit.platform.configuration.testcases.TypeInterface.Example", - "sourceType": "org.junit.platform.configuration.TypeInterface.TypeClass" + "sourceType": "org.junit.platform.configuration.testcases.TypeInterface" } ], "hints": [ @@ -346,7 +334,7 @@ void interfaceType() { { "name": "class-reference", "parameters": { - "target": "org.junit.platform.configuration.testcases.TypeClass.Example" + "target": "org.junit.platform.configuration.testcases.TypeInterface.Example" } } ] @@ -363,7 +351,7 @@ void interfaceTypeWithDefaultClass() { { "properties": [ { - "name": "org.example.classes", + "name": "org.example.property", "type": "org.junit.platform.configuration.testcases.TypeInterfaceWithDefaultClass.Example", "sourceType": "org.junit.platform.configuration.testcases.TypeInterfaceWithDefaultClass", "defaultValue": "org.junit.platform.configuration.testcases.TypeInterfaceWithDefaultClass.Default" @@ -371,7 +359,7 @@ void interfaceTypeWithDefaultClass() { ], "hints": [ { - "name": "org.example.classes", + "name": "org.example.property", "providers": [ { "name": "class-reference", @@ -400,6 +388,38 @@ void stringType() { }"""); } + @Test + void abstractType() { + compiler.compileWithoutError(TypeAbstractClass.class); + assertMetaDataIsEqualTo(""" + { + "properties": [ + { + "name": "org.example.property", + "type": "org.junit.platform.configuration.testcases.TypeAbstractClass.Example", + "sourceType": "org.junit.platform.configuration.testcases.TypeAbstractClass" + } + ] + }"""); + } + + @Test + void abstractTypeWithDefaultClassDoesNotGenerateHints() { + compiler.compileWithoutError(TypeAbstractClassWithDefaultClass.class); + assertMetaDataIsEqualTo( + """ + { + "properties": [ + { + "name": "org.example.property", + "type": "org.junit.platform.configuration.testcases.TypeAbstractClassWithDefaultClass.Example", + "sourceType": "org.junit.platform.configuration.testcases.TypeAbstractClassWithDefaultClass", + "defaultValue": "org.junit.platform.configuration.testcases.TypeAbstractClassWithDefaultClass.Default" + } + ] + }"""); + } + @Test void enumType() { compiler.compileWithoutError(TypeEnum.class); diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultClassWithoutType.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultClassWithoutType.java index bf253a1dda76..7e49b635cb95 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultClassWithoutType.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/DefaultClassWithoutType.java @@ -16,7 +16,7 @@ public final class DefaultClassWithoutType { @ConfigurationParameter(defaultValue = @Value(classValue = Default.class)) - public static final String CLASSES_PROPERTY_NAME = "org.example.classes"; + public static final String CLASSES_PROPERTY_NAME = "org.example.property"; private static class Default { diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeAbstractClass.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeAbstractClass.java new file mode 100644 index 000000000000..4c81a576dc79 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeAbstractClass.java @@ -0,0 +1,23 @@ +/* + * Copyright 2026 the original author or 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.configuration.testcases; + +import org.junit.platform.configuration.api.ConfigurationParameter; + +public final class TypeAbstractClass { + + @ConfigurationParameter(type = Example.class) + public static final String CLASSES_PROPERTY_NAME = "org.example.property"; + + private static abstract class Example { + + } +} diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeAbstractClassWithDefaultClass.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeAbstractClassWithDefaultClass.java index cbdd9c7de8a2..98952351e2a0 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeAbstractClassWithDefaultClass.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeAbstractClassWithDefaultClass.java @@ -16,7 +16,7 @@ public final class TypeAbstractClassWithDefaultClass { @ConfigurationParameter(type = Example.class, defaultValue = @Value(classValue = Default.class)) - public static final String CLASSES_PROPERTY_NAME = "org.example.classes"; + public static final String CLASSES_PROPERTY_NAME = "org.example.property"; private static abstract class Example { diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeInterface.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeInterface.java index d4b7c96f1b19..f1969c29dc55 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeInterface.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeInterface.java @@ -15,7 +15,7 @@ public final class TypeInterface { @ConfigurationParameter(type = Example.class) - public static final String CLASSES_PROPERTY_NAME = "org.example.classes"; + public static final String CLASSES_PROPERTY_NAME = "org.example.property"; private interface Example { diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeInterfaceWithDefaultClass.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeInterfaceWithDefaultClass.java index 19c8fd927ece..89bdb70dc86e 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeInterfaceWithDefaultClass.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/TypeInterfaceWithDefaultClass.java @@ -16,7 +16,7 @@ public final class TypeInterfaceWithDefaultClass { @ConfigurationParameter(type = Example.class, defaultValue = @Value(classValue = Default.class)) - public static final String CLASSES_PROPERTY_NAME = "org.example.classes"; + public static final String CLASSES_PROPERTY_NAME = "org.example.property"; private interface Example { From baff4125856c068df6617215c6cb2c75469122e0 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 18 Sep 2026 14:29:44 +0200 Subject: [PATCH 114/117] Make Timeout.TimeoutMode part of API --- .../java/org/junit/jupiter/api/Constants.java | 2 +- .../java/org/junit/jupiter/api/Timeout.java | 18 ++++++++++- .../extension/TimeoutConfiguration.java | 31 +++++-------------- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java index 24868553f534..34514ad37eca 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java @@ -417,7 +417,7 @@ public final class Constants { * * @see Timeout#TIMEOUT_MODE_PROPERTY_NAME */ - @ConfigurationParameter(defaultValue = @Value(stringValue = Timeout.TIMEOUT_MODE_DEFAULT)) + @ConfigurationParameter(type = Timeout.TimeoutMode.class, defaultValue = @Value(stringValue = Timeout.TIMEOUT_MODE_DEFAULT)) public static final String TIMEOUT_MODE_PROPERTY_NAME = Timeout.TIMEOUT_MODE_PROPERTY_NAME; /** diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java index 949a5387d36a..419a8436bdca 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java @@ -314,7 +314,7 @@ * {@link Timeout @Timeout} is applied to tests.

* *

Value must be one names of enum constants defined in - * {@link ThreadMode}, ignoring case. If not specified, the default is + * {@link TimeoutMode}, ignoring case. If not specified, the default is * {@value #TIMEOUT_MODE_DEFAULT}. * * @since 5.6 @@ -400,4 +400,20 @@ enum ThreadMode { SEPARATE_THREAD, } + @API(status = STABLE, since = "6.2") + enum TimeoutMode { + /** + * Timeouts are applied to tests. + */ + ENABLED, + /** + * Timeouts are not applied to tests. + */ + DISABLED, + /** + * Timeouts are not applied to tests when the JVM is started in debug mode. + */ + DISABLED_ON_DEBUG + } + } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java index 7a07c398c51c..cd7d1b70d699 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java @@ -30,6 +30,7 @@ import java.util.function.Supplier; import org.junit.jupiter.api.Timeout.ThreadMode; +import org.junit.jupiter.api.Timeout.TimeoutMode; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.config.EnumConfigurationParameterConverter; import org.junit.platform.commons.logging.Logger; @@ -53,7 +54,7 @@ class TimeoutConfiguration { this.extensionContext = extensionContext; this.timeoutDisabled = new EnumConfigurationParameterConverter<>(TimeoutMode.class, "timeout mode") // .get(extensionContext, TIMEOUT_MODE_PROPERTY_NAME) // - .map(TimeoutMode::isTimeoutDisabled) // + .map(TimeoutConfiguration::isTimeoutDisabled) // .orElse(false); } @@ -138,30 +139,12 @@ private Optional parseTimeoutThreadModeConfiguration() { .get(extensionContext, DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME); } - private enum TimeoutMode { - - ENABLED { - @Override - boolean isTimeoutDisabled() { - return false; - } - }, - - DISABLED { - @Override - boolean isTimeoutDisabled() { - return true; - } - }, - - DISABLED_ON_DEBUG { - @Override - boolean isTimeoutDisabled() { - return RuntimeUtils.isDebugMode(); - } + private static boolean isTimeoutDisabled(TimeoutMode timeoutMode) { + return switch (timeoutMode) { + case ENABLED -> false; + case DISABLED -> true; + case DISABLED_ON_DEBUG -> RuntimeUtils.isDebugMode(); }; - - abstract boolean isTimeoutDisabled(); } } From f0cad5d827b4feb65a7f777dea978ac433e5f7b7 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 18 Sep 2026 16:29:04 +0200 Subject: [PATCH 115/117] Support manually provided hints --- .../api/ConfigurationParameter.java | 38 +++++++++++++++++++ .../configuration/testcases/Hints.java | 25 ++++++++++++ .../testcases/HintsWithDescription.java | 26 +++++++++++++ .../HintsWithPermitsAdditionalValues.java | 26 +++++++++++++ 4 files changed, 115 insertions(+) create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/testcases/Hints.java create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/testcases/HintsWithDescription.java create mode 100644 platform-tests/src/test/java/org/junit/platform/configuration/testcases/HintsWithPermitsAdditionalValues.java diff --git a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java index 5438eb32f574..48d02597e00a 100644 --- a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java +++ b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java @@ -52,6 +52,15 @@ */ Deprecation deprecation() default @Deprecation; + /** + * Hints for valid values for the parameter. + *

+ * Can be omitted when {@link #type()} is an enum or interface. + * + * @return hints for valid values about the parameter. + */ + Hints hints() default @Hints; + @interface Deprecation { /** * A brief description of why the parameter was deprecated. @@ -136,4 +145,33 @@ Class[] classValue() default {}; } + @interface Hints { + /** + * Valid values for the value of the parameter. + * + * @return valid values for the value of the parameter. + */ + Hint[] value() default {}; + + /** + * Returns true if values other than the provided hints are allowed. + * By default, is assumed the hints are exhaustive. + * + * @return true if values other than the provided hints are allowed. + */ + boolean permitsAdditionalValues() default false; + } + + @interface Hint { + /** + * A short description of the value. + */ + String description() default ""; + + /** + * A valid value for the configuration parameter. + */ + String value() default ""; + + } } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Hints.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Hints.java new file mode 100644 index 000000000000..b0048be4237a --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Hints.java @@ -0,0 +1,25 @@ +/* + * Copyright 2026 the original author or 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.configuration.testcases; + +import org.junit.platform.configuration.api.ConfigurationParameter; +import org.junit.platform.configuration.api.ConfigurationParameter.Hint; + +public final class Hints { + + @ConfigurationParameter(hints = @ConfigurationParameter.Hints({ // + @Hint(value = "ns"), // + @Hint(value = "μs"), // + @Hint(value = "ms") // + })) + public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; + +} diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/HintsWithDescription.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/HintsWithDescription.java new file mode 100644 index 000000000000..6fce54ebcd20 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/HintsWithDescription.java @@ -0,0 +1,26 @@ +/* + * Copyright 2026 the original author or 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.configuration.testcases; + +import org.junit.platform.configuration.api.ConfigurationParameter; +import org.junit.platform.configuration.api.ConfigurationParameter.Hint; +import org.junit.platform.configuration.api.ConfigurationParameter.Hints; + +public final class HintsWithDescription { + + @ConfigurationParameter(hints = @Hints({ // + @Hint(value = "100 ns", description = "a timeout in nano seconds"), // + @Hint(value = "100 μs", description = "a timeout in micro seconds"), // + @Hint(value = "100 ms", description = "a timeout in milli seconds") // + })) + public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; + +} diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/HintsWithPermitsAdditionalValues.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/HintsWithPermitsAdditionalValues.java new file mode 100644 index 000000000000..7cd9c062d427 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/HintsWithPermitsAdditionalValues.java @@ -0,0 +1,26 @@ +/* + * Copyright 2026 the original author or 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.configuration.testcases; + +import org.junit.platform.configuration.api.ConfigurationParameter; +import org.junit.platform.configuration.api.ConfigurationParameter.Hint; +import org.junit.platform.configuration.api.ConfigurationParameter.Hints; + +public final class HintsWithPermitsAdditionalValues { + + @ConfigurationParameter(hints = @Hints(permitsAdditionalValues = true, value = { // + @Hint(value = "ns"), // + @Hint(value = "μs"), // + @Hint(value = "ms") // + })) + public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; + +} From b77beab858141711386ea6c37d3021bff34ece48 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 18 Sep 2026 16:54:21 +0200 Subject: [PATCH 116/117] Document all the timeouts --- .../java/org/junit/jupiter/api/Constants.java | 121 +++++++++++++++--- 1 file changed, 105 insertions(+), 16 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java index 34514ad37eca..d909ad30270e 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java @@ -29,6 +29,8 @@ import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.platform.configuration.api.ConfigurationParameter; +import org.junit.platform.configuration.api.ConfigurationParameter.Hint; +import org.junit.platform.configuration.api.ConfigurationParameter.Hints; import org.junit.platform.engine.support.hierarchical.DefaultParallelExecutionConfigurationStrategy; import org.junit.platform.engine.support.hierarchical.ParallelExecutionConfigurationStrategy; import org.junit.platform.engine.support.hierarchical.ParallelHierarchicalTestExecutorServiceFactory; @@ -252,8 +254,7 @@ public final class Constants { * */ @ConfigurationParameter(type = ParallelExecutorServiceType.class, defaultValue = @Value(stringValue = ParallelHierarchicalTestExecutorServiceFactory.EXECUTOR_SERVICE_DEFAULT)) - public static final String PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX - + "executor-service"; + public static final String PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "executor-service"; /** * Property name used to select the parallel execution configuration @@ -274,8 +275,7 @@ public final class Constants { * */ @ConfigurationParameter(type = Integer.class) - public static final String PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX - + "fixed.parallelism"; + public static final String PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "fixed.parallelism"; /** * Property name used to configure the maximum pool size of the underlying @@ -287,8 +287,7 @@ public final class Constants { * */ @ConfigurationParameter(type = Integer.class) - public static final String PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX - + "fixed.max-pool-size"; + public static final String PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "fixed.max-pool-size"; /** * Property name used to disable saturation of the underlying fork-join pool @@ -330,7 +329,16 @@ public final class Constants { * * @see Timeout#DEFAULT_TIMEOUT_PROPERTY_NAME */ - @ConfigurationParameter(type = String.class) + @ConfigurationParameter(type = String.class, hints = @Hints(permitsAdditionalValues = true, value = { // + @Hint(value = "42 ", description = "A duration in seconds"), // + @Hint(value = "42 ns", description = "A duration in nanoseconds"), // + @Hint(value = "42 μs", description = "A duration in microseconds"), // + @Hint(value = "42 ms", description = "A duration in milliseconds"), // + @Hint(value = "42 s ", description = "A duration in seconds"), // + @Hint(value = "42 m ", description = "A duration in minutes"), // + @Hint(value = "42 h ", description = "A duration in hours"), // + @Hint(value = "42 d ", description = "A duration in days") // + })) public static final String DEFAULT_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TIMEOUT_PROPERTY_NAME; /** @@ -338,7 +346,16 @@ public final class Constants { * * @see Timeout#DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME */ - @ConfigurationParameter(type = String.class) + @ConfigurationParameter(type = String.class, hints = @Hints(permitsAdditionalValues = true, value = { // + @Hint(value = "42 ", description = "A duration in seconds"), // + @Hint(value = "42 ns", description = "A duration in nanoseconds"), // + @Hint(value = "42 μs", description = "A duration in microseconds"), // + @Hint(value = "42 ms", description = "A duration in milliseconds"), // + @Hint(value = "42 s ", description = "A duration in seconds"), // + @Hint(value = "42 m ", description = "A duration in minutes"), // + @Hint(value = "42 h ", description = "A duration in hours"), // + @Hint(value = "42 d ", description = "A duration in days") // + })) public static final String DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -347,7 +364,16 @@ public final class Constants { * * @see Timeout#DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME */ - @ConfigurationParameter(type = String.class) + @ConfigurationParameter(type = String.class, hints = @Hints(permitsAdditionalValues = true, value = { // + @Hint(value = "42 ", description = "A duration in seconds"), // + @Hint(value = "42 ns", description = "A duration in nanoseconds"), // + @Hint(value = "42 μs", description = "A duration in microseconds"), // + @Hint(value = "42 ms", description = "A duration in milliseconds"), // + @Hint(value = "42 s ", description = "A duration in seconds"), // + @Hint(value = "42 m ", description = "A duration in minutes"), // + @Hint(value = "42 h ", description = "A duration in hours"), // + @Hint(value = "42 d ", description = "A duration in days") // + })) public static final String DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -356,7 +382,16 @@ public final class Constants { * * @see Timeout#DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME */ - @ConfigurationParameter(type = String.class) + @ConfigurationParameter(type = String.class, hints = @Hints(permitsAdditionalValues = true, value = { // + @Hint(value = "42 ", description = "A duration in seconds"), // + @Hint(value = "42 ns", description = "A duration in nanoseconds"), // + @Hint(value = "42 μs", description = "A duration in microseconds"), // + @Hint(value = "42 ms", description = "A duration in milliseconds"), // + @Hint(value = "42 s ", description = "A duration in seconds"), // + @Hint(value = "42 m ", description = "A duration in minutes"), // + @Hint(value = "42 h ", description = "A duration in hours"), // + @Hint(value = "42 d ", description = "A duration in days") // + })) public static final String DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -365,7 +400,16 @@ public final class Constants { * * @see Timeout#DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME */ - @ConfigurationParameter(type = String.class) + @ConfigurationParameter(type = String.class, hints = @Hints(permitsAdditionalValues = true, value = { // + @Hint(value = "42 ", description = "A duration in seconds"), // + @Hint(value = "42 ns", description = "A duration in nanoseconds"), // + @Hint(value = "42 μs", description = "A duration in microseconds"), // + @Hint(value = "42 ms", description = "A duration in milliseconds"), // + @Hint(value = "42 s ", description = "A duration in seconds"), // + @Hint(value = "42 m ", description = "A duration in minutes"), // + @Hint(value = "42 h ", description = "A duration in hours"), // + @Hint(value = "42 d ", description = "A duration in days") // + })) public static final String DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -373,7 +417,16 @@ public final class Constants { * * @see Timeout#DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME */ - @ConfigurationParameter(type = String.class) + @ConfigurationParameter(type = String.class, hints = @Hints(permitsAdditionalValues = true, value = { // + @Hint(value = "42 ", description = "A duration in seconds"), // + @Hint(value = "42 ns", description = "A duration in nanoseconds"), // + @Hint(value = "42 μs", description = "A duration in microseconds"), // + @Hint(value = "42 ms", description = "A duration in milliseconds"), // + @Hint(value = "42 s ", description = "A duration in seconds"), // + @Hint(value = "42 m ", description = "A duration in minutes"), // + @Hint(value = "42 h ", description = "A duration in hours"), // + @Hint(value = "42 d ", description = "A duration in days") // + })) public static final String DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -382,7 +435,16 @@ public final class Constants { * * @see Timeout#DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME */ - @ConfigurationParameter(type = String.class) + @ConfigurationParameter(type = String.class, hints = @Hints(permitsAdditionalValues = true, value = { // + @Hint(value = "42 ", description = "A duration in seconds"), // + @Hint(value = "42 ns", description = "A duration in nanoseconds"), // + @Hint(value = "42 μs", description = "A duration in microseconds"), // + @Hint(value = "42 ms", description = "A duration in milliseconds"), // + @Hint(value = "42 s ", description = "A duration in seconds"), // + @Hint(value = "42 m ", description = "A duration in minutes"), // + @Hint(value = "42 h ", description = "A duration in hours"), // + @Hint(value = "42 d ", description = "A duration in days") // + })) public static final String DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -391,7 +453,16 @@ public final class Constants { * * @see Timeout#DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME */ - @ConfigurationParameter(type = String.class) + @ConfigurationParameter(type = String.class, hints = @Hints(permitsAdditionalValues = true, value = { // + @Hint(value = "42 ", description = "A duration in seconds"), // + @Hint(value = "42 ns", description = "A duration in nanoseconds"), // + @Hint(value = "42 μs", description = "A duration in microseconds"), // + @Hint(value = "42 ms", description = "A duration in milliseconds"), // + @Hint(value = "42 s ", description = "A duration in seconds"), // + @Hint(value = "42 m ", description = "A duration in minutes"), // + @Hint(value = "42 h ", description = "A duration in hours"), // + @Hint(value = "42 d ", description = "A duration in days") // + })) public static final String DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -400,7 +471,16 @@ public final class Constants { * * @see Timeout#DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME */ - @ConfigurationParameter(type = String.class) + @ConfigurationParameter(type = String.class, hints = @Hints(permitsAdditionalValues = true, value = { // + @Hint(value = "42 ", description = "A duration in seconds"), // + @Hint(value = "42 ns", description = "A duration in nanoseconds"), // + @Hint(value = "42 μs", description = "A duration in microseconds"), // + @Hint(value = "42 ms", description = "A duration in milliseconds"), // + @Hint(value = "42 s ", description = "A duration in seconds"), // + @Hint(value = "42 m ", description = "A duration in minutes"), // + @Hint(value = "42 h ", description = "A duration in hours"), // + @Hint(value = "42 d ", description = "A duration in days") // + })) public static final String DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME; /** @@ -409,7 +489,16 @@ public final class Constants { * * @see Timeout#DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME */ - @ConfigurationParameter(type = String.class) + @ConfigurationParameter(type = String.class, hints = @Hints(permitsAdditionalValues = true, value = { // + @Hint(value = "42 ", description = "A duration in seconds"), // + @Hint(value = "42 ns", description = "A duration in nanoseconds"), // + @Hint(value = "42 μs", description = "A duration in microseconds"), // + @Hint(value = "42 ms", description = "A duration in milliseconds"), // + @Hint(value = "42 s ", description = "A duration in seconds"), // + @Hint(value = "42 m ", description = "A duration in minutes"), // + @Hint(value = "42 h ", description = "A duration in hours"), // + @Hint(value = "42 d ", description = "A duration in days") // + })) public static final String DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME; /** From bf45128ce77e8d15dc4725fd36fe9856c59204c9 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 18 Sep 2026 17:59:21 +0200 Subject: [PATCH 117/117] Process the hints annotation --- .../java/org/junit/jupiter/api/Constants.java | 9 +- .../api/ConfigurationParameter.java | 9 +- .../processor/AnnotationMirrorUtil.java | 13 +++ .../ConfigurationParameterAnnotatedField.java | 6 ++ .../ConfigurationParameterHandler.java | 30 +++++++ ...ationMetadataAnnotationProcessorTests.java | 87 +++++++++++++++++++ .../configuration/testcases/Hints.java | 6 +- .../testcases/HintsWithDescription.java | 6 +- .../HintsWithPermitsAdditionalValues.java | 6 +- 9 files changed, 152 insertions(+), 20 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java index d909ad30270e..121c3c040436 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java @@ -254,7 +254,8 @@ public final class Constants { * */ @ConfigurationParameter(type = ParallelExecutorServiceType.class, defaultValue = @Value(stringValue = ParallelHierarchicalTestExecutorServiceFactory.EXECUTOR_SERVICE_DEFAULT)) - public static final String PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "executor-service"; + public static final String PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + + "executor-service"; /** * Property name used to select the parallel execution configuration @@ -275,7 +276,8 @@ public final class Constants { * */ @ConfigurationParameter(type = Integer.class) - public static final String PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "fixed.parallelism"; + public static final String PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + + "fixed.parallelism"; /** * Property name used to configure the maximum pool size of the underlying @@ -287,7 +289,8 @@ public final class Constants { * */ @ConfigurationParameter(type = Integer.class) - public static final String PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "fixed.max-pool-size"; + public static final String PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + + "fixed.max-pool-size"; /** * Property name used to disable saturation of the underlying fork-join pool diff --git a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java index 48d02597e00a..3b766352cb37 100644 --- a/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java +++ b/junit-platform-configuration-api/src/main/java/org/junit/platform/configuration/api/ConfigurationParameter.java @@ -163,15 +163,16 @@ } @interface Hint { - /** - * A short description of the value. - */ - String description() default ""; /** * A valid value for the configuration parameter. */ String value() default ""; + /** + * A short description of the value. + */ + String description() default ""; + } } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java index b6732d1fea6f..47dd7eeb424c 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/AnnotationMirrorUtil.java @@ -60,6 +60,19 @@ private AnnotationMirrorUtil() { return null; } + @SuppressWarnings("unchecked") + static @Nullable List getAnnotationMirrorList(AnnotationMirror annotation, String name) { + for (var entry : annotation.getElementValues().entrySet()) { + if (entry.getKey().getSimpleName().toString().equals(name)) { + var value = entry.getValue().getValue(); + if (value instanceof List list) { + return (List) list; + } + } + } + return null; + } + static Map> getValuesMap(AnnotationMirror annotation) { return annotation.getElementValues().entrySet() // .stream() // diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java index b0bac36625e9..d80a426d6b5e 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterAnnotatedField.java @@ -106,4 +106,10 @@ String docComment() { String enclosingTypeName() { return enclosingType.getQualifiedName().toString(); } + + @Nullable + AnnotationMirror hints() { + return getAnnotationMirror(annotationMirror, "hints"); + } + } diff --git a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java index 615fd22cea6b..da86b5bd8e4b 100644 --- a/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java +++ b/junit-platform-configuration-processor/src/main/java/org/junit/platform/configuration/processor/ConfigurationParameterHandler.java @@ -10,15 +10,20 @@ package org.junit.platform.configuration.processor; +import static java.lang.Boolean.TRUE; import static java.util.Objects.requireNonNull; import static javax.tools.Diagnostic.Kind.ERROR; import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getAnnotationMirror; +import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getAnnotationMirrorList; +import static org.junit.platform.configuration.processor.AnnotationMirrorUtil.getAnnotationValue; +import java.util.Collections; import java.util.List; import java.util.regex.Pattern; import javax.annotation.processing.Messager; import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; @@ -182,6 +187,11 @@ private record Default(String defaultType, Object value) { private @Nullable Hint processHint(String name, ConfigurationParameterAnnotatedField field, @Nullable Default defaults) { + // Derive hint from ConfigurationParameter.hints value + var hints = field.hints(); + if (hints != null) { + return new Hint(name, processHintValues(hints), processPermitsAdditionalValues(hints)); + } // Derive hint from ConfigurationParameter.type value var typeElement = field.typeTypeElement(); if (typeElement != null) { @@ -219,6 +229,26 @@ private record Default(String defaultType, Object value) { return null; } + private @Nullable List processPermitsAdditionalValues(AnnotationMirror hints) { + var permitsAdditionalValues = getAnnotationValue(hints, "permitsAdditionalValues"); + if (permitsAdditionalValues == null || !TRUE.equals(permitsAdditionalValues.getValue())) { + return null; + } + return List.of(new ValueProvider("any", null)); + } + + private List processHintValues(AnnotationMirror hints) { + var hintValues = getAnnotationMirrorList(hints, "value"); + if (hintValues == null) { + return Collections.emptyList(); + } + return hintValues.stream() // + .map(AnnotationMirrorUtil::getStringValuesMap) // + .filter(map -> map.get("value") != null) // + .map(map -> new ValueHint(map.get("value"), map.get("description"))) // + .toList(); + } + private static List processClassValues(String typeElementName) { var parameters = new Parameters(typeElementName); var valueprovider = new ValueProvider("class-reference", parameters); diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java index 7525e9d007c3..8759ffff1c0f 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/processor/ConfigurationMetadataAnnotationProcessorTests.java @@ -38,6 +38,9 @@ import org.junit.platform.configuration.testcases.DocumentedWithHeader; import org.junit.platform.configuration.testcases.DocumentedWithMultiLines; import org.junit.platform.configuration.testcases.DocumentedWithMultipleParagraphs; +import org.junit.platform.configuration.testcases.Hints; +import org.junit.platform.configuration.testcases.HintsWithDescription; +import org.junit.platform.configuration.testcases.HintsWithPermitsAdditionalValues; import org.junit.platform.configuration.testcases.Minimal; import org.junit.platform.configuration.testcases.NonFinal; import org.junit.platform.configuration.testcases.NonStatic; @@ -602,6 +605,90 @@ void defaults() { }"""); } + @Test + void hints() { + compiler.compileWithoutError(Hints.class); + assertMetaDataIsEqualTo(""" + { + "properties": [ + { + "name": "org.example.property", + "sourceType": "org.junit.platform.configuration.testcases.Hints" + } + ], + "hints": [ + { + "name": "org.example.property", + "values": [ + { + "value": "42 ns" + }, + { + "value": "42 μs" + }, + { + "value": "42 ms" + } + ] + } + ] + }"""); + } + + @Test + void hintsWithDescription() { + compiler.compileWithoutError(HintsWithDescription.class); + assertMetaDataIsEqualTo(""" + { + "properties": [ + { + "name": "org.example.property", + "sourceType": "org.junit.platform.configuration.testcases.HintsWithDescription" + } + ], + "hints": [ + { + "name": "org.example.property", + "values": [ + { + "value": "42 ns", + "description": "a timeout in nano seconds" + } + ] + } + ] + }"""); + } + + @Test + void hintsWithPermitsAdditionalValues() { + compiler.compileWithoutError(HintsWithPermitsAdditionalValues.class); + assertMetaDataIsEqualTo(""" + { + "properties": [ + { + "name": "org.example.property", + "sourceType": "org.junit.platform.configuration.testcases.HintsWithPermitsAdditionalValues" + } + ], + "hints": [ + { + "name": "org.example.property", + "values": [ + { + "value": "42 ns" + } + ], + "providers": [ + { + "name": "any" + } + ] + } + ] + }"""); + } + @Test void mustBeFinal() { var result = compiler.compile(NonFinal.class); diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Hints.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Hints.java index b0048be4237a..e72eb01273d7 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Hints.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/Hints.java @@ -16,9 +16,9 @@ public final class Hints { @ConfigurationParameter(hints = @ConfigurationParameter.Hints({ // - @Hint(value = "ns"), // - @Hint(value = "μs"), // - @Hint(value = "ms") // + @Hint(value = "42 ns"), // + @Hint(value = "42 μs"), // + @Hint(value = "42 ms") // })) public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/HintsWithDescription.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/HintsWithDescription.java index 6fce54ebcd20..0425d421a557 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/HintsWithDescription.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/HintsWithDescription.java @@ -16,11 +16,7 @@ public final class HintsWithDescription { - @ConfigurationParameter(hints = @Hints({ // - @Hint(value = "100 ns", description = "a timeout in nano seconds"), // - @Hint(value = "100 μs", description = "a timeout in micro seconds"), // - @Hint(value = "100 ms", description = "a timeout in milli seconds") // - })) + @ConfigurationParameter(hints = @Hints(@Hint(value = "42 ns", description = "a timeout in nano seconds"))) public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; } diff --git a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/HintsWithPermitsAdditionalValues.java b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/HintsWithPermitsAdditionalValues.java index 7cd9c062d427..d0c7cba24283 100644 --- a/platform-tests/src/test/java/org/junit/platform/configuration/testcases/HintsWithPermitsAdditionalValues.java +++ b/platform-tests/src/test/java/org/junit/platform/configuration/testcases/HintsWithPermitsAdditionalValues.java @@ -16,11 +16,7 @@ public final class HintsWithPermitsAdditionalValues { - @ConfigurationParameter(hints = @Hints(permitsAdditionalValues = true, value = { // - @Hint(value = "ns"), // - @Hint(value = "μs"), // - @Hint(value = "ms") // - })) + @ConfigurationParameter(hints = @Hints(permitsAdditionalValues = true, value = @Hint(value = "42 ns"))) public static final String EXAMPLE_PROPERTY_NAME = "org.example.property"; }