Skip to content

Commit e12dd61

Browse files
authored
Create the gradle/gradle-test-versions.yml file (#287)
Create the gradle/gradle-test-versions.yml file
1 parent 1039501 commit e12dd61

File tree

9 files changed

+445
-26
lines changed

9 files changed

+445
-26
lines changed

README.md

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ or in some "Versions" class that has a listing of all the dependencies the test
5252
final class TestPluginVersions {
5353
static final String CONJURE_JAVA = "com.palantir.conjure.java:conjure-java:5.7.1";
5454
static final String CONJURE = "com.palantir.conjure:conjure:4.10.1";
55+
}
5556
```
5657

5758
Once these tests are written, the versions of the plugins are often not updated, even when the project under test keeps its dependencies of those plugins up-to-date. This can cause tests to fail when the plugin is updated not because the plugin is bad, but because there is an incompatibility in the old versions of plugins used in the integration tests. This can often happen with Gradle version bumps.
@@ -106,7 +107,53 @@ dependencies {
106107
# Resolution of Gradle versions to test against
107108
Similarly, tests may hardcode versions of Gradle that they need to stay compatible with. These versions also get stale and PRs start failing for the inverse reason of the above - the code in the plugin or a dependency of it is updated and is no longer compatible with an old version of Gradle. For example, attempting to update jackson libraries from `2.15.0` -> `2.17.0` would fail if a test tried to run on Gradle versiosn < `7.6.4` (when compatibility with jackson `2.17.0` was fixed).
108109

109-
The `GradleTestVersions` class can provide up-to-date versions of Gradle to test against.
110+
## Setting Gradle test versions
111+
112+
There are two ways to set which Gradle versions to test against:
113+
* setting the versions in the `gradle/gradle-test-versions.yml` file
114+
* setting `gradleVersions` in the the `gradleTestUtils` extension (this is deprecated)
115+
When using `@GradlePluginTests`, the versions of Gradle used will be the union of the versions above.
116+
117+
If you haven't set either, tests will fall back to using the Gradle version of the test driver.
118+
119+
### The `gradle/gradle-test-versions.yml` file
120+
121+
An example file:
122+
123+
```yaml
124+
major-versions:
125+
8: 8.14.3
126+
9: 9.2.0-rc1
127+
extra-versions:
128+
- 8.8
129+
- 8.14.2
130+
```
131+
The motivation for having a separate config file is to more easily allow an automated process of bumping the versions of Gradle we test against, whilst also allowing users to pick out specific versions to test against.
132+
Concretely, an automated process would bump versions in the `major-versions` blob, whilst leaving `extra-versions` alone.
133+
When support for a Gradle major version is phased out, it can be just removed from this file in one fell swoop.
134+
135+
> [!NOTE]
136+
> This file is global, and affects every project that is using this plugin.
137+
138+
### [Deprecated] Setting `gradleVersions` in the `gradleTestUtils` extension
139+
You can set Gradle versions to test by setting `gradleVersions` property on the `gradleTestUtils` extension. For example, to test against Gradle versions `7.6.4` and `8.8`, you would do:
140+
```groovy
141+
gradleTestUtils {
142+
gradleVersions = ['7.6.4', '8.8']
143+
}
144+
```
145+
146+
> [!WARNING]
147+
> This is deprecated because it's merges together the intent of a user wanting a _specific_ version and also the intent of testing against the latest versions of Gradle. How does an automated process know if the user wanted to test this specific version which also happens to be the latest version?## Accessing Gradle test versions
148+
149+
### Java tests with `@GradlePluginTests`
150+
151+
Tests annotated with `@GradlePluginTests` automatically run with all the versions specified in the config file.
152+
153+
### [Deprecated] Groovy tests with spock e.g. `IntegrationSpec`
154+
155+
Access Gradle test versions via the `GradleTestVersions#gradleVersionForTests` method
156+
110157
```groovy
111158
import nebula.test.IntegrationSpec
112159
import com.palantir.gradle.plugintesting.GradleTestVersions
@@ -125,13 +172,6 @@ class HelloWorldSpec extends IntegrationSpec {
125172
}
126173
}
127174
```
128-
The plugin sets default versions to test against, but these can be overridden using the `gradleTestUtils` extension. e.g.
129-
130-
```groovy
131-
gradleTestUtils {
132-
gradleVersions = ['7.6.4', '8.8']
133-
}
134-
```
135175

136176
# Gradle Plugin Testing Framework
137177

gradle-plugin-testing/build.gradle

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,18 @@ apply plugin: 'com.palantir.external-publish-gradle-plugin'
66

77
dependencies {
88
implementation project(':plugin-testing-core')
9+
implementation 'com.fasterxml.jackson.core:jackson-databind'
10+
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml'
911
implementation 'com.palantir.baseline:gradle-baseline-java'
1012
implementation 'com.palantir.suppressible-error-prone:gradle-suppressible-error-prone'
13+
implementation 'one.util:streamex'
14+
implementation 'com.google.guava:guava'
15+
16+
annotationProcessor 'org.immutables:value'
1117

1218
compileOnly 'com.palantir.gradle.consistentversions:gradle-consistent-versions'
19+
compileOnlyApi 'org.immutables:value::annotations'
20+
1321

1422
testImplementation project(':gradle-plugin-testing-junit')
1523
testImplementation 'com.netflix.nebula:nebula-test'
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* (c) Copyright 2025 Palantir Technologies Inc. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.palantir.gradle.plugintesting;
18+
19+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
20+
import com.fasterxml.jackson.annotation.JsonProperty;
21+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
22+
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
23+
import com.google.common.collect.ImmutableSortedSet;
24+
import java.util.SortedMap;
25+
import java.util.SortedSet;
26+
import java.util.TreeSet;
27+
import one.util.streamex.EntryStream;
28+
import one.util.streamex.StreamEx;
29+
import org.immutables.value.Value;
30+
31+
@Value.Immutable
32+
@JsonSerialize(as = ImmutableGradleTestVersionsConfig.class)
33+
@JsonDeserialize(as = ImmutableGradleTestVersionsConfig.class)
34+
@JsonIgnoreProperties(ignoreUnknown = true)
35+
public interface GradleTestVersionsConfig {
36+
@JsonProperty("major-versions")
37+
@Value.NaturalOrder
38+
SortedMap<Integer, String> majorVersions();
39+
40+
@JsonProperty("extra-versions")
41+
@Value.NaturalOrder
42+
SortedSet<String> extraVersions();
43+
44+
final class Builder extends ImmutableGradleTestVersionsConfig.Builder {}
45+
46+
static Builder builder() {
47+
return new Builder();
48+
}
49+
50+
default GradleTestVersionsConfig withoutMajorVersion(int majorVersion) {
51+
SortedMap<Integer, String> newMajorVersions = EntryStream.of(majorVersions())
52+
.filterKeys(maj -> maj != majorVersion)
53+
.toSortedMap();
54+
SortedSet<String> newExtraVersions = StreamEx.of(extraVersions())
55+
.filter(version -> getMajorVersion(version) != majorVersion)
56+
.toCollection(TreeSet::new);
57+
return builder()
58+
.from(this)
59+
.majorVersions(newMajorVersions)
60+
.extraVersions(newExtraVersions)
61+
.build();
62+
}
63+
64+
default SortedSet<String> allVersions() {
65+
return ImmutableSortedSet.<String>naturalOrder()
66+
.addAll(majorVersions().values())
67+
.addAll(extraVersions())
68+
.build();
69+
}
70+
71+
private static int getMajorVersion(String newMajorVersion) {
72+
int dotIndex = newMajorVersion.indexOf('.');
73+
String majorVersionStr = dotIndex == -1 ? newMajorVersion : newMajorVersion.substring(0, dotIndex);
74+
return Integer.parseInt(majorVersionStr);
75+
}
76+
}

gradle-plugin-testing/src/main/java/com/palantir/gradle/plugintesting/PluginTestingExtension.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ public abstract class PluginTestingExtension {
3030

3131
/**
3232
* Gradle versions to test against.
33+
* @deprecated Use {@code gradle/gradle-test-versions.yml} to set Gradle versions to test against. This property
34+
* will be removed as part of rolling out {@code gradle/gradle-test-versions.yml}.
3335
*/
36+
@Deprecated
3437
public abstract SetProperty<String> getGradleVersions();
3538

3639
/**
@@ -41,8 +44,5 @@ public abstract class PluginTestingExtension {
4144
public PluginTestingExtension() {
4245
getIgnoreGradleDeprecations().convention(true);
4346
getConfigurationCacheEnabled().convention(false);
44-
// TODO(#XXX): Should this be the latest gradle 8, or maybe whatever this plugin is compiled against?
45-
// or is this the set of "milestone" versions and we dynamically add the version of the consuming project?
46-
getGradleVersions().convention(GradleTestVersions.DEFAULT_TEST_GRADLE_VERSIONS);
4747
}
4848
}

gradle-plugin-testing/src/main/java/com/palantir/gradle/plugintesting/PluginTestingPlugin.java

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
*/
1616
package com.palantir.gradle.plugintesting;
1717

18+
import com.fasterxml.jackson.core.JsonProcessingException;
19+
import com.fasterxml.jackson.core.type.TypeReference;
20+
import com.fasterxml.jackson.databind.ObjectMapper;
21+
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
22+
import com.google.common.collect.ImmutableSet;
1823
import com.palantir.baseline.tasks.CheckUnusedDependenciesParentTask;
1924
import com.palantir.gradle.plugintesting.TestDependencyVersionsTask.TestDependency;
2025
import com.palantir.gradle.suppressibleerrorprone.SuppressibleErrorProneExtension;
@@ -24,6 +29,7 @@
2429
import java.util.Optional;
2530
import java.util.Set;
2631
import java.util.stream.Collectors;
32+
import javax.inject.Inject;
2733
import org.gradle.api.Action;
2834
import org.gradle.api.NamedDomainObjectProvider;
2935
import org.gradle.api.Plugin;
@@ -32,15 +38,19 @@
3238
import org.gradle.api.artifacts.Configuration;
3339
import org.gradle.api.artifacts.DependencyScopeConfiguration;
3440
import org.gradle.api.artifacts.ResolvableConfiguration;
41+
import org.gradle.api.file.ProjectLayout;
42+
import org.gradle.api.file.RegularFile;
3543
import org.gradle.api.provider.Provider;
44+
import org.gradle.api.provider.ProviderFactory;
3645
import org.gradle.api.tasks.SourceSet;
3746
import org.gradle.api.tasks.SourceSetContainer;
3847
import org.gradle.api.tasks.TaskProvider;
3948
import org.gradle.api.tasks.testing.Test;
4049
import org.gradle.plugin.devel.plugins.JavaGradlePluginPlugin;
4150
import org.gradle.plugin.devel.tasks.PluginUnderTestMetadata;
51+
import org.gradle.util.GradleVersion;
4252

43-
public class PluginTestingPlugin implements Plugin<Project> {
53+
public abstract class PluginTestingPlugin implements Plugin<Project> {
4454
/**
4555
* Used in tests to pick up the current version of this plugin.
4656
*/
@@ -50,9 +60,16 @@ public class PluginTestingPlugin implements Plugin<Project> {
5060
List.of("plugin-testing-core", "configuration-cache-spec", "gradle-plugin-testing-junit");
5161

5262
public static final Set<String> PATCHABLE_CHECKS = Set.of("GradleTestStringFormatting", "GradleTestPluginsBlock");
63+
public static final ObjectMapper YAML_MAPPER = new ObjectMapper(new YAMLFactory());
5364

5465
private static final String MAVEN_GROUP = "com.palantir.gradle.plugintesting";
5566

67+
@Inject
68+
protected abstract ProviderFactory getProviderFactory();
69+
70+
@Inject
71+
protected abstract ProjectLayout getProjectLayout();
72+
5673
private static String coreMavenCoordinates(String name) {
5774
return MAVEN_GROUP + ":" + name;
5875
}
@@ -71,7 +88,7 @@ public void apply(Project project) {
7188
}
7289

7390
@SuppressWarnings("for-rollout:TaskDependsOn")
74-
private static void doApply(Project project) {
91+
private void doApply(Project project) {
7592
PluginTestingExtension testUtilsExt = project.getExtensions().getByType(PluginTestingExtension.class);
7693

7794
setupErrorprones(project);
@@ -112,8 +129,18 @@ public void execute(Task _task) {
112129
testDependenciesFileAbsolutePath.get());
113130

114131
// add system property for what versions of gradle should be used in tests
115-
String versions =
116-
String.join(",", testUtilsExt.getGradleVersions().get());
132+
Set<String> versionsFromConfig =
133+
readGradleTestingVersionsConfigFile().getOrElse(Set.of());
134+
Set<String> versionsFromExtension =
135+
testUtilsExt.getGradleVersions().get();
136+
Set<String> gradleTestVersions = ImmutableSet.<String>builder()
137+
.addAll(versionsFromConfig)
138+
.addAll(versionsFromExtension)
139+
.build();
140+
if (gradleTestVersions.isEmpty()) {
141+
gradleTestVersions = Set.of(GradleVersion.current().getVersion());
142+
}
143+
String versions = String.join(",", gradleTestVersions);
117144
test.systemProperty(GradleTestVersions.TEST_GRADLE_VERSIONS_SYSTEM_PROPERTY, versions);
118145

119146
// add system property for whether to use configuration-cache by default in tests
@@ -207,4 +234,21 @@ private static void addGradlePluginForTestingConfigurations(Project project) {
207234
.test(scope -> scope.from(gradlePluginForTestingResolvable.getName()));
208235
});
209236
}
237+
238+
private Provider<Set<String>> readGradleTestingVersionsConfigFile() {
239+
RegularFile testVersionsConfigPath =
240+
getProjectLayout().getProjectDirectory().file("gradle/gradle-test-versions.yml");
241+
242+
Provider<GradleTestVersionsConfig> config = getProviderFactory()
243+
.fileContents(testVersionsConfigPath)
244+
.getAsText()
245+
.map(text -> {
246+
try {
247+
return YAML_MAPPER.readValue(text, new TypeReference<>() {});
248+
} catch (JsonProcessingException e) {
249+
throw new IllegalArgumentException("Failed to parse gradle/gradle-test-versions.yml", e);
250+
}
251+
});
252+
return config.map(GradleTestVersionsConfig::allVersions);
253+
}
210254
}

0 commit comments

Comments
 (0)