Skip to content

Commit 62c63df

Browse files
committed
Conditionally implement Configuration Cache compatibility
I've figured out a way to get the JVM Aggregate Testing plugin to stay compatible with the configuration cache. Unfortunately, it involves the usage of an internal type. Here's what I'm doing to ensure that this doesn't backfire. A private method queries the test suite's implementation for a `Property<JvmTestToolchain<?>>`. It uses that property to return a `Function<Test, Provider<TestFramework>>`. While `TestFramework` is also internal, it has been stabilized while the `JvmTestToolchain<?>` type is an internal implementation of `jvm-test-suite`, which is an incubating plugin. The other thing is that this configuration cache compatibility can only be fulfilled if the participating project itself uses the `jvm-test-suite` plugin. I've already done the work to do this on our own projects, such as AccessTransformers, Unsafe, and Logging Utils.
1 parent 5f57e0b commit 62c63df

File tree

2 files changed

+59
-9
lines changed

2 files changed

+59
-9
lines changed

src/main/groovy/net/minecraftforge/testing/aggregate/AggregateTestExtensionImpl.java

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,26 @@
44
*/
55
package net.minecraftforge.testing.aggregate;
66

7+
import org.codehaus.groovy.runtime.InvokerHelper;
78
import org.codehaus.groovy.runtime.StringGroovyMethods;
89
import org.gradle.api.Project;
910
import org.gradle.api.file.Directory;
1011
import org.gradle.api.file.ProjectLayout;
12+
import org.gradle.api.internal.tasks.testing.TestFramework;
1113
import org.gradle.api.model.ObjectFactory;
1214
import org.gradle.api.plugins.jvm.JvmTestSuite;
15+
import org.gradle.api.provider.Property;
1316
import org.gradle.api.provider.Provider;
1417
import org.gradle.api.tasks.TaskProvider;
1518
import org.gradle.api.tasks.testing.Test;
19+
import org.gradle.api.testing.toolchains.internal.JvmTestToolchain;
1620
import org.gradle.jvm.toolchain.JavaLanguageVersion;
1721
import org.gradle.jvm.toolchain.JavaToolchainService;
1822
import org.gradle.jvm.toolchain.JvmImplementation;
1923
import org.gradle.jvm.toolchain.JvmVendorSpec;
24+
import org.gradle.language.base.plugins.LifecycleBasePlugin;
2025
import org.gradle.testing.base.TestingExtension;
26+
import org.jetbrains.annotations.Nullable;
2127
import org.jetbrains.annotations.UnmodifiableView;
2228

2329
import javax.inject.Inject;
@@ -26,6 +32,7 @@
2632
import java.util.List;
2733
import java.util.Map;
2834
import java.util.TreeMap;
35+
import java.util.function.Function;
2936

3037
abstract class AggregateTestExtensionImpl implements AggregateTestExtensionInternal {
3138
private final Map<String, List<Integer>> jvms = new TreeMap<>(Comparator.naturalOrder());
@@ -67,18 +74,33 @@ private void finish(Project project) {
6774
var suites = testing.getSuites().withType(JvmTestSuite.class);
6875

6976
for (var suite : suites) {
77+
@Nullable var testToolchain = this.getTestToolchain(suite);
7078
for (var target : suite.getTargets()) {
71-
this.configure(project, target.getTestTask().get());
79+
this.configure(project, target.getTestTask().get(), testToolchain);
7280
}
7381
}
7482
} else {
83+
this.problems.reportNotUsingJvmTestSuite();
7584
for (var test : project.getTasks().withType(Test.class)) {
76-
this.configure(project, test);
85+
this.configure(project, test, null);
7786
}
7887
}
7988
}
8089

81-
private void configure(Project project, Test test) {
90+
// NOTE: Avoiding leaking the JvmTestToolchain type since it is part of an incubating plugin's internal implementation and could be removed.
91+
// While TestFramework is also internal, it has since been stabilized and is not expected to be removed.
92+
@SuppressWarnings("unchecked")
93+
private @Nullable Function<Test, Provider<TestFramework>> getTestToolchain(JvmTestSuite suite) {
94+
try {
95+
var testToolchain = (Property<JvmTestToolchain<?>>) InvokerHelper.getProperty(suite, "testToolchain");
96+
return test -> testToolchain.map(t -> t.createTestFramework(test));
97+
} catch (Exception e) {
98+
this.problems.reportTestToolchainInaccessible(e, suite.getName());
99+
return null;
100+
}
101+
}
102+
103+
private void configure(Project project, Test test, @Nullable Function<Test, Provider<TestFramework>> testToolchain) {
82104
if (this.jvms.isEmpty()) return;
83105

84106
TaskProvider<? extends AggregateTest> testAll = project.getTasks().register("jvmAggregate" + StringGroovyMethods.capitalize(test.getName()), AggregateTestImpl.class, task -> {
@@ -91,7 +113,7 @@ private void configure(Project project, Test test) {
91113

92114
for (int version : versions) {
93115
var output = this.getLayout().getBuildDirectory().dir(AggregateTest.TEST_RESULTS_DIRECTORY + "/%s-%d".formatted(vendor, version)).map(this.problems.ensureFileLocation());
94-
var task = this.register(project, test, vendor, version, output);
116+
var task = this.register(project, testToolchain, test, vendor, version, output);
95117

96118
testAll.configure(testAllTask -> {
97119
testAllTask.getInputs().dir(output);
@@ -101,19 +123,23 @@ private void configure(Project project, Test test) {
101123
}
102124
}
103125

104-
private TaskProvider<Test> register(Project project, Test test, String vendor, int version, Provider<Directory> output) {
126+
private TaskProvider<Test> register(Project project, @Nullable Function<Test, Provider<TestFramework>> testToolchain, Test test, String vendor, int version, Provider<Directory> output) {
105127
var vendorSpec = JvmVendorSpec.of(vendor);
106128

107129
return project.getTasks().register("testUsing" + StringGroovyMethods.capitalize(vendor) + version, Test.class, task -> {
108130
var description = test.getDescription();
109131
task.setDescription(description != null
110132
? "%s (using %s %s)".formatted(description, vendorSpec, version)
111133
: "Runs '%s' using %s %s".formatted(test.getName(), vendorSpec, version));
112-
task.notCompatibleWithConfigurationCache(
113-
"JVM-specific tests need the test framework from the parent test task, which cannot be serialized."
114-
);
134+
if (testToolchain != null) {
135+
task.getTestFrameworkProperty().set(testToolchain.apply(task));
136+
} else {
137+
task.getTestFrameworkProperty().set(test.getTestFrameworkProperty());
138+
task.notCompatibleWithConfigurationCache(
139+
"JVM-specific tests need the test framework from the parent test task, which cannot be serialized."
140+
);
141+
}
115142

116-
task.getTestFrameworkProperty().set(test.getTestFrameworkProperty());
117143
task.setClasspath(test.getClasspath());
118144
task.setTestClassesDirs(test.getTestClassesDirs());
119145
task.getJavaLauncher().set(this.getJavaToolchains().launcherFor(spec -> {

src/main/groovy/net/minecraftforge/testing/aggregate/AggregateTestProblems.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package net.minecraftforge.testing.aggregate;
66

77
import net.minecraftforge.gradleutils.shared.EnhancedProblems;
8+
import org.gradle.api.problems.Severity;
89

910
import javax.inject.Inject;
1011
import java.io.Serial;
@@ -16,4 +17,27 @@ abstract class AggregateTestProblems extends EnhancedProblems {
1617
public AggregateTestProblems() {
1718
super(AggregateTestPlugin.NAME, AggregateTestPlugin.DISPLAY_NAME);
1819
}
20+
21+
void reportTestToolchainInaccessible(Exception e, String name) {
22+
this.report("test-toolchain-inaccessible", "Test Toolchain could not be accessed", spec -> spec
23+
.details("""
24+
The '%s' test suite's testing toolchain could not be accessed.
25+
While this is an internal Gradle property, it is used to avoid problems with the configuration cache.
26+
The JVM Aggregate Testing plugin will continue to work, but it will not be compatible with the configuration cache."""
27+
.formatted(name))
28+
.withException(e)
29+
.severity(Severity.WARNING)
30+
.solution(HELP_MESSAGE));
31+
}
32+
33+
void reportNotUsingJvmTestSuite() {
34+
this.report("jvm-test-suite-plugin-not-found", "JVM Test Suite plugin not found", spec -> spec
35+
.details("""
36+
The JVM Test Suite plugin was not found in this project.
37+
The JVM Aggregate Testing uses implementation details from this plugin to stay compatible with the configuration cache.
38+
Consider using the JVM Test Suite plugin in your project, as aggregate testing is configured to work with it.""")
39+
.severity(Severity.ADVICE)
40+
.solution("Apply the `jvm-test-suite` plugin to your project.")
41+
.solution(HELP_MESSAGE));
42+
}
1943
}

0 commit comments

Comments
 (0)