diff --git a/CHANGELOG.md b/CHANGELOG.md index ccaf58f..74950d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Java Module Testing Gradle Plugin - Changelog +## Version 1.3 +* [#18](https://github.com/gradlex-org/java-module-testing/issues/18) Allow whitebox tests to define requires in `java9/module-info.java` (Thanks [brianoliver](https://github.com/brianoliver) for suggesting!) + ## Version 1.2.2 * No duplicated '--add-opens' runtime args diff --git a/README.md b/README.md index 6ac83cc..1c3540f 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,27 @@ javaModuleTesting.whitebox(testing.suites["test"]) { See documentation on [JVM Test Suites](https://docs.gradle.org/current/userguide/jvm_test_suite_plugin.html#sec:jvm_test_suite_configuration) for more details on creating and configuring test suites. +Alternatively, you can put the `requires` into a `module-info.java` file using the same notation that you would use for blackbox tests. +For this, you need to create the file in `/java9/module-info.java`. For example: + +``` +src + ├── main + │ └── java + │ ├── module-info.java + │ └── ... + └── test + ├── java + │ └── ... + └── java9 + └── module-info.java + | module org.example.app.test { + | requires org.example.app; // 'main' module into which the tests are patched + | requires org.junit.jupiter.api; + | } +} +``` + A whitebox _test source set_ does **not** have a `module-info.java`. Instead, the _main_ and _test_ classes will be patched together and the test will run in the _main_ module which now includes the test classes as well. Additional `requires` for the test are defined as shown above. diff --git a/src/main/java/org/gradlex/javamodule/testing/JavaModuleTestingExtension.java b/src/main/java/org/gradlex/javamodule/testing/JavaModuleTestingExtension.java index e706e8b..c0ac479 100644 --- a/src/main/java/org/gradlex/javamodule/testing/JavaModuleTestingExtension.java +++ b/src/main/java/org/gradlex/javamodule/testing/JavaModuleTestingExtension.java @@ -22,6 +22,7 @@ import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.dsl.DependencyHandler; import org.gradle.api.file.FileCollection; +import org.gradle.api.file.RegularFile; import org.gradle.api.plugins.jvm.JvmTestSuite; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.SourceSet; @@ -35,12 +36,16 @@ import org.gradle.testing.base.TestSuite; import org.gradle.testing.base.TestingExtension; import org.gradlex.javamodule.testing.internal.ModuleInfoParser; +import org.gradlex.javamodule.testing.internal.ModuleInfoRequiresParser; import org.gradlex.javamodule.testing.internal.bridges.JavaModuleDependenciesBridge; import org.gradlex.javamodule.testing.internal.provider.WhiteboxTestCompileArgumentProvider; import org.gradlex.javamodule.testing.internal.provider.WhiteboxTestRuntimeArgumentProvider; import javax.inject.Inject; import java.io.File; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; @SuppressWarnings("UnstableApiUsage") public abstract class JavaModuleTestingExtension { @@ -114,11 +119,31 @@ public void whitebox(TestSuite jvmTestSuite, Action conf) if (jvmTestSuite instanceof JvmTestSuite) { WhiteboxJvmTestSuite whiteboxJvmTestSuite = project.getObjects().newInstance(WhiteboxJvmTestSuite.class); whiteboxJvmTestSuite.getSourcesUnderTest().convention(project.getExtensions().getByType(SourceSetContainer.class).getByName(SourceSet.MAIN_SOURCE_SET_NAME)); + whiteboxJvmTestSuite.getRequires().addAll(requiresFromModuleInfo((JvmTestSuite) jvmTestSuite, whiteboxJvmTestSuite.getSourcesUnderTest())); conf.execute(whiteboxJvmTestSuite); configureJvmTestSuiteForWhitebox((JvmTestSuite) jvmTestSuite, whiteboxJvmTestSuite); } } + private Provider> requiresFromModuleInfo(JvmTestSuite jvmTestSuite, Provider sourcesUnderTest) { + RegularFile moduleInfoFile = project.getLayout().getProjectDirectory().file(whiteboxModuleInfo(jvmTestSuite).getAbsolutePath()); + Provider moduleInfoContent = project.getProviders().fileContents(moduleInfoFile).getAsText(); + return moduleInfoContent.map(c -> { + ModuleInfoParser moduleInfoParser = new ModuleInfoParser(project.getLayout(), project.getProviders()); + String mainModuleName = moduleInfoParser.moduleName(sourcesUnderTest.get().getAllJava().getSrcDirs()); + List requires = ModuleInfoRequiresParser.parse(moduleInfoContent.get()); + if (requires.stream().anyMatch(r -> r.equals(mainModuleName))) { + return requires.stream().filter(r -> !r.equals(mainModuleName)).collect(Collectors.toList()); + } + return Collections.emptyList(); + }).orElse(Collections.emptyList()); + } + + private File whiteboxModuleInfo(JvmTestSuite jvmTestSuite) { + File sourceSetDir = jvmTestSuite.getSources().getJava().getSrcDirs().iterator().next().getParentFile(); + return new File(sourceSetDir, "java9/module-info.java"); + } + private void configureJvmTestSuiteForBlackbox(JvmTestSuite jvmTestSuite) { ConfigurationContainer configurations = project.getConfigurations(); TaskContainer tasks = project.getTasks(); diff --git a/src/main/java/org/gradlex/javamodule/testing/internal/ModuleInfoRequiresParser.java b/src/main/java/org/gradlex/javamodule/testing/internal/ModuleInfoRequiresParser.java new file mode 100644 index 0000000..32d07fd --- /dev/null +++ b/src/main/java/org/gradlex/javamodule/testing/internal/ModuleInfoRequiresParser.java @@ -0,0 +1,65 @@ +/* + * Copyright the GradleX team. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradlex.javamodule.testing.internal; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class ModuleInfoRequiresParser { + + public static List parse(String moduleInfoFileContent) { + List requires = new ArrayList<>(); + boolean insideComment = false; + for(String line: moduleInfoFileContent.split("\n")) { + insideComment = parseLine(line, insideComment, requires); + } + return requires; + } + + /** + * @return true, if we are inside a multi-line comment after this line + */ + private static boolean parseLine(String moduleLine, boolean insideComment, List requires) { + if (insideComment) { + return !moduleLine.contains("*/"); + } + + List tokens = Arrays.asList(moduleLine + .replace(";", "") + .replace("{", "") + .replaceAll("/\\*.*?\\*/", " ") + .trim().split("\\s+")); + int singleLineCommentStartIndex = tokens.indexOf("//"); + if (singleLineCommentStartIndex >= 0) { + tokens = tokens.subList(0, singleLineCommentStartIndex); + } + + if (tokens.size() > 1 && tokens.get(0).equals("requires")) { + if (tokens.size() > 3 && tokens.contains("static") && tokens.contains("transitive")) { + requires.add(tokens.get(3)); + } else if (tokens.size() > 2 && tokens.contains("transitive")) { + requires.add(tokens.get(2)); + } else if (tokens.size() > 2 && tokens.contains("static")) { + requires.add(tokens.get(2)); + } else { + requires.add(tokens.get(1)); + } + } + return moduleLine.lastIndexOf("/*") > moduleLine.lastIndexOf("*/"); + } +} diff --git a/src/test/groovy/org/gradlex/javamodule/testing/test/CustomizationTest.groovy b/src/test/groovy/org/gradlex/javamodule/testing/test/CustomizationTest.groovy index 67d197b..078d4a0 100644 --- a/src/test/groovy/org/gradlex/javamodule/testing/test/CustomizationTest.groovy +++ b/src/test/groovy/org/gradlex/javamodule/testing/test/CustomizationTest.groovy @@ -31,6 +31,26 @@ class CustomizationTest extends Specification { result.task(":app:test").outcome == TaskOutcome.SUCCESS } + def "can define whitebox test suite requires in module-info file"() { + given: + appModuleInfoFile << ''' + module org.example.app { + } + ''' + appWhiteboxTestModuleInfoFile << ''' + module org.example.app.test { + requires org.example.app; + requires org.junit.jupiter.api; + } + ''' + + when: + def result = runTests() + + then: + result.task(":app:test").outcome == TaskOutcome.SUCCESS + } + def "repetitive blackbox calls on the same test suite have no effect"() { given: appBuildFile << ''' diff --git a/src/test/groovy/org/gradlex/javamodule/testing/test/fixture/GradleBuild.groovy b/src/test/groovy/org/gradlex/javamodule/testing/test/fixture/GradleBuild.groovy index 5c9a654..8b2fc3a 100644 --- a/src/test/groovy/org/gradlex/javamodule/testing/test/fixture/GradleBuild.groovy +++ b/src/test/groovy/org/gradlex/javamodule/testing/test/fixture/GradleBuild.groovy @@ -13,6 +13,7 @@ class GradleBuild { final File appBuildFile final File appModuleInfoFile final File appTestModuleInfoFile + final File appWhiteboxTestModuleInfoFile final File libBuildFile final File libModuleInfoFile @@ -24,6 +25,7 @@ class GradleBuild { this.appBuildFile = file("app/build.gradle.kts") this.appModuleInfoFile = file("app/src/main/java/module-info.java") this.appTestModuleInfoFile = file("app/src/test/java/module-info.java") + this.appWhiteboxTestModuleInfoFile = file("app/src/test/java9/module-info.java") this.libBuildFile = file("lib/build.gradle.kts") this.libModuleInfoFile = file("lib/src/main/java/module-info.java")