Skip to content

Commit d78c7b5

Browse files
committed
Introduce docker-test plugin for running tests that require Docker
See gh-41228
1 parent 07442f8 commit d78c7b5

File tree

5 files changed

+163
-2
lines changed

5 files changed

+163
-2
lines changed

buildSrc/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ gradlePlugin {
9393
id = "org.springframework.boot.deployed"
9494
implementationClass = "org.springframework.boot.build.DeployedPlugin"
9595
}
96+
dockerTestPlugin {
97+
id = "org.springframework.boot.docker-test"
98+
implementationClass = "org.springframework.boot.build.test.DockerTestPlugin"
99+
}
96100
integrationTestPlugin {
97101
id = "org.springframework.boot.integration-test"
98102
implementationClass = "org.springframework.boot.build.test.IntegrationTestPlugin"

buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenPluginPlugin.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787

8888
import org.springframework.boot.build.DeployedPlugin;
8989
import org.springframework.boot.build.MavenRepositoryPlugin;
90+
import org.springframework.boot.build.test.DockerTestPlugin;
9091
import org.springframework.boot.build.test.IntegrationTestPlugin;
9192
import org.springframework.core.CollectionFactory;
9293
import org.springframework.util.Assert;
@@ -138,11 +139,15 @@ private void addPopulateIntTestMavenRepositoryTask(Project project) {
138139
.set(new File(project.getBuildDir(), "runtime-classpath-repository"));
139140
project.getDependencies()
140141
.components((components) -> components.all(MavenRepositoryComponentMetadataRule.class));
141-
Sync task = project.getTasks().create("populateIntTestMavenRepository", Sync.class);
142-
task.setDestinationDir(new File(project.getBuildDir(), "int-test-maven-repository"));
142+
Sync task = project.getTasks().create("populateTestMavenRepository", Sync.class);
143+
task.setDestinationDir(new File(project.getBuildDir(), "test-maven-repository"));
143144
task.with(copyIntTestMavenRepositoryFiles(project, runtimeClasspathMavenRepository));
144145
task.dependsOn(project.getTasks().getByName(MavenRepositoryPlugin.PUBLISH_TO_PROJECT_REPOSITORY_TASK_NAME));
145146
project.getTasks().getByName(IntegrationTestPlugin.INT_TEST_TASK_NAME).dependsOn(task);
147+
project.getPlugins()
148+
.withType(DockerTestPlugin.class)
149+
.all((dockerTestPlugin) -> project.getTasks()
150+
.named(DockerTestPlugin.DOCKER_TEST_TASK_NAME, (dockerTest) -> dockerTest.dependsOn(task)));
146151
}
147152

148153
private CopySpec copyIntTestMavenRepositoryFiles(Project project,
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
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+
* https://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 org.springframework.boot.build.test;
18+
19+
import org.gradle.api.Project;
20+
import org.gradle.api.provider.Provider;
21+
import org.gradle.api.services.BuildService;
22+
import org.gradle.api.services.BuildServiceParameters;
23+
24+
/**
25+
* Build service for Docker-based tests. Configured to only allow serial execution,
26+
* thereby ensuring that Docker-based tests do not run in parallel.
27+
*
28+
* @author Andy Wilkinson
29+
*/
30+
abstract class DockerTestBuildService implements BuildService<BuildServiceParameters.None> {
31+
32+
static Provider<DockerTestBuildService> registerIfNecessary(Project project) {
33+
return project.getGradle()
34+
.getSharedServices()
35+
.registerIfAbsent("dockerTest", DockerTestBuildService.class, (spec) -> spec.getMaxParallelUsages().set(1));
36+
}
37+
38+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
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+
* https://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 org.springframework.boot.build.test;
18+
19+
import org.gradle.api.Plugin;
20+
import org.gradle.api.Project;
21+
import org.gradle.api.plugins.JavaPlugin;
22+
import org.gradle.api.plugins.JavaPluginExtension;
23+
import org.gradle.api.provider.Provider;
24+
import org.gradle.api.services.BuildService;
25+
import org.gradle.api.tasks.SourceSet;
26+
import org.gradle.api.tasks.SourceSetContainer;
27+
import org.gradle.api.tasks.testing.Test;
28+
import org.gradle.language.base.plugins.LifecycleBasePlugin;
29+
import org.gradle.plugins.ide.eclipse.EclipsePlugin;
30+
import org.gradle.plugins.ide.eclipse.model.EclipseModel;
31+
32+
/**
33+
* Plugin for Docker-based tests. Creates a {@link SourceSet source set}, {@link Test
34+
* test} task, and {@link BuildService shared service} named {@code dockerTest}. The build
35+
* service is configured to only allow serial usage and the {@code dockerTest} task is
36+
* configured to use the build service. In a parallel build, this ensures that only a
37+
* single {@code dockerTest} task can run at any given time.
38+
*
39+
* @author Andy Wilkinson
40+
*/
41+
public class DockerTestPlugin implements Plugin<Project> {
42+
43+
/**
44+
* Name of the {@code dockerTest} task.
45+
*/
46+
public static String DOCKER_TEST_TASK_NAME = "dockerTest";
47+
48+
/**
49+
* Name of the {@code dockerTest} source set.
50+
*/
51+
public static String DOCKER_TEST_SOURCE_SET_NAME = "dockerTest";
52+
53+
/**
54+
* Name of the {@code dockerTest} shared service.
55+
*/
56+
public static String DOCKER_TEST_SERVICE_NAME = "dockerTest";
57+
58+
@Override
59+
public void apply(Project project) {
60+
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> configureDockerTesting(project));
61+
}
62+
63+
private void configureDockerTesting(Project project) {
64+
Provider<DockerTestBuildService> buildService = DockerTestBuildService.registerIfNecessary(project);
65+
SourceSet dockerTestSourceSet = createSourceSet(project);
66+
Provider<Test> dockerTest = createTestTask(project, dockerTestSourceSet, buildService);
67+
project.getTasks().getByName(LifecycleBasePlugin.CHECK_TASK_NAME).dependsOn(dockerTest);
68+
project.getPlugins().withType(EclipsePlugin.class, (eclipsePlugin) -> {
69+
EclipseModel eclipse = project.getExtensions().getByType(EclipseModel.class);
70+
eclipse.classpath((classpath) -> classpath.getPlusConfigurations()
71+
.add(project.getConfigurations()
72+
.getByName(dockerTestSourceSet.getRuntimeClasspathConfigurationName())));
73+
});
74+
}
75+
76+
private SourceSet createSourceSet(Project project) {
77+
SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets();
78+
SourceSet dockerTestSourceSet = sourceSets.create(DOCKER_TEST_SOURCE_SET_NAME);
79+
SourceSet main = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
80+
SourceSet test = sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME);
81+
dockerTestSourceSet.setCompileClasspath(dockerTestSourceSet.getCompileClasspath()
82+
.plus(main.getOutput())
83+
.plus(main.getCompileClasspath())
84+
.plus(test.getOutput()));
85+
dockerTestSourceSet.setRuntimeClasspath(dockerTestSourceSet.getRuntimeClasspath()
86+
.plus(main.getOutput())
87+
.plus(main.getRuntimeClasspath())
88+
.plus(test.getOutput()));
89+
project.getPlugins().withType(IntegrationTestPlugin.class, (integrationTestPlugin) -> {
90+
SourceSet intTest = sourceSets.getByName(IntegrationTestPlugin.INT_TEST_SOURCE_SET_NAME);
91+
dockerTestSourceSet
92+
.setCompileClasspath(dockerTestSourceSet.getCompileClasspath().plus(intTest.getOutput()));
93+
dockerTestSourceSet
94+
.setRuntimeClasspath(dockerTestSourceSet.getRuntimeClasspath().plus(intTest.getOutput()));
95+
});
96+
return dockerTestSourceSet;
97+
}
98+
99+
private Provider<Test> createTestTask(Project project, SourceSet dockerTestSourceSet,
100+
Provider<DockerTestBuildService> buildService) {
101+
Provider<Test> dockerTest = project.getTasks().register(DOCKER_TEST_TASK_NAME, Test.class, (task) -> {
102+
task.usesService(buildService);
103+
task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
104+
task.setDescription("Runs Docker-based tests.");
105+
task.setTestClassesDirs(dockerTestSourceSet.getOutput().getClassesDirs());
106+
task.setClasspath(dockerTestSourceSet.getRuntimeClasspath());
107+
task.shouldRunAfter(JavaPlugin.TEST_TASK_NAME);
108+
});
109+
return dockerTest;
110+
}
111+
112+
}

src/checkstyle/checkstyle-suppressions.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@
5959
<suppress files="[\\/]spring-boot-smoke-tests[\\/]spring-boot-smoke-test-testng[\\/]" checks="SpringJUnit5" />
6060
<suppress files="[\\/]spring-boot-test-support[\\/]" checks="SpringJavadoc" message="\@since" />
6161
<suppress files="[\\/]spring-boot-gradle-test-support[\\/]" checks="SpringJavadoc" message="\@since" />
62+
<suppress files="[\\/]src[\\/]dockerTest[\\/]java[\\/]" checks="JavadocPackage" />
63+
<suppress files="[\\/]src[\\/]dockerTest[\\/]java[\\/]" checks="SpringJavadoc" message="\@since" />
6264
<suppress files="[\\/]src[\\/]intTest[\\/]java[\\/]" checks="JavadocPackage" />
6365
<suppress files="[\\/]src[\\/]intTest[\\/]java[\\/]" checks="SpringJavadoc" message="\@since" />
6466
<suppress files="[\\/]src[\\/]systemTest[\\/]java[\\/]" checks="JavadocPackage" />

0 commit comments

Comments
 (0)