Skip to content

Commit

Permalink
Base plugin on 'test suite' API
Browse files Browse the repository at this point in the history
  • Loading branch information
jjohannes committed May 11, 2022
1 parent 23e6c4d commit 93e9fd2
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 170 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package de.jjohannes.gradle.moduletesting;

import de.jjohannes.gradle.moduletesting.internal.ModuleInfoParser;
import de.jjohannes.gradle.moduletesting.internal.bridges.JavaModuleDependenciesBridge;
import de.jjohannes.gradle.moduletesting.internal.provider.WhiteboxTestCompileArgumentProvider;
import de.jjohannes.gradle.moduletesting.internal.provider.WhiteboxTestRuntimeArgumentProvider;
import org.gradle.api.Action;
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.plugins.jvm.JvmTestSuite;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.TaskContainer;
Expand All @@ -13,14 +19,13 @@
import org.gradle.api.tasks.testing.Test;
import org.gradle.internal.jvm.JavaModuleDetector;
import org.gradle.jvm.tasks.Jar;
import org.gradle.language.base.plugins.LifecycleBasePlugin;
import org.gradle.testing.base.TestSuite;

import javax.inject.Inject;
import java.util.List;
import java.util.Map;

@SuppressWarnings("UnstableApiUsage")
public abstract class JavaModuleTestingExtension {
private static final Action<WhiteboxTestSet> NO_OP_ACTION = c -> {};
private static final Action<WhiteboxJvmTestSuite> NO_OP_ACTION = c -> {};

private final Project project;
private final JavaModuleDetector moduleDetector;
Expand All @@ -31,63 +36,32 @@ public JavaModuleTestingExtension(Project project, JavaModuleDetector moduleDete
this.moduleDetector = moduleDetector;
}

public void blackboxTests(String testSourceSetName) {
SourceSet sourceSet = configureSourceSetForTesting(testSourceSetName);
configureSourceSetForBlackbox(sourceSet);
}

public void junit5WhiteboxTests(String testSourceSetName, String junit5Version) {
junit5WhiteboxTests(testSourceSetName, junit5Version, NO_OP_ACTION);
}

public void junit5WhiteboxTests(String testSourceSetName, String junit5Version, Action<WhiteboxTestSet> conf) {
whiteboxTests(testSourceSetName, "org.junit.jupiter.api", "org.junit.jupiter:junit-jupiter-api:" + junit5Version, conf);
project.getTasks().named(testSourceSetName, Test.class).configure(Test::useJUnitPlatform);
}

public void junit4WhiteboxTests(String testSourceSetName, String junit4Version) {
junit4WhiteboxTests(testSourceSetName, junit4Version, NO_OP_ACTION);
public void blackbox(TestSuite jvmTestSuite) {
if (jvmTestSuite instanceof JvmTestSuite) {
configureJvmTestSuiteForBlackbox((JvmTestSuite) jvmTestSuite);
}
}

public void junit4WhiteboxTests(String testSourceSetName, String junit4Version, Action<WhiteboxTestSet> conf) {
whiteboxTests(testSourceSetName, "junit", "junit:junit:" + junit4Version, conf);
project.getTasks().named(testSourceSetName, Test.class).configure(Test::useJUnit);
}
public void whitebox(TestSuite jvmTestSuite) {
whitebox(jvmTestSuite, NO_OP_ACTION);

private void whiteboxTests(String testSourceSetName, String testFrameworkModuleName, String testFrameworkGAV, Action<WhiteboxTestSet> conf) {
WhiteboxTestSet whiteboxTestSet = new WhiteboxTestSet();
conf.execute(whiteboxTestSet);
SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
SourceSet mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
SourceSet testSourceSet = configureSourceSetForTesting(testSourceSetName);
configureSourceSetForWhitebox(mainSourceSet, testSourceSet, testFrameworkModuleName, testFrameworkGAV, whiteboxTestSet.getTestRequires());
}

private SourceSet configureSourceSetForTesting(String name) {
SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
TaskContainer tasks = project.getTasks();
SourceSet sourceSet = sourceSets.maybeCreate(name);

TaskProvider<Test> testTask;
if (!tasks.getNames().contains(name)) {
testTask = tasks.register(name, Test.class, t -> {
t.setDescription("Runs " + name + " tests.");
t.setGroup("verification");
});
tasks.named(LifecycleBasePlugin.CHECK_TASK_NAME, t-> {
t.dependsOn(testTask);
});
public void whitebox(TestSuite jvmTestSuite, Action<WhiteboxJvmTestSuite> 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));
conf.execute(whiteboxJvmTestSuite);
configureJvmTestSuiteForWhitebox((JvmTestSuite) jvmTestSuite, whiteboxJvmTestSuite);
}

return sourceSet;
}

private void configureSourceSetForBlackbox(SourceSet sourceSet) {
private void configureJvmTestSuiteForBlackbox(JvmTestSuite jvmTestSuite) {
ConfigurationContainer configurations = project.getConfigurations();
DependencyHandler dependencies = project.getDependencies();
TaskContainer tasks = project.getTasks();

TaskProvider<Jar> jarTask;
SourceSet sourceSet = jvmTestSuite.getSources();
if (!tasks.getNames().contains(sourceSet.getJarTaskName())) {
jarTask = tasks.register(sourceSet.getJarTaskName(), Jar.class, t -> {
t.getArchiveClassifier().set(sourceSet.getName());
Expand All @@ -98,61 +72,58 @@ private void configureSourceSetForBlackbox(SourceSet sourceSet) {
}

tasks.named(sourceSet.getName(), Test.class, t -> {
// Classpath consists only of Jars to include classes+resources in one place
t.setClasspath(configurations.getByName(sourceSet.getRuntimeClasspathConfigurationName()).plus(project.files(jarTask)));
// Rest test classes dir to allow switching back from 'whitebox' to 'blackbox'
t.setTestClassesDirs(sourceSet.getOutput().getClassesDirs());
});
dependencies.add(sourceSet.getImplementationConfigurationName(), project);
}

private void configureSourceSetForWhitebox(SourceSet mainSourceSet, SourceSet testSourceSet, String testFrameworkModuleName, String testFrameworkGAV, List<String> testRequires) {
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());

tasks.named(testSourceSet.getCompileJavaTaskName(), JavaCompile.class, t -> {
t.setClasspath(mainSourceSet.getOutput().plus(configurations.getByName(testSourceSet.getCompileClasspathConfigurationName())));
t.getOptions().getCompilerArgumentProviders().add(new WhiteboxTestCompileArgumentProvider(
mainSourceSet.getJava().getSrcDirs(),
testSourceSet.getJava().getSrcDirs(),
t,
testFrameworkModuleName,
testRequires,
SourceSet testSources = jvmTestSuite.getSources();
tasks.named(testSources.getCompileJavaTaskName(), JavaCompile.class, compileJava -> {
SourceSet sourcesUnderTest = whiteboxJvmTestSuite.getSourcesUnderTest().get();
compileJava.setClasspath(sourcesUnderTest.getOutput().plus(configurations.getByName(testSources.getCompileClasspathConfigurationName())));
compileJava.getOptions().getCompilerArgumentProviders().add(new WhiteboxTestCompileArgumentProvider(
sourcesUnderTest.getJava().getSrcDirs(),
testSources.getJava().getSrcDirs(),
compileJava,
whiteboxJvmTestSuite.getRequires(),
moduleDetector,
moduleInfoParser));
});

tasks.named(testSourceSet.getName(), Test.class, t -> {
t.setClasspath(configurations.getByName(testSourceSet.getRuntimeClasspathConfigurationName()).plus(mainSourceSet.getOutput()).plus(testSourceSet.getOutput()));
tasks.named(testSources.getName(), Test.class, test -> {
SourceSet sourcesUnderTest = whiteboxJvmTestSuite.getSourcesUnderTest().get();
test.setClasspath(configurations.getByName(testSources.getRuntimeClasspathConfigurationName()).plus(sourcesUnderTest.getOutput()).plus(testSources.getOutput()));

// Add main classes here so that Gradle finds module-info.class and treats this as a test with module path
t.setTestClassesDirs(mainSourceSet.getOutput().getClassesDirs().plus(testSourceSet.getOutput().getClassesDirs()));

t.getJvmArgumentProviders().add(new WhiteboxTestRuntimeArgumentProvider(
mainSourceSet.getJava().getSrcDirs(),
testSourceSet.getJava().getClassesDirectory(),
testFrameworkModuleName,
testRequires,
test.setTestClassesDirs(sourcesUnderTest.getOutput().getClassesDirs().plus(testSources.getOutput().getClassesDirs()));

test.getJvmArgumentProviders().add(new WhiteboxTestRuntimeArgumentProvider(
sourcesUnderTest.getJava().getSrcDirs(),
testSources.getJava().getClassesDirectory(),
sourcesUnderTest.getOutput().getResourcesDir(),
testSources.getOutput().getResourcesDir(),
whiteboxJvmTestSuite.getRequires(),
whiteboxJvmTestSuite.getOpensTo(),
moduleInfoParser
));
});

Configuration implementation = configurations.getByName(testSourceSet.getImplementationConfigurationName());
Configuration runtimeOnly = configurations.getByName(testSourceSet.getRuntimeOnlyConfigurationName());

implementation.extendsFrom(configurations.getByName(mainSourceSet.getImplementationConfigurationName()));
runtimeOnly.extendsFrom(configurations.getByName(mainSourceSet.getRuntimeOnlyConfigurationName()));

dependencies.add(implementation.getName(), testFrameworkGAV);

Configuration implementation = configurations.getByName(testSources.getImplementationConfigurationName());
implementation.withDependencies(d -> {
for (String requiresModuleName : testRequires) {
Map<?, ?> gav = JavaModuleDependenciesBridge.gav(project, requiresModuleName);
for (String requiresModuleName : whiteboxJvmTestSuite.getRequires().get()) {
Provider<?> gav = JavaModuleDependenciesBridge.gav(project, requiresModuleName);
if (gav != null) {
dependencies.add(implementation.getName(), gav);
dependencies.addProvider(implementation.getName(), gav);
}
}
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ public abstract class JavaModuleTestingPlugin implements Plugin<Project> {

@Override
public void apply(Project project) {
if (GradleVersion.current().compareTo(GradleVersion.version("7.0")) < 0) {
throw new RuntimeException("This plugin requires Gradle 7.0+");
if (GradleVersion.current().compareTo(GradleVersion.version("7.4")) < 0) {
throw new RuntimeException("This plugin requires Gradle 7.4+");
}

project.getExtensions().create("javaModuleTesting", JavaModuleTestingExtension.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package de.jjohannes.gradle.moduletesting;

import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.SourceSet;

public interface WhiteboxJvmTestSuite {

Property<SourceSet> getSourcesUnderTest();

ListProperty<String> getRequires();

ListProperty<String> getOpensTo();
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package de.jjohannes.gradle.moduletesting;
package de.jjohannes.gradle.moduletesting.internal;

import org.gradle.api.file.ProjectLayout;
import org.gradle.api.file.RegularFile;
Expand All @@ -23,7 +23,7 @@ public ModuleInfoParser(ProjectLayout layout, ProviderFactory providers) {
public String moduleName(Set<File> sourceFolders) {
for (File folder : sourceFolders) {
Provider<RegularFile> moduleInfoFile = layout.file(providers.provider(() -> new File(folder, "module-info.java")));
Provider<String> moduleInfoContent = providers.fileContents(moduleInfoFile).getAsText().forUseAtConfigurationTime();
Provider<String> moduleInfoContent = providers.fileContents(moduleInfoFile).getAsText();
if (moduleInfoContent.isPresent()) {
return moduleName(moduleInfoContent.get());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
package de.jjohannes.gradle.moduletesting;
package de.jjohannes.gradle.moduletesting.internal.bridges;

import org.gradle.api.Project;
import org.gradle.api.provider.Provider;

import java.lang.reflect.Method;
import java.util.Map;

public class JavaModuleDependenciesBridge {

public static Map<?, ?> gav(Project project, String moduleName) {
public static Provider<?> gav(Project project, String moduleName) {
Object javaModuleDependencies = project.getExtensions().findByName("javaModuleDependencies");
if (javaModuleDependencies == null) {
return null;
}
try {
Method gav = javaModuleDependencies.getClass().getMethod("gav", String.class);
return (Map<?, ?>) gav.invoke(javaModuleDependencies, moduleName);
return (Provider<?>) gav.invoke(javaModuleDependencies, moduleName);
} catch (Exception e) {
throw new RuntimeException(e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package de.jjohannes.gradle.moduletesting;
package de.jjohannes.gradle.moduletesting.internal.provider;


import de.jjohannes.gradle.moduletesting.internal.ModuleInfoParser;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.compile.JavaCompile;
import org.gradle.internal.jvm.JavaModuleDetector;
import org.gradle.process.CommandLineArgumentProvider;
Expand All @@ -15,16 +17,14 @@ public class WhiteboxTestCompileArgumentProvider implements CommandLineArgumentP
private final Set<File> mainSourceFolders;
private final Set<File> testSourceFolders;
private final JavaCompile task;
private final String testFrameworkAPI;
private final List<String> testRequires;
private final Provider<List<String>> testRequires;
private final JavaModuleDetector moduleDetector;
private final ModuleInfoParser moduleInfoParser;

public WhiteboxTestCompileArgumentProvider(Set<File> mainSourceFolders, Set<File> testSourceFolders, JavaCompile task, String testFrameworkAPI, List<String> testRequires, JavaModuleDetector moduleDetector, ModuleInfoParser moduleInfoParser) {
public WhiteboxTestCompileArgumentProvider(Set<File> mainSourceFolders, Set<File> testSourceFolders, JavaCompile task, Provider<List<String>> testRequires, JavaModuleDetector moduleDetector, ModuleInfoParser moduleInfoParser) {
this.mainSourceFolders = mainSourceFolders;
this.testSourceFolders = testSourceFolders;
this.task = task;
this.testFrameworkAPI = testFrameworkAPI;
this.testRequires = testRequires;
this.moduleDetector = moduleDetector;
this.moduleInfoParser = moduleInfoParser;
Expand All @@ -42,13 +42,7 @@ public Iterable<String> asArguments() {
args.add("--module-path");
args.add(moduleDetector.inferModulePath(true, task.getClasspath()).getFiles().stream().map(File::getPath).collect(Collectors.joining(cpSeparator)));

// Add module dependency to test framework
args.add("--add-modules");
args.add(testFrameworkAPI);
args.add("--add-reads");
args.add(moduleName + "=" + testFrameworkAPI);

for (String testRequires : testRequires) {
for (String testRequires : testRequires.get()) {
args.add("--add-modules");
args.add(testRequires);
args.add("--add-reads");
Expand Down
Loading

0 comments on commit 93e9fd2

Please sign in to comment.