From d78c7b541a8dec9abe17869eda2b7038b5699fca Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 25 Jun 2024 10:50:29 +0100 Subject: [PATCH] Introduce docker-test plugin for running tests that require Docker See gh-41228 --- buildSrc/build.gradle | 4 + .../build/mavenplugin/MavenPluginPlugin.java | 9 +- .../build/test/DockerTestBuildService.java | 38 ++++++ .../boot/build/test/DockerTestPlugin.java | 112 ++++++++++++++++++ src/checkstyle/checkstyle-suppressions.xml | 2 + 5 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/test/DockerTestBuildService.java create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/test/DockerTestPlugin.java diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 9aaeb426df6..c3c02b1ac80 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -93,6 +93,10 @@ gradlePlugin { id = "org.springframework.boot.deployed" implementationClass = "org.springframework.boot.build.DeployedPlugin" } + dockerTestPlugin { + id = "org.springframework.boot.docker-test" + implementationClass = "org.springframework.boot.build.test.DockerTestPlugin" + } integrationTestPlugin { id = "org.springframework.boot.integration-test" implementationClass = "org.springframework.boot.build.test.IntegrationTestPlugin" diff --git a/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenPluginPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenPluginPlugin.java index 6301deb2b4b..15e4450c012 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenPluginPlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenPluginPlugin.java @@ -87,6 +87,7 @@ import org.xml.sax.SAXException; import org.springframework.boot.build.DeployedPlugin; import org.springframework.boot.build.MavenRepositoryPlugin; +import org.springframework.boot.build.test.DockerTestPlugin; import org.springframework.boot.build.test.IntegrationTestPlugin; import org.springframework.core.CollectionFactory; import org.springframework.util.Assert; @@ -138,11 +139,15 @@ public class MavenPluginPlugin implements Plugin { .set(new File(project.getBuildDir(), "runtime-classpath-repository")); project.getDependencies() .components((components) -> components.all(MavenRepositoryComponentMetadataRule.class)); - Sync task = project.getTasks().create("populateIntTestMavenRepository", Sync.class); - task.setDestinationDir(new File(project.getBuildDir(), "int-test-maven-repository")); + Sync task = project.getTasks().create("populateTestMavenRepository", Sync.class); + task.setDestinationDir(new File(project.getBuildDir(), "test-maven-repository")); task.with(copyIntTestMavenRepositoryFiles(project, runtimeClasspathMavenRepository)); task.dependsOn(project.getTasks().getByName(MavenRepositoryPlugin.PUBLISH_TO_PROJECT_REPOSITORY_TASK_NAME)); project.getTasks().getByName(IntegrationTestPlugin.INT_TEST_TASK_NAME).dependsOn(task); + project.getPlugins() + .withType(DockerTestPlugin.class) + .all((dockerTestPlugin) -> project.getTasks() + .named(DockerTestPlugin.DOCKER_TEST_TASK_NAME, (dockerTest) -> dockerTest.dependsOn(task))); } private CopySpec copyIntTestMavenRepositoryFiles(Project project, diff --git a/buildSrc/src/main/java/org/springframework/boot/build/test/DockerTestBuildService.java b/buildSrc/src/main/java/org/springframework/boot/build/test/DockerTestBuildService.java new file mode 100644 index 00000000000..2f70a735513 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/test/DockerTestBuildService.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.build.test; + +import org.gradle.api.Project; +import org.gradle.api.provider.Provider; +import org.gradle.api.services.BuildService; +import org.gradle.api.services.BuildServiceParameters; + +/** + * Build service for Docker-based tests. Configured to only allow serial execution, + * thereby ensuring that Docker-based tests do not run in parallel. + * + * @author Andy Wilkinson + */ +abstract class DockerTestBuildService implements BuildService { + + static Provider registerIfNecessary(Project project) { + return project.getGradle() + .getSharedServices() + .registerIfAbsent("dockerTest", DockerTestBuildService.class, (spec) -> spec.getMaxParallelUsages().set(1)); + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/test/DockerTestPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/test/DockerTestPlugin.java new file mode 100644 index 00000000000..017ca001a43 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/test/DockerTestPlugin.java @@ -0,0 +1,112 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.build.test; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.provider.Provider; +import org.gradle.api.services.BuildService; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.api.tasks.testing.Test; +import org.gradle.language.base.plugins.LifecycleBasePlugin; +import org.gradle.plugins.ide.eclipse.EclipsePlugin; +import org.gradle.plugins.ide.eclipse.model.EclipseModel; + +/** + * Plugin for Docker-based tests. Creates a {@link SourceSet source set}, {@link Test + * test} task, and {@link BuildService shared service} named {@code dockerTest}. The build + * service is configured to only allow serial usage and the {@code dockerTest} task is + * configured to use the build service. In a parallel build, this ensures that only a + * single {@code dockerTest} task can run at any given time. + * + * @author Andy Wilkinson + */ +public class DockerTestPlugin implements Plugin { + + /** + * Name of the {@code dockerTest} task. + */ + public static String DOCKER_TEST_TASK_NAME = "dockerTest"; + + /** + * Name of the {@code dockerTest} source set. + */ + public static String DOCKER_TEST_SOURCE_SET_NAME = "dockerTest"; + + /** + * Name of the {@code dockerTest} shared service. + */ + public static String DOCKER_TEST_SERVICE_NAME = "dockerTest"; + + @Override + public void apply(Project project) { + project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> configureDockerTesting(project)); + } + + private void configureDockerTesting(Project project) { + Provider buildService = DockerTestBuildService.registerIfNecessary(project); + SourceSet dockerTestSourceSet = createSourceSet(project); + Provider dockerTest = createTestTask(project, dockerTestSourceSet, buildService); + project.getTasks().getByName(LifecycleBasePlugin.CHECK_TASK_NAME).dependsOn(dockerTest); + project.getPlugins().withType(EclipsePlugin.class, (eclipsePlugin) -> { + EclipseModel eclipse = project.getExtensions().getByType(EclipseModel.class); + eclipse.classpath((classpath) -> classpath.getPlusConfigurations() + .add(project.getConfigurations() + .getByName(dockerTestSourceSet.getRuntimeClasspathConfigurationName()))); + }); + } + + private SourceSet createSourceSet(Project project) { + SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets(); + SourceSet dockerTestSourceSet = sourceSets.create(DOCKER_TEST_SOURCE_SET_NAME); + SourceSet main = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME); + SourceSet test = sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME); + dockerTestSourceSet.setCompileClasspath(dockerTestSourceSet.getCompileClasspath() + .plus(main.getOutput()) + .plus(main.getCompileClasspath()) + .plus(test.getOutput())); + dockerTestSourceSet.setRuntimeClasspath(dockerTestSourceSet.getRuntimeClasspath() + .plus(main.getOutput()) + .plus(main.getRuntimeClasspath()) + .plus(test.getOutput())); + project.getPlugins().withType(IntegrationTestPlugin.class, (integrationTestPlugin) -> { + SourceSet intTest = sourceSets.getByName(IntegrationTestPlugin.INT_TEST_SOURCE_SET_NAME); + dockerTestSourceSet + .setCompileClasspath(dockerTestSourceSet.getCompileClasspath().plus(intTest.getOutput())); + dockerTestSourceSet + .setRuntimeClasspath(dockerTestSourceSet.getRuntimeClasspath().plus(intTest.getOutput())); + }); + return dockerTestSourceSet; + } + + private Provider createTestTask(Project project, SourceSet dockerTestSourceSet, + Provider buildService) { + Provider dockerTest = project.getTasks().register(DOCKER_TEST_TASK_NAME, Test.class, (task) -> { + task.usesService(buildService); + task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); + task.setDescription("Runs Docker-based tests."); + task.setTestClassesDirs(dockerTestSourceSet.getOutput().getClassesDirs()); + task.setClasspath(dockerTestSourceSet.getRuntimeClasspath()); + task.shouldRunAfter(JavaPlugin.TEST_TASK_NAME); + }); + return dockerTest; + } + +} diff --git a/src/checkstyle/checkstyle-suppressions.xml b/src/checkstyle/checkstyle-suppressions.xml index 5707daeb89d..cfc49fb4392 100644 --- a/src/checkstyle/checkstyle-suppressions.xml +++ b/src/checkstyle/checkstyle-suppressions.xml @@ -59,6 +59,8 @@ + +