Polish ArchitectureCheckTests
Prior to this commit, it wasn't possible to write a test to verify that a specific ArchRule applied only to a specific SourceSet. This PR rewrites `ArchitectureCheckTests` to support different sources and also adds functionality to configure the plugin. With these changes, all tests now verify not only the `checkArchitectureMain` task but also `checkArchitectureTest`. See gh-46822 Signed-off-by: Dmytro Nosan <dimanosan@gmail.com>
This commit is contained in:
parent
39dced55a4
commit
bb8e884999
|
|
@ -16,20 +16,32 @@
|
|||
|
||||
package org.springframework.boot.build.architecture;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Consumer;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.testkit.runner.BuildResult;
|
||||
import org.gradle.testkit.runner.GradleRunner;
|
||||
import org.gradle.testkit.runner.TaskOutcome;
|
||||
import org.gradle.testkit.runner.UnexpectedBuildFailure;
|
||||
import org.gradle.testkit.runner.UnexpectedBuildSuccess;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
|
@ -43,161 +55,184 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
*/
|
||||
class ArchitectureCheckTests {
|
||||
|
||||
private Path projectDir;
|
||||
private static final String SPRING_CONTEXT = "org.springframework:spring-context:6.2.9";
|
||||
|
||||
private Path buildFile;
|
||||
private static final String SPRING_INTEGRATION_JMX = "org.springframework.integration:spring-integration-jmx:6.5.1";
|
||||
|
||||
private GradleBuild gradleBuild;
|
||||
|
||||
@BeforeEach
|
||||
void setup(@TempDir Path projectDir) {
|
||||
this.projectDir = projectDir;
|
||||
this.buildFile = projectDir.resolve("build.gradle");
|
||||
this.gradleBuild = new GradleBuild(projectDir);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenPackagesAreTangledTaskFailsAndWritesAReport() throws IOException {
|
||||
runGradleWithCompiledClasses("tangled",
|
||||
shouldHaveFailureReportWithMessages("slices matching '(**)' should be free of cycles"));
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@EnumSource(Task.class)
|
||||
void whenPackagesAreTangledShouldFailAndWriteReport(Task task) throws IOException {
|
||||
prepareTask(task, "tangled");
|
||||
buildAndFail(this.gradleBuild, task, "slices matching '(**)' should be free of cycles");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenPackagesAreNotTangledTaskSucceedsAndWritesAnEmptyReport() throws IOException {
|
||||
runGradleWithCompiledClasses("untangled", shouldHaveEmptyFailureReport());
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@EnumSource(Task.class)
|
||||
void whenPackagesAreNotTangledShouldSucceedAndWriteEmptyReport(Task task) throws IOException {
|
||||
prepareTask(task, "untangled");
|
||||
build(this.gradleBuild, task);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenBeanPostProcessorBeanMethodIsNotStaticTaskFailsAndWritesAReport() throws IOException {
|
||||
runGradleWithCompiledClasses("bpp/nonstatic",
|
||||
shouldHaveFailureReportWithMessages(
|
||||
"methods that are annotated with @Bean and have raw return type assignable "
|
||||
+ "to org.springframework.beans.factory.config.BeanPostProcessor"));
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@EnumSource(Task.class)
|
||||
void whenBeanPostProcessorBeanMethodIsNotStaticShouldFailAndWriteReport(Task task) throws IOException {
|
||||
prepareTask(task, "bpp/nonstatic");
|
||||
buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT), task,
|
||||
"methods that are annotated with @Bean and have raw return type assignable"
|
||||
+ " to org.springframework.beans.factory.config.BeanPostProcessor");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenBeanPostProcessorBeanMethodIsStaticAndHasUnsafeParametersTaskFailsAndWritesAReport() throws IOException {
|
||||
runGradleWithCompiledClasses("bpp/unsafeparameters",
|
||||
shouldHaveFailureReportWithMessages(
|
||||
"methods that are annotated with @Bean and have raw return type assignable "
|
||||
+ "to org.springframework.beans.factory.config.BeanPostProcessor"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenBeanPostProcessorBeanMethodIsStaticAndHasSafeParametersTaskSucceedsAndWritesAnEmptyReport()
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@EnumSource(Task.class)
|
||||
void whenBeanPostProcessorBeanMethodIsStaticAndHasUnsafeParametersShouldFailAndWriteReport(Task task)
|
||||
throws IOException {
|
||||
runGradleWithCompiledClasses("bpp/safeparameters", shouldHaveEmptyFailureReport());
|
||||
prepareTask(task, "bpp/unsafeparameters");
|
||||
buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT), task,
|
||||
"methods that are annotated with @Bean and have raw return type assignable"
|
||||
+ " to org.springframework.beans.factory.config.BeanPostProcessor");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenBeanPostProcessorBeanMethodIsStaticAndHasNoParametersTaskSucceedsAndWritesAnEmptyReport()
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@EnumSource(Task.class)
|
||||
void whenBeanPostProcessorBeanMethodIsStaticAndHasSafeParametersShouldSucceedAndWriteEmptyReport(Task task)
|
||||
throws IOException {
|
||||
runGradleWithCompiledClasses("bpp/noparameters", shouldHaveEmptyFailureReport());
|
||||
prepareTask(task, "bpp/safeparameters");
|
||||
build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenBeanFactoryPostProcessorBeanMethodIsNotStaticTaskFailsAndWritesAReport() throws IOException {
|
||||
runGradleWithCompiledClasses("bfpp/nonstatic",
|
||||
shouldHaveFailureReportWithMessages("methods that are annotated with @Bean and have raw return "
|
||||
+ "type assignable to org.springframework.beans.factory.config.BeanFactoryPostProcessor"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenBeanFactoryPostProcessorBeanMethodIsStaticAndHasParametersTaskFailsAndWritesAReport() throws IOException {
|
||||
runGradleWithCompiledClasses("bfpp/parameters",
|
||||
shouldHaveFailureReportWithMessages("methods that are annotated with @Bean and have raw return "
|
||||
+ "type assignable to org.springframework.beans.factory.config.BeanFactoryPostProcessor"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenBeanFactoryPostProcessorBeanMethodIsStaticAndHasNoParametersTaskSucceedsAndWritesAnEmptyReport()
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@EnumSource(Task.class)
|
||||
void whenBeanPostProcessorBeanMethodIsStaticAndHasNoParametersShouldSucceedAndWriteEmptyReport(Task task)
|
||||
throws IOException {
|
||||
runGradleWithCompiledClasses("bfpp/noparameters", shouldHaveEmptyFailureReport());
|
||||
prepareTask(task, "bpp/noparameters");
|
||||
build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenClassLoadsResourceUsingResourceUtilsTaskFailsAndWritesReport() throws IOException {
|
||||
runGradleWithCompiledClasses("resources/loads", shouldHaveFailureReportWithMessages(
|
||||
"no classes should call method where target owner type org.springframework.util.ResourceUtils and target name 'getURL'"));
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@EnumSource(Task.class)
|
||||
void whenBeanFactoryPostProcessorBeanMethodIsNotStaticShouldFailAndWriteReport(Task task) throws IOException {
|
||||
prepareTask(task, "bfpp/nonstatic");
|
||||
buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT), task,
|
||||
"methods that are annotated with @Bean and have raw return type assignable"
|
||||
+ " to org.springframework.beans.factory.config.BeanFactoryPostProcessor");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenClassUsesResourceUtilsWithoutLoadingResourcesTaskSucceedsAndWritesAnEmptyReport() throws IOException {
|
||||
runGradleWithCompiledClasses("resources/noloads", shouldHaveEmptyFailureReport());
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@EnumSource(Task.class)
|
||||
void whenBeanFactoryPostProcessorBeanMethodIsStaticAndHasParametersShouldFailAndWriteReport(Task task)
|
||||
throws IOException {
|
||||
prepareTask(task, "bfpp/parameters");
|
||||
buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT), task,
|
||||
"methods that are annotated with @Bean and have raw return type assignable"
|
||||
+ " to org.springframework.beans.factory.config.BeanFactoryPostProcessor");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenClassDoesNotCallObjectsRequireNonNullTaskSucceedsAndWritesAnEmptyReport() throws IOException {
|
||||
runGradleWithCompiledClasses("objects/noRequireNonNull", shouldHaveEmptyFailureReport());
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@EnumSource(Task.class)
|
||||
void whenBeanFactoryPostProcessorBeanMethodIsStaticAndHasNoParametersShouldSucceedAndWriteEmptyReport(Task task)
|
||||
throws IOException {
|
||||
prepareTask(task, "bfpp/noparameters");
|
||||
build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenClassCallsObjectsRequireNonNullWithMessageTaskFailsAndWritesReport() throws IOException {
|
||||
runGradleWithCompiledClasses("objects/requireNonNullWithString", shouldHaveFailureReportWithMessages(
|
||||
"no classes should call method Objects.requireNonNull(Object, String)"));
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@EnumSource(Task.class)
|
||||
void whenClassLoadsResourceUsingResourceUtilsShouldFailAndWriteReport(Task task) throws IOException {
|
||||
prepareTask(task, "resources/loads");
|
||||
buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT), task,
|
||||
"no classes should call method where target owner type"
|
||||
+ " org.springframework.util.ResourceUtils and target name 'getURL'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenClassCallsObjectsRequireNonNullWithSupplierTaskFailsAndWritesReport() throws IOException {
|
||||
runGradleWithCompiledClasses("objects/requireNonNullWithSupplier", shouldHaveFailureReportWithMessages(
|
||||
"no classes should call method Objects.requireNonNull(Object, Supplier)"));
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@EnumSource(Task.class)
|
||||
void whenClassUsesResourceUtilsWithoutLoadingResourcesShouldSucceedAndWriteEmptyReport(Task task)
|
||||
throws IOException {
|
||||
prepareTask(task, "resources/noloads");
|
||||
build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenClassCallsStringToUpperCaseWithoutLocaleFailsAndWritesReport() throws IOException {
|
||||
runGradleWithCompiledClasses("string/toUpperCase",
|
||||
shouldHaveFailureReportWithMessages("because String.toUpperCase(Locale.ROOT) should be used instead"));
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@EnumSource(Task.class)
|
||||
void whenClassDoesNotCallObjectsRequireNonNullShouldSucceedAndWriteEmptyReport(Task task) throws IOException {
|
||||
prepareTask(task, "objects/noRequireNonNull");
|
||||
build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenClassCallsStringToLowerCaseWithoutLocaleFailsAndWritesReport() throws IOException {
|
||||
runGradleWithCompiledClasses("string/toLowerCase",
|
||||
shouldHaveFailureReportWithMessages("because String.toLowerCase(Locale.ROOT) should be used instead"));
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@EnumSource(Task.class)
|
||||
void whenClassCallsObjectsRequireNonNullWithMessageShouldFailAndWriteReport(Task task) throws IOException {
|
||||
prepareTask(task, "objects/requireNonNullWithString");
|
||||
buildAndFail(this.gradleBuild, task, "no classes should call method Objects.requireNonNull(Object, String)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenClassCallsStringToLowerCaseWithLocaleShouldNotFail() throws IOException {
|
||||
runGradleWithCompiledClasses("string/toLowerCaseWithLocale", shouldHaveEmptyFailureReport());
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@EnumSource(Task.class)
|
||||
void whenClassCallsObjectsRequireNonNullWithMessageAndProhibitObjectsRequireNonNullIsFalseShouldSucceedAndWriteEmptyReport(
|
||||
Task task) throws IOException {
|
||||
prepareTask(task, "objects/requireNonNullWithString");
|
||||
build(this.gradleBuild.withProhibitObjectsRequireNonNull(task, false), task);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenClassCallsStringToUpperCaseWithLocaleShouldNotFail() throws IOException {
|
||||
runGradleWithCompiledClasses("string/toUpperCaseWithLocale", shouldHaveEmptyFailureReport());
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@EnumSource(Task.class)
|
||||
void whenClassCallsObjectsRequireNonNullWithSupplierShouldFailAndWriteReport(Task task) throws IOException {
|
||||
prepareTask(task, "objects/requireNonNullWithSupplier");
|
||||
buildAndFail(this.gradleBuild, task, "no classes should call method Objects.requireNonNull(Object, Supplier)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenBeanMethodExposePrivateTypeShouldFailAndWriteReport() throws IOException {
|
||||
runGradleWithCompiledClasses("beans/privatebean", shouldHaveFailureReportWithMessages(
|
||||
"methods that are annotated with @Bean should not return types declared with the PRIVATE modifier,"
|
||||
+ " as such types are incompatible with Spring AOT processing",
|
||||
"Method <org.springframework.boot.build.architecture.beans.privatebean.PrivateBean.myBean()> "
|
||||
+ "returns Class <org.springframework.boot.build.architecture.beans.privatebean.PrivateBean$MyBean>"
|
||||
+ " which is declared as [PRIVATE, STATIC, FINAL]"));
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@EnumSource(Task.class)
|
||||
void whenClassCallsObjectsRequireNonNullWithSupplierAndProhibitObjectsRequireNonNullIsFalseShouldSucceedAndWriteEmptyReport(
|
||||
Task task) throws IOException {
|
||||
prepareTask(task, "objects/requireNonNullWithSupplier");
|
||||
build(this.gradleBuild.withProhibitObjectsRequireNonNull(task, false), task);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenBeanMethodExposeNonPrivateTypeShouldNotFail() throws IOException {
|
||||
runGradleWithCompiledClasses("beans/regular", shouldHaveEmptyFailureReport());
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@EnumSource(Task.class)
|
||||
void whenClassCallsStringToUpperCaseWithoutLocaleShouldFailAndWriteReport(Task task) throws IOException {
|
||||
prepareTask(task, "string/toUpperCase");
|
||||
buildAndFail(this.gradleBuild, task, "because String.toUpperCase(Locale.ROOT) should be used instead");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenBeanPostProcessorBeanMethodIsNotStaticWithExternalClass() throws IOException {
|
||||
Files.writeString(this.buildFile, """
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot.architecture'
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
java {
|
||||
sourceCompatibility = 17
|
||||
}
|
||||
dependencies {
|
||||
implementation("org.springframework.integration:spring-integration-jmx:6.3.9")
|
||||
}
|
||||
""");
|
||||
Path testClass = this.projectDir.resolve("src/main/java/boot/architecture/bpp/external/TestClass.java");
|
||||
Files.createDirectories(testClass.getParent());
|
||||
Files.writeString(testClass, """
|
||||
package org.springframework.boot.build.architecture.bpp.external;
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@EnumSource(Task.class)
|
||||
void whenClassCallsStringToLowerCaseWithoutLocaleShouldFailAndWriteReport(Task task) throws IOException {
|
||||
prepareTask(task, "string/toLowerCase");
|
||||
buildAndFail(this.gradleBuild, task, "because String.toLowerCase(Locale.ROOT) should be used instead");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@EnumSource(Task.class)
|
||||
void whenClassCallsStringToLowerCaseWithLocaleShouldSucceedAndWriteEmptyReport(Task task) throws IOException {
|
||||
prepareTask(task, "string/toLowerCaseWithLocale");
|
||||
build(this.gradleBuild, task);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@EnumSource(Task.class)
|
||||
void whenClassCallsStringToUpperCaseWithLocaleShouldSucceedAndWriteEmptyReport(Task task) throws IOException {
|
||||
prepareTask(task, "string/toUpperCaseWithLocale");
|
||||
build(this.gradleBuild, task);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@EnumSource(Task.class)
|
||||
void whenBeanPostProcessorBeanMethodIsNotStaticWithExternalClassShouldFailAndWriteReport(Task task)
|
||||
throws IOException {
|
||||
Path sourceDirectory = task.getSourceDirectory(this.gradleBuild.getProjectDir())
|
||||
.resolve(ClassUtils.classPackageAsResourcePath(getClass()));
|
||||
Files.createDirectories(sourceDirectory);
|
||||
Files.writeString(sourceDirectory.resolve("TestClass.java"), """
|
||||
package %s;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.integration.monitor.IntegrationMBeanExporter;
|
||||
public class TestClass {
|
||||
|
|
@ -206,68 +241,171 @@ class ArchitectureCheckTests {
|
|||
return new IntegrationMBeanExporter();
|
||||
}
|
||||
}
|
||||
""");
|
||||
runGradle(shouldHaveFailureReportWithMessages("methods that are annotated with @Bean and have raw return "
|
||||
+ "type assignable to org.springframework.beans.factory.config.BeanPostProcessor "));
|
||||
""".formatted(ClassUtils.getPackageName(getClass())));
|
||||
buildAndFail(this.gradleBuild.withDependencies(SPRING_INTEGRATION_JMX), task,
|
||||
"methods that are annotated with @Bean and have raw return type assignable "
|
||||
+ "to org.springframework.beans.factory.config.BeanPostProcessor");
|
||||
}
|
||||
|
||||
private Consumer<GradleRunner> shouldHaveEmptyFailureReport() {
|
||||
return (gradleRunner) -> {
|
||||
try {
|
||||
assertThat(gradleRunner.build().getOutput()).contains("BUILD SUCCESSFUL")
|
||||
.contains("Task :checkArchitectureMain");
|
||||
assertThat(failureReport()).isEmpty();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new AssertionError("Expected build to succeed but it failed\n" + failureReport(), ex);
|
||||
}
|
||||
};
|
||||
@Test
|
||||
void whenBeanMethodExposesPrivateTypeWithMainSourcesShouldFailAndWriteReport() throws IOException {
|
||||
prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "beans/privatebean");
|
||||
buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT), Task.CHECK_ARCHITECTURE_MAIN,
|
||||
"methods that are annotated with @Bean should not return types declared "
|
||||
+ "with the PRIVATE modifier, as such types are incompatible with Spring AOT processing",
|
||||
"returns Class <org.springframework.boot.build.architecture.beans.privatebean.PrivateBean$MyBean>"
|
||||
+ " which is declared as [PRIVATE, STATIC, FINAL]");
|
||||
}
|
||||
|
||||
private Consumer<GradleRunner> shouldHaveFailureReportWithMessages(String... messages) {
|
||||
return (gradleRunner) -> {
|
||||
assertThat(gradleRunner.buildAndFail().getOutput()).contains("BUILD FAILED")
|
||||
.contains("Task :checkArchitectureMain FAILED");
|
||||
assertThat(failureReport()).contains(messages);
|
||||
};
|
||||
@Test
|
||||
void whenBeanMethodExposesPrivateTypeWithTestsSourcesShouldSucceedAndWriteEmptyReport() throws IOException {
|
||||
prepareTask(Task.CHECK_ARCHITECTURE_TEST, "beans/privatebean");
|
||||
build(this.gradleBuild.withDependencies(SPRING_CONTEXT), Task.CHECK_ARCHITECTURE_TEST);
|
||||
}
|
||||
|
||||
private void runGradleWithCompiledClasses(String path, Consumer<GradleRunner> callback) throws IOException {
|
||||
ClassPathResource classPathResource = new ClassPathResource(path, getClass());
|
||||
FileSystemUtils.copyRecursively(classPathResource.getFile().toPath(),
|
||||
this.projectDir.resolve("classes").resolve(classPathResource.getPath()));
|
||||
Files.writeString(this.buildFile, """
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot.architecture'
|
||||
}
|
||||
sourceSets {
|
||||
main {
|
||||
output.classesDirs.setFrom(file("classes"))
|
||||
}
|
||||
}
|
||||
""");
|
||||
runGradle(callback);
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@EnumSource(Task.class)
|
||||
void whenBeanMethodExposesNonPrivateTypeShouldSucceedAndWriteEmptyReport(Task task) throws IOException {
|
||||
prepareTask(task, "beans/regular");
|
||||
build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task);
|
||||
}
|
||||
|
||||
private void runGradle(Consumer<GradleRunner> callback) {
|
||||
callback.accept(GradleRunner.create()
|
||||
.withProjectDir(this.projectDir.toFile())
|
||||
.withArguments("checkArchitectureMain")
|
||||
.withPluginClasspath());
|
||||
private void prepareTask(Task task, String... sourceDirectories) throws IOException {
|
||||
for (String sourceDirectory : sourceDirectories) {
|
||||
FileSystemUtils.copyRecursively(
|
||||
Paths.get("src/test/java")
|
||||
.resolve(ClassUtils.classPackageAsResourcePath(getClass()))
|
||||
.resolve(sourceDirectory),
|
||||
task.getSourceDirectory(this.gradleBuild.getProjectDir())
|
||||
.resolve(ClassUtils.classPackageAsResourcePath(getClass()))
|
||||
.resolve(sourceDirectory));
|
||||
}
|
||||
}
|
||||
|
||||
private String failureReport() {
|
||||
private void build(GradleBuild gradleBuild, Task task) throws IOException {
|
||||
try {
|
||||
Path failureReport = this.projectDir.resolve("build/checkArchitectureMain/failure-report.txt");
|
||||
return Files.readString(failureReport, StandardCharsets.UTF_8);
|
||||
BuildResult buildResult = gradleBuild.build(task.toString());
|
||||
assertThat(buildResult.taskPaths(TaskOutcome.SUCCESS)).contains(":" + task);
|
||||
assertThat(task.getFailureReport(gradleBuild.getProjectDir())).isEmpty();
|
||||
}
|
||||
catch (FileNotFoundException ex) {
|
||||
return "Failure report does not exist";
|
||||
catch (UnexpectedBuildFailure ex) {
|
||||
StringBuilder message = new StringBuilder("Expected build to succeed but it failed");
|
||||
if (Files.exists(task.getFailureReportFile(gradleBuild.getProjectDir()))) {
|
||||
message.append('\n').append(task.getFailureReport(gradleBuild.getProjectDir()));
|
||||
}
|
||||
message.append('\n').append(ex.getBuildResult().getOutput());
|
||||
throw new AssertionError(message.toString(), ex);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return "Failure report could not be read: " + ex.getMessage();
|
||||
}
|
||||
|
||||
private void buildAndFail(GradleBuild gradleBuild, Task task, String... messages) throws IOException {
|
||||
try {
|
||||
BuildResult buildResult = gradleBuild.buildAndFail(task.toString());
|
||||
assertThat(buildResult.taskPaths(TaskOutcome.FAILED)).contains(":" + task);
|
||||
assertThat(task.getFailureReport(gradleBuild.getProjectDir())).contains(messages);
|
||||
}
|
||||
catch (UnexpectedBuildSuccess ex) {
|
||||
throw new AssertionError("Expected build to fail but it succeeded\n" + ex.getBuildResult().getOutput(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private enum Task {
|
||||
|
||||
CHECK_ARCHITECTURE_MAIN(SourceSet.MAIN_SOURCE_SET_NAME),
|
||||
|
||||
CHECK_ARCHITECTURE_TEST(SourceSet.TEST_SOURCE_SET_NAME);
|
||||
|
||||
private final String sourceSetName;
|
||||
|
||||
Task(String sourceSetName) {
|
||||
this.sourceSetName = sourceSetName;
|
||||
}
|
||||
|
||||
String getFailureReport(Path projectDir) throws IOException {
|
||||
return Files.readString(getFailureReportFile(projectDir), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
Path getFailureReportFile(Path projectDir) {
|
||||
return projectDir.resolve("build/%s/failure-report.txt".formatted(toString()));
|
||||
}
|
||||
|
||||
Path getSourceDirectory(Path projectDir) {
|
||||
return projectDir.resolve("src/%s/java".formatted(this.sourceSetName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "checkArchitecture" + StringUtils.capitalize(this.sourceSetName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class GradleBuild {
|
||||
|
||||
private final Path projectDir;
|
||||
|
||||
private final Set<String> dependencies = new LinkedHashSet<>();
|
||||
|
||||
private final Map<Task, Boolean> prohibitObjectsRequireNonNull = new LinkedHashMap<>();
|
||||
|
||||
private GradleBuild(Path projectDir) {
|
||||
this.projectDir = projectDir;
|
||||
}
|
||||
|
||||
Path getProjectDir() {
|
||||
return this.projectDir;
|
||||
}
|
||||
|
||||
GradleBuild withProhibitObjectsRequireNonNull(Task task, boolean prohibitObjectsRequireNonNull) {
|
||||
this.prohibitObjectsRequireNonNull.put(task, prohibitObjectsRequireNonNull);
|
||||
return this;
|
||||
}
|
||||
|
||||
GradleBuild withDependencies(String... dependencies) {
|
||||
this.dependencies.addAll(Arrays.asList(dependencies));
|
||||
return this;
|
||||
}
|
||||
|
||||
BuildResult build(String... arguments) throws IOException {
|
||||
return prepareRunner(arguments).build();
|
||||
}
|
||||
|
||||
BuildResult buildAndFail(String... arguments) throws IOException {
|
||||
return prepareRunner(arguments).buildAndFail();
|
||||
}
|
||||
|
||||
private GradleRunner prepareRunner(String... arguments) throws IOException {
|
||||
StringBuilder buildFile = new StringBuilder();
|
||||
buildFile.append("plugins {\n")
|
||||
.append(" id 'java'\n")
|
||||
.append(" id 'org.springframework.boot.architecture'\n")
|
||||
.append("}\n\n")
|
||||
.append("repositories {\n")
|
||||
.append(" mavenCentral()\n")
|
||||
.append("}\n\n")
|
||||
.append("java {\n")
|
||||
.append(" sourceCompatibility = '17'\n")
|
||||
.append(" targetCompatibility = '17'\n")
|
||||
.append("}\n\n");
|
||||
if (!this.dependencies.isEmpty()) {
|
||||
buildFile.append("dependencies {\n");
|
||||
for (String dependency : this.dependencies) {
|
||||
buildFile.append(" implementation '%s'\n".formatted(dependency));
|
||||
}
|
||||
buildFile.append("}\n");
|
||||
}
|
||||
this.prohibitObjectsRequireNonNull.forEach((task, prohibitObjectsRequireNonNull) -> buildFile.append(task)
|
||||
.append(" {\n")
|
||||
.append(" prohibitObjectsRequireNonNull = ")
|
||||
.append(prohibitObjectsRequireNonNull)
|
||||
.append("\n}\n\n"));
|
||||
Files.writeString(this.projectDir.resolve("build.gradle"), buildFile, StandardCharsets.UTF_8);
|
||||
return GradleRunner.create()
|
||||
.withProjectDir(this.projectDir.toFile())
|
||||
.withArguments(arguments)
|
||||
.withPluginClasspath();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue