From 0223a400c3708702187f94e914f344d55811820f Mon Sep 17 00:00:00 2001 From: Jendrik Johannes Date: Sun, 2 Nov 2025 12:08:42 +0100 Subject: [PATCH 1/2] Remove current extendsFrom and cleanup code --- .../testing/JavaModuleTestingExtension.java | 117 +++++++++--------- .../WhiteboxTestCompileArgumentProvider.java | 6 +- .../WhiteboxTestRuntimeArgumentProvider.java | 14 +-- 3 files changed, 67 insertions(+), 70 deletions(-) diff --git a/src/main/java/org/gradlex/javamodule/testing/JavaModuleTestingExtension.java b/src/main/java/org/gradlex/javamodule/testing/JavaModuleTestingExtension.java index 86c175f..354f4a4 100644 --- a/src/main/java/org/gradlex/javamodule/testing/JavaModuleTestingExtension.java +++ b/src/main/java/org/gradlex/javamodule/testing/JavaModuleTestingExtension.java @@ -3,13 +3,14 @@ import java.io.File; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import javax.inject.Inject; import org.gradle.api.Action; import org.gradle.api.Describable; import org.gradle.api.Project; -import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.dsl.DependencyHandler; import org.gradle.api.file.RegularFile; @@ -39,6 +40,9 @@ public abstract class JavaModuleTestingExtension { private final Project project; + // for late 'extendsFrom' configuration + private final Map whiteboxOrClasspathTestSuites = new HashMap<>(); + @Inject public JavaModuleTestingExtension(Project project) { this.project = project; @@ -148,6 +152,8 @@ public void whitebox(TestSuite jvmTestSuite, Action conf) .convention(project.getExtensions() .getByType(SourceSetContainer.class) .getByName(SourceSet.MAIN_SOURCE_SET_NAME)); + + // Improve: requiresFromModuleInfo is done multiple times when 'whitebox' is called multiple times whiteboxJvmTestSuite .getRequires() .addAll(requiresFromModuleInfo( @@ -156,6 +162,7 @@ public void whitebox(TestSuite jvmTestSuite, Action conf) .getRequiresRuntime() .addAll(requiresFromModuleInfo( (JvmTestSuite) jvmTestSuite, whiteboxJvmTestSuite.getSourcesUnderTest(), true)); + conf.execute(whiteboxJvmTestSuite); configureJvmTestSuiteForWhitebox((JvmTestSuite) jvmTestSuite, whiteboxJvmTestSuite); } @@ -224,24 +231,17 @@ private void configureJvmTestSuiteForBlackbox(JvmTestSuite jvmTestSuite) { private void configureJvmTestSuiteForWhitebox( JvmTestSuite jvmTestSuite, WhiteboxJvmTestSuite whiteboxJvmTestSuite) { ConfigurationContainer configurations = project.getConfigurations(); - DependencyHandler dependencies = project.getDependencies(); TaskContainer tasks = project.getTasks(); ModuleInfoParser moduleInfoParser = new ModuleInfoParser(project.getLayout(), project.getProviders()); SourceSet testSources = jvmTestSuite.getSources(); + SourceSet sourcesUnderTest = whiteboxJvmTestSuite.getSourcesUnderTest().get(); + whiteboxOrClasspathTestSuites.put(testSources, sourcesUnderTest); + JavaModuleDependenciesBridge.addRequiresRuntimeSupport( project, whiteboxJvmTestSuite.getSourcesUnderTest().get(), jvmTestSuite.getSources()); tasks.named(testSources.getCompileJavaTaskName(), JavaCompile.class, compileJava -> { - SourceSet sourcesUnderTest = - whiteboxJvmTestSuite.getSourcesUnderTest().get(); - - Configuration compileOnly = configurations.getByName(sourcesUnderTest.getCompileOnlyConfigurationName()); - Configuration testCompileOnly = configurations.getByName(testSources.getCompileOnlyConfigurationName()); - if (!testCompileOnly.getExtendsFrom().contains(compileOnly)) { - testCompileOnly.extendsFrom(compileOnly); - } - compileJava.setClasspath(sourcesUnderTest .getOutput() .plus(configurations.getByName(testSources.getCompileClasspathConfigurationName()))); @@ -250,29 +250,12 @@ private void configureJvmTestSuiteForWhitebox( compileJava.getOptions().getCompilerArgumentProviders().stream() .filter(p -> p instanceof WhiteboxTestCompileArgumentProvider) .findFirst() - .orElseGet(() -> { - WhiteboxTestCompileArgumentProvider newProvider = - new WhiteboxTestCompileArgumentProvider( - testSources.getJava().getSrcDirs(), - moduleInfoParser, - project.getObjects()); - compileJava - .getOptions() - .getCompilerArgumentProviders() - .add(newProvider); - compileJava.doFirst( - project.getObjects().newInstance(JavaCompileSetModulePathAction.class)); - return newProvider; - }); + .orElseGet(() -> initCompileArgProvider(compileJava, testSources, moduleInfoParser)); argumentProvider.setMainSourceFolders(sourcesUnderTest.getJava().getSrcDirs()); - argumentProvider.testRequires( - JavaModuleDependenciesBridge.getCompileClasspathModules(project, testSources)); argumentProvider.testRequires(whiteboxJvmTestSuite.getRequires()); }); tasks.named(testSources.getName(), Test.class, test -> { - SourceSet sourcesUnderTest = - whiteboxJvmTestSuite.getSourcesUnderTest().get(); test.setClasspath(configurations .getByName(testSources.getRuntimeClasspathConfigurationName()) .plus(sourcesUnderTest.getOutput()) @@ -288,49 +271,63 @@ private void configureJvmTestSuiteForWhitebox( (WhiteboxTestRuntimeArgumentProvider) test.getJvmArgumentProviders().stream() .filter(p -> p instanceof WhiteboxTestRuntimeArgumentProvider) .findFirst() - .orElseGet(() -> { - WhiteboxTestRuntimeArgumentProvider newProvider = - new WhiteboxTestRuntimeArgumentProvider( - testSources.getJava().getClassesDirectory(), - testSources.getOutput().getResourcesDir(), - moduleInfoParser, - project.getObjects()); - test.getJvmArgumentProviders().add(newProvider); - return newProvider; - }); + .orElseGet(() -> initRuntimeArgProvider(test, testSources, moduleInfoParser)); argumentProvider.setMainSourceFolders(sourcesUnderTest.getJava().getSrcDirs()); argumentProvider.setResourcesUnderTest(sourcesUnderTest.getOutput().getResourcesDir()); - argumentProvider.testRequires( - JavaModuleDependenciesBridge.getRuntimeClasspathModules(project, testSources)); argumentProvider.testRequires(whiteboxJvmTestSuite.getRequires()); - argumentProvider.testOpensTo(JavaModuleDependenciesBridge.getOpensToModules(project, testSources)); argumentProvider.testOpensTo(whiteboxJvmTestSuite.getOpensTo()); - argumentProvider.testExportsTo(JavaModuleDependenciesBridge.getExportsToModules(project, testSources)); argumentProvider.testExportsTo(whiteboxJvmTestSuite.getExportsTo()); }); - Configuration implementation = configurations.getByName(testSources.getImplementationConfigurationName()); - implementation.withDependencies(d -> { - for (String requiresModuleName : whiteboxJvmTestSuite.getRequires().get()) { - Provider dependency = JavaModuleDependenciesBridge.create( - project, - requiresModuleName, - whiteboxJvmTestSuite.getSourcesUnderTest().get()); - if (dependency != null) { - dependencies.addProvider(implementation.getName(), dependency); - } - } - }); - Configuration runtimeOnly = configurations.getByName(testSources.getRuntimeOnlyConfigurationName()); - runtimeOnly.withDependencies(d -> { - for (String requiresModuleName : - whiteboxJvmTestSuite.getRequiresRuntime().get()) { + addDependencyForRequires( + whiteboxJvmTestSuite, + project, + testSources.getImplementationConfigurationName(), + whiteboxJvmTestSuite.getRequires()); + addDependencyForRequires( + whiteboxJvmTestSuite, + project, + testSources.getRuntimeOnlyConfigurationName(), + whiteboxJvmTestSuite.getRequiresRuntime()); + } + + private WhiteboxTestCompileArgumentProvider initCompileArgProvider( + JavaCompile compileJava, SourceSet testSources, ModuleInfoParser moduleInfoParser) { + WhiteboxTestCompileArgumentProvider newProvider = new WhiteboxTestCompileArgumentProvider( + testSources.getJava().getSrcDirs(), moduleInfoParser, project.getObjects()); + newProvider.testRequires(JavaModuleDependenciesBridge.getCompileClasspathModules(project, testSources)); + compileJava.getOptions().getCompilerArgumentProviders().add(newProvider); + compileJava.doFirst(project.getObjects().newInstance(JavaCompileSetModulePathAction.class)); + return newProvider; + } + + private WhiteboxTestRuntimeArgumentProvider initRuntimeArgProvider( + Test test, SourceSet testSources, ModuleInfoParser moduleInfoParser) { + WhiteboxTestRuntimeArgumentProvider newProvider = new WhiteboxTestRuntimeArgumentProvider( + testSources.getJava().getClassesDirectory(), + testSources.getOutput().getResourcesDir(), + moduleInfoParser, + project.getObjects()); + newProvider.testRequires(JavaModuleDependenciesBridge.getRuntimeClasspathModules(project, testSources)); + newProvider.testOpensTo(JavaModuleDependenciesBridge.getOpensToModules(project, testSources)); + newProvider.testExportsTo(JavaModuleDependenciesBridge.getExportsToModules(project, testSources)); + test.getJvmArgumentProviders().add(newProvider); + return newProvider; + } + + private void addDependencyForRequires( + WhiteboxJvmTestSuite whiteboxJvmTestSuite, String scope, Provider> requires) { + ConfigurationContainer configurations = project.getConfigurations(); + DependencyHandler dependencies = project.getDependencies(); + + configurations.getByName(scope).withDependencies(d -> { + for (String requiresModuleName : requires.get()) { Provider dependency = JavaModuleDependenciesBridge.create( project, requiresModuleName, whiteboxJvmTestSuite.getSourcesUnderTest().get()); if (dependency != null) { - dependencies.addProvider(runtimeOnly.getName(), dependency); + dependencies.addProvider(scope, dependency); } } }); diff --git a/src/main/java/org/gradlex/javamodule/testing/internal/provider/WhiteboxTestCompileArgumentProvider.java b/src/main/java/org/gradlex/javamodule/testing/internal/provider/WhiteboxTestCompileArgumentProvider.java index fab395a..f8ca736 100644 --- a/src/main/java/org/gradlex/javamodule/testing/internal/provider/WhiteboxTestCompileArgumentProvider.java +++ b/src/main/java/org/gradlex/javamodule/testing/internal/provider/WhiteboxTestCompileArgumentProvider.java @@ -7,8 +7,8 @@ import java.util.Set; import java.util.stream.Collectors; import org.gradle.api.model.ObjectFactory; -import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Provider; +import org.gradle.api.provider.SetProperty; import org.gradle.process.CommandLineArgumentProvider; import org.gradlex.javamodule.testing.internal.ModuleInfoParser; import org.jspecify.annotations.NullMarked; @@ -21,13 +21,13 @@ public class WhiteboxTestCompileArgumentProvider implements CommandLineArgumentP @SuppressWarnings("NotNullFieldNotInitialized") private Set mainSourceFolders; - private final ListProperty allTestRequires; + private final SetProperty allTestRequires; public WhiteboxTestCompileArgumentProvider( Set testSourceFolders, ModuleInfoParser moduleInfoParser, ObjectFactory objects) { this.testSourceFolders = testSourceFolders; this.moduleInfoParser = moduleInfoParser; - this.allTestRequires = objects.listProperty(String.class); + this.allTestRequires = objects.setProperty(String.class); } public void setMainSourceFolders(Set mainSourceFolders) { diff --git a/src/main/java/org/gradlex/javamodule/testing/internal/provider/WhiteboxTestRuntimeArgumentProvider.java b/src/main/java/org/gradlex/javamodule/testing/internal/provider/WhiteboxTestRuntimeArgumentProvider.java index 06ff595..975d11f 100644 --- a/src/main/java/org/gradlex/javamodule/testing/internal/provider/WhiteboxTestRuntimeArgumentProvider.java +++ b/src/main/java/org/gradlex/javamodule/testing/internal/provider/WhiteboxTestRuntimeArgumentProvider.java @@ -8,8 +8,8 @@ import java.util.TreeSet; import org.gradle.api.file.Directory; import org.gradle.api.model.ObjectFactory; -import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Provider; +import org.gradle.api.provider.SetProperty; import org.gradle.process.CommandLineArgumentProvider; import org.gradlex.javamodule.testing.internal.ModuleInfoParser; import org.jspecify.annotations.NullMarked; @@ -26,9 +26,9 @@ public class WhiteboxTestRuntimeArgumentProvider implements CommandLineArgumentP @SuppressWarnings("NotNullFieldNotInitialized") private File resourcesUnderTest; - private final ListProperty allTestRequires; - private final ListProperty allTestOpensTo; - private final ListProperty allTestExportsTo; + private final SetProperty allTestRequires; + private final SetProperty allTestOpensTo; + private final SetProperty allTestExportsTo; public WhiteboxTestRuntimeArgumentProvider( Provider testClassesFolders, @@ -38,9 +38,9 @@ public WhiteboxTestRuntimeArgumentProvider( this.testClassesFolders = testClassesFolders; this.testResources = testResources; this.moduleInfoParser = moduleInfoParser; - this.allTestRequires = objects.listProperty(String.class); - this.allTestOpensTo = objects.listProperty(String.class); - this.allTestExportsTo = objects.listProperty(String.class); + this.allTestRequires = objects.setProperty(String.class); + this.allTestOpensTo = objects.setProperty(String.class); + this.allTestExportsTo = objects.setProperty(String.class); } public void setMainSourceFolders(Set mainSourceFolders) { From dda235870a82e9b6aa9ff0660aee4877bdbe0d67 Mon Sep 17 00:00:00 2001 From: Jendrik Johannes Date: Sun, 2 Nov 2025 12:31:53 +0100 Subject: [PATCH 2/2] Add late extendsFrom implementation --- .../testing/JavaModuleTestingExtension.java | 30 ++++++- .../testing/test/CoreFunctionalityTest.java | 80 +++++++++++++++++++ 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/gradlex/javamodule/testing/JavaModuleTestingExtension.java b/src/main/java/org/gradlex/javamodule/testing/JavaModuleTestingExtension.java index 354f4a4..e9ad1b4 100644 --- a/src/main/java/org/gradlex/javamodule/testing/JavaModuleTestingExtension.java +++ b/src/main/java/org/gradlex/javamodule/testing/JavaModuleTestingExtension.java @@ -11,6 +11,7 @@ import org.gradle.api.Action; import org.gradle.api.Describable; import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.dsl.DependencyHandler; import org.gradle.api.file.RegularFile; @@ -61,6 +62,7 @@ public JavaModuleTestingExtension(Project project) { whitebox(jvmTestSuite, conf -> conf.getOpensTo().add("org.junit.platform.commons")); } }); + project.afterEvaluate(this::lateConfigureExtendsFrom); } /** @@ -281,12 +283,10 @@ private void configureJvmTestSuiteForWhitebox( addDependencyForRequires( whiteboxJvmTestSuite, - project, testSources.getImplementationConfigurationName(), whiteboxJvmTestSuite.getRequires()); addDependencyForRequires( whiteboxJvmTestSuite, - project, testSources.getRuntimeOnlyConfigurationName(), whiteboxJvmTestSuite.getRequiresRuntime()); } @@ -360,4 +360,30 @@ private void revertJvmTestSuiteForWhitebox(JvmTestSuite jvmTestSuite) { test.getJvmArgumentProviders().removeIf(p -> p instanceof WhiteboxTestRuntimeArgumentProvider); }); } + + private void lateConfigureExtendsFrom(Project project) { + ConfigurationContainer configurations = project.getConfigurations(); + whiteboxOrClasspathTestSuites.forEach((testSources, sourcesUnderTest) -> { + extendsFrom( + configurations, + testSources.getImplementationConfigurationName(), + sourcesUnderTest.getImplementationConfigurationName()); + extendsFrom( + configurations, + testSources.getRuntimeOnlyConfigurationName(), + sourcesUnderTest.getRuntimeOnlyConfigurationName()); + extendsFrom( + configurations, + testSources.getCompileOnlyConfigurationName(), + sourcesUnderTest.getCompileOnlyConfigurationName()); + }); + } + + private void extendsFrom(ConfigurationContainer configurations, String testScope, String underTestScope) { + Configuration testScopeConf = configurations.getByName(testScope); + Configuration underTestScopeConf = configurations.getByName(underTestScope); + if (!testScopeConf.getExtendsFrom().contains(underTestScopeConf)) { + testScopeConf.extendsFrom(underTestScopeConf); + } + } } diff --git a/src/test/java/org/gradlex/javamodule/testing/test/CoreFunctionalityTest.java b/src/test/java/org/gradlex/javamodule/testing/test/CoreFunctionalityTest.java index 3d7a1e9..7735119 100644 --- a/src/test/java/org/gradlex/javamodule/testing/test/CoreFunctionalityTest.java +++ b/src/test/java/org/gradlex/javamodule/testing/test/CoreFunctionalityTest.java @@ -13,6 +13,42 @@ class CoreFunctionalityTest { @Test void testCompileOnly_extends_compileOnly_for_whitebox_test_suites() { + build.appBuildFile.appendText( + """ + javaModuleTesting.whitebox(testing.suites["test"]) { + requires.add("org.junit.jupiter.api") + } + dependencies { + compileOnly("jakarta.servlet:jakarta.servlet-api:6.1.0") + }"""); + build.file("app/src/main/java/org/example/app/ServletImpl.java") + .writeText( + """ + package org.example.app; + public abstract class ServletImpl implements jakarta.servlet.Servlet { } + """); + build.file("app/src/test/java/org/example/app/test/ServletMock.java") + .writeText( + """ + package org.example.app.test; + public abstract class ServletMock extends org.example.app.ServletImpl { } + """); + build.appModuleInfoFile.writeText( + """ + module org.example.app { + requires static jakarta.servlet; + } + """); + + var result = build.runner("compileTestJava").build(); + var compileTestResult = result.task(":app:compileTestJava"); + + assertThat(compileTestResult).isNotNull(); + assertThat(compileTestResult.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + } + + @Test + void testCompileOnly_extends_compileOnly_for_classpath_test_suites() { build.appBuildFile.appendText( """ javaModuleTesting.classpath(testing.suites["test"]) @@ -44,4 +80,48 @@ public abstract class ServletMock extends org.example.app.ServletImpl { } assertThat(compileTestResult).isNotNull(); assertThat(compileTestResult.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); } + + @Test + void testImplementation_extends_implementation_for_whitebox_test_suites() { + build.useTestFixturesPlugin(); + build.appBuildFile.appendText( + """ + javaModuleTesting.whitebox(testing.suites["test"]) { + requires.add("org.junit.jupiter.api") + sourcesUnderTest.set(sourceSets.testFixtures) + } + dependencies { + testFixturesImplementation("jakarta.servlet:jakarta.servlet-api:6.1.0") + }"""); + build.file("app/src/testFixtures/java/org/example/app/Main.java") + .writeText(""" + package org.example.app; + public class Main {} + """); + build.file("app/src/testFixtures/java/org/example/app/ServletImpl.java") + .writeText( + """ + package org.example.app; + public abstract class ServletImpl implements jakarta.servlet.Servlet { } + """); + build.file("app/src/test/java/org/example/app/test/ServletMock.java") + .writeText( + """ + package org.example.app.test; + public abstract class ServletMock extends org.example.app.ServletImpl { } + """); + build.file("app/src/testFixtures/java/module-info.java") + .writeText( + """ + module org.example.fixtures { + requires static jakarta.servlet; + } + """); + + var result = build.runner("compileTestJava").build(); + var compileTestResult = result.task(":app:compileTestJava"); + + assertThat(compileTestResult).isNotNull(); + assertThat(compileTestResult.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + } }