Introduce docker-test plugin for running tests that require Docker

See gh-41228
This commit is contained in:
Andy Wilkinson 2024-06-25 10:50:29 +01:00
parent 07442f8366
commit d78c7b541a
5 changed files with 163 additions and 2 deletions

View File

@ -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"

View File

@ -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<Project> {
.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,

View File

@ -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<BuildServiceParameters.None> {
static Provider<DockerTestBuildService> registerIfNecessary(Project project) {
return project.getGradle()
.getSharedServices()
.registerIfAbsent("dockerTest", DockerTestBuildService.class, (spec) -> spec.getMaxParallelUsages().set(1));
}
}

View File

@ -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<Project> {
/**
* 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<DockerTestBuildService> buildService = DockerTestBuildService.registerIfNecessary(project);
SourceSet dockerTestSourceSet = createSourceSet(project);
Provider<Test> 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<Test> createTestTask(Project project, SourceSet dockerTestSourceSet,
Provider<DockerTestBuildService> buildService) {
Provider<Test> 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;
}
}

View File

@ -59,6 +59,8 @@
<suppress files="[\\/]spring-boot-smoke-tests[\\/]spring-boot-smoke-test-testng[\\/]" checks="SpringJUnit5" />
<suppress files="[\\/]spring-boot-test-support[\\/]" checks="SpringJavadoc" message="\@since" />
<suppress files="[\\/]spring-boot-gradle-test-support[\\/]" checks="SpringJavadoc" message="\@since" />
<suppress files="[\\/]src[\\/]dockerTest[\\/]java[\\/]" checks="JavadocPackage" />
<suppress files="[\\/]src[\\/]dockerTest[\\/]java[\\/]" checks="SpringJavadoc" message="\@since" />
<suppress files="[\\/]src[\\/]intTest[\\/]java[\\/]" checks="JavadocPackage" />
<suppress files="[\\/]src[\\/]intTest[\\/]java[\\/]" checks="SpringJavadoc" message="\@since" />
<suppress files="[\\/]src[\\/]systemTest[\\/]java[\\/]" checks="JavadocPackage" />