Skip to content

Commit ec6407e

Browse files
authored
initial real plugin (#1)
1 parent fcb3475 commit ec6407e

File tree

14 files changed

+656
-48
lines changed

14 files changed

+656
-48
lines changed

README.md

Lines changed: 104 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,110 @@
22
<a href="https://autorelease.general.dmz.palantir.tech/palantir/gradle-plugin-testing"><img src="https://img.shields.io/badge/Perform%20an-Autorelease-success.svg" alt="Autorelease"></a>
33
</p>
44

5-
## About gradle-plugin-testing
5+
# About
66

7-
Gradle plugin and helpers to assist with writing tests for other gradle plugins
7+
Gradle plugin and helpers to assist with writing tests for other gradle plugins.
88

9-
### Built With
9+
## Using the Plugin
10+
11+
```groovy
12+
apply plugin: 'com.palantir.gradle-plugin-testing'
13+
```
1014

11-
* Groovy
12-
* Java
15+
The plugin automatically adds a `testImplementation` dependency on the `com.palantir.gradle.plugintesting:plugin-testing-core` library so that the helper classes are available in tests.
16+
17+
# Primary Functionality
18+
19+
## Automatic suppression of gradle deprecation warnings
20+
The nebula-test framework will automatically fail a test if it detects gradle deprecation messages in the output. Unfortunately, this will fail if another plugin used in a test is using deprecated gradle components even if the actual plugin under test is clean. The plugin-testing plugin will automatically set the `ignoreDeprecations` system property so that the nebula-test framework will ignore deprecation messages. Use other mechanisms, such as java deprecation linting, to detect and fix deprecations in the plugin under test.
21+
22+
This behavior can be overridden using the `gradleTestUtils` extension. e.g.
23+
24+
```groovy
25+
gradleTestUtils {
26+
ignoreGradleDeprecations = false
27+
}
28+
```
29+
30+
## Resolution of dependencies in generated build files
31+
Integration tests for Gradle plugins often write build files which include other plugins. The version of those included plugins is hardcoded in some way, either directly in the test, e.g.
32+
33+
```groovy
34+
//...within a nebula spec...
35+
buildFile << """
36+
buildscript {
37+
repositories {
38+
mavenCentral()
39+
}
40+
dependencies {
41+
classpath 'com.palantir.gradle.conjure:gradle-conjure:0.0.1'
42+
}
43+
}
44+
dependencies {
45+
implementation 'com.palantir.conjure.java:conjure-lib:0.0.1'
46+
}
47+
"""
48+
```
49+
50+
or in some "Versions" class that has a listing of all the dependencies the test uses. e.g.
51+
```java
52+
final class TestPluginVersions {
53+
static final String CONJURE_JAVA = "com.palantir.conjure.java:conjure-java:5.7.1";
54+
static final String CONJURE = "com.palantir.conjure:conjure:4.10.1";
55+
```
56+
57+
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.
58+
59+
When applied to a project, the `gradle-plugin-testing` plugin scans the `testRuntimeClasspath` configuration for the project and passes all dependencies to the test task as a system property. The version of the dependencies can then be resolved when tests are run and written into generated files. e.g.
60+
61+
```groovy
62+
import static com.palantir.gradle.plugintesting.TestDependencyVersions.resolve
63+
import nebula.test.IntegrationSpec
64+
65+
class HelloWorldSpec extends IntegrationSpec {
66+
def setup() {
67+
//language=gradle
68+
buildFile << """
69+
buildscript {
70+
repositories {
71+
mavenCentral()
72+
}
73+
dependencies {
74+
classpath '${resolve('com.palantir.gradle.conjure:gradle-conjure')}'
75+
}
76+
}
77+
dependencies {
78+
implementation '${resolve('com.palantir.conjure.java:conjure-lib')}'
79+
}
80+
"""
81+
}
82+
```
83+
# Resolution of Gradle versions to test against
84+
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).
85+
86+
The `GradleTestVersions` class can provide up-to-date versions of Gradle to test against.
87+
```groovy
88+
import nebula.test.IntegrationSpec
89+
import com.palantir.gradle.plugintesting.GradleTestVersions
90+
91+
class HelloWorldSpec extends IntegrationSpec {
92+
def 'runs on version of gradle: #version'() {
93+
when:
94+
gradleVersion = version
95+
96+
then:
97+
def result = runTasks('someTask')
98+
result.success
99+
100+
where:
101+
version << GradleTestVersions.gradleVersionsForTests
102+
}
103+
}
104+
```
105+
The plugin sets default versions to test against, but these can be overridden using the `gradleTestUtils` extension. e.g.
106+
107+
```groovy
108+
gradleTestUtils {
109+
gradleVersions = ['7.6.4', '8.8']
110+
}
111+
```

changelog/@unreleased/pr-1.v2.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
type: feature
2+
feature:
3+
description: initial real plugin
4+
links:
5+
- https://github.com/palantir/gradle-plugin-testing/pull/1

gradle-plugin-testing/build.gradle

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ apply plugin: 'com.palantir.external-publish-jar'
44
apply plugin: 'com.palantir.external-publish-gradle-plugin'
55

66
dependencies {
7-
compileOnly gradleApi()
7+
implementation project(':plugin-testing-core')
8+
implementation 'com.google.guava:guava'
89

9-
testImplementation gradleTestKit()
10+
testImplementation 'org.junit.jupiter:junit-jupiter'
1011
testImplementation 'com.netflix.nebula:nebula-test'
1112
}
1213

@@ -16,11 +17,20 @@ gradlePlugin {
1617

1718
plugins {
1819
pluginTestingPlugin {
19-
id = 'com.palantir.plugin-testing'
20+
id = 'com.palantir.gradle-plugin-testing'
2021
displayName = 'Palantir Plugin for testing other gradle plugins'
2122
description = 'A Gradle plugin that detects failures in CircleCI jobs and converts them into JUnit test reports that can be rendered in CircleCI UI.'
2223
tags.set(['failure', 'report', 'reporting', 'circleci'])
2324
implementationClass = 'com.palantir.gradle.plugintesting.PluginTestingPlugin'
2425
}
2526
}
2627
}
28+
29+
test {
30+
systemProperty 'projectVersion', project.version
31+
//TODO(#xxx): Remove this once we have a published version of the plugin that we can apply to the project itself
32+
systemProperty 'ignoreDeprecations', 'true'
33+
34+
// Added as a jar the so in nebula-tests needs to exist in maven local
35+
dependsOn tasks.findByPath(':plugin-testing-core:publishToMavenLocal')
36+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* (c) Copyright 2024 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 org.gradle.api.provider.Property;
20+
import org.gradle.api.provider.SetProperty;
21+
22+
public abstract class PluginTestingExtension {
23+
public static final String EXTENSION_NAME = "gradleTestUtils";
24+
25+
/**
26+
* Whether to set the ignoreDeprecations system property when running tests. This is for nebula tests that will
27+
* fail if there are gradle deprecations.
28+
*/
29+
public abstract Property<Boolean> getIgnoreGradleDeprecations();
30+
31+
/**
32+
* Gradle versions to test against.
33+
*/
34+
public abstract SetProperty<String> getGradleVersions();
35+
36+
public PluginTestingExtension() {
37+
getIgnoreGradleDeprecations().convention(true);
38+
// TODO(#XXX): Should this be the latest gradle 8, or maybe whatever this plugin is compiled against?
39+
// or is this the set of "milestone" versions and we dynamically add the version of the consuming project?
40+
getGradleVersions().convention(GradleTestVersions.DEFAULT_TEST_GRADLE_VERSIONS);
41+
}
42+
}

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

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,95 @@
1616

1717
package com.palantir.gradle.plugintesting;
1818

19+
import java.util.Optional;
20+
import java.util.Set;
21+
import java.util.stream.Collectors;
22+
import org.gradle.api.Action;
23+
import org.gradle.api.NamedDomainObjectProvider;
1924
import org.gradle.api.Plugin;
2025
import org.gradle.api.Project;
26+
import org.gradle.api.Task;
27+
import org.gradle.api.artifacts.Configuration;
28+
import org.gradle.api.artifacts.ModuleDependency;
29+
import org.gradle.api.tasks.SourceSet;
30+
import org.gradle.api.tasks.SourceSetContainer;
31+
import org.gradle.api.tasks.testing.Test;
2132

2233
public class PluginTestingPlugin implements Plugin<Project> {
34+
/**
35+
* Used in tests to pick up the current version of this plugin.
36+
*/
37+
static final String PLUGIN_VERSION_PROPERTY_NAME = "pluginTestingPluginVersion";
2338

39+
/**
40+
* Applies the plugin to the given project.
41+
*/
2442
@Override
25-
public final void apply(Project project) {
26-
project.getTasks().register("plugintestingTask", PluginTestingTask.class);
43+
public void apply(Project project) {
44+
PluginTestingExtension testUtilsExt =
45+
project.getExtensions().create(PluginTestingExtension.EXTENSION_NAME, PluginTestingExtension.class);
46+
47+
addTestDependency(project);
48+
49+
SourceSetContainer sourceSetContainer = project.getExtensions().getByType(SourceSetContainer.class);
50+
SourceSet sourceSet = sourceSetContainer.getByName(SourceSet.TEST_SOURCE_SET_NAME);
51+
NamedDomainObjectProvider<Configuration> testRuntimeConfig =
52+
project.getConfigurations().named(sourceSet.getRuntimeClasspathConfigurationName());
53+
54+
project.getTasks().withType(Test.class).configureEach(test -> {
55+
// need to use the doFirst so that any custom settings on the extension are applied before reading
56+
// the values and setting the system properties.
57+
Action<Task> action = new Action<>() {
58+
@Override
59+
public void execute(Task _task) {
60+
// add system property for all test dependencies so that TestDepVersions can resolve them
61+
Set<String> depSet = getDependencyStrings(testRuntimeConfig.get());
62+
String depsString = String.join(",", depSet);
63+
test.systemProperty(TestDependencyVersions.TEST_DEPENDENCIES_SYSTEM_PROPERTY, depsString);
64+
65+
// add system property for what versions of gradle should be used in tests
66+
String versions =
67+
String.join(",", testUtilsExt.getGradleVersions().get());
68+
test.systemProperty(GradleTestVersions.TEST_GRADLE_VERSIONS_SYSTEM_PROPERTY, versions);
69+
70+
// add system property to ignore gradle deprecations so that nebula tests don't fail
71+
if (testUtilsExt.getIgnoreGradleDeprecations().get()) {
72+
// from
73+
// https://github.com/nebula-plugins/nebula-test/blob/main/src/main/groovy/nebula/test/IntegrationBase.groovy
74+
test.systemProperty("ignoreDeprecations", "true");
75+
}
76+
}
77+
};
78+
test.doFirst(action);
79+
});
80+
}
81+
82+
/**
83+
* Add test dependency on the utility jar to the project so that an explicit dependency statement isn't needed.
84+
* This is done by getting the Implementation-Version metainfo from the compiled jar when this plugin is used
85+
* for real, but that doesn't work when running tests in this repo, so we can also look it up via a gradle property
86+
* that tests set.
87+
*/
88+
private static void addTestDependency(Project project) {
89+
SourceSetContainer sourceSetContainer = project.getExtensions().getByType(SourceSetContainer.class);
90+
SourceSet testSourceSet = sourceSetContainer.getByName(SourceSet.TEST_SOURCE_SET_NAME);
91+
String version = Optional.ofNullable((String) project.findProperty(PLUGIN_VERSION_PROPERTY_NAME))
92+
.or(() -> Optional.ofNullable(
93+
PluginTestingPlugin.class.getPackage().getImplementationVersion()))
94+
.orElseThrow(() -> new RuntimeException("PluginTestingPlugin implementation version not found"));
95+
96+
String testImplConfigName = testSourceSet.getImplementationConfigurationName();
97+
project.getConfigurations().named(testImplConfigName).configure(conf -> {
98+
conf.getDependencies()
99+
.add(project.getDependencies()
100+
.create("com.palantir.gradle.plugintesting:plugin-testing-core:" + version));
101+
});
102+
}
103+
104+
private static Set<String> getDependencyStrings(Configuration config) {
105+
return config.getAllDependencies().stream()
106+
.filter(ModuleDependency.class::isInstance)
107+
.map(dep -> dep.getGroup() + ":" + dep.getName() + ":" + dep.getVersion())
108+
.collect(Collectors.toSet());
27109
}
28110
}

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

Lines changed: 0 additions & 26 deletions
This file was deleted.

0 commit comments

Comments
 (0)