|
|
|
@ -26,6 +26,7 @@ import java.util.LinkedHashMap; |
|
|
|
import java.util.LinkedHashSet; |
|
|
|
import java.util.LinkedHashSet; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.Set; |
|
|
|
import java.util.Set; |
|
|
|
|
|
|
|
import java.util.function.UnaryOperator; |
|
|
|
|
|
|
|
|
|
|
|
import org.gradle.api.tasks.SourceSet; |
|
|
|
import org.gradle.api.tasks.SourceSet; |
|
|
|
import org.gradle.testkit.runner.BuildResult; |
|
|
|
import org.gradle.testkit.runner.BuildResult; |
|
|
|
@ -39,6 +40,7 @@ import org.junit.jupiter.api.io.TempDir; |
|
|
|
import org.junit.jupiter.params.ParameterizedTest; |
|
|
|
import org.junit.jupiter.params.ParameterizedTest; |
|
|
|
import org.junit.jupiter.params.provider.EnumSource; |
|
|
|
import org.junit.jupiter.params.provider.EnumSource; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import org.springframework.boot.build.architecture.annotations.TestConditionalOnClass; |
|
|
|
import org.springframework.util.ClassUtils; |
|
|
|
import org.springframework.util.ClassUtils; |
|
|
|
import org.springframework.util.FileSystemUtils; |
|
|
|
import org.springframework.util.FileSystemUtils; |
|
|
|
import org.springframework.util.StringUtils; |
|
|
|
import org.springframework.util.StringUtils; |
|
|
|
@ -180,7 +182,7 @@ class ArchitectureCheckTests { |
|
|
|
void whenClassCallsObjectsRequireNonNullWithMessageAndProhibitObjectsRequireNonNullIsFalseShouldSucceedAndWriteEmptyReport( |
|
|
|
void whenClassCallsObjectsRequireNonNullWithMessageAndProhibitObjectsRequireNonNullIsFalseShouldSucceedAndWriteEmptyReport( |
|
|
|
Task task) throws IOException { |
|
|
|
Task task) throws IOException { |
|
|
|
prepareTask(task, "objects/requireNonNullWithString"); |
|
|
|
prepareTask(task, "objects/requireNonNullWithString"); |
|
|
|
build(this.gradleBuild.withProhibitObjectsRequireNonNull(task, false), task); |
|
|
|
build(this.gradleBuild.withProhibitObjectsRequireNonNull(false), task); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ParameterizedTest(name = "{0}") |
|
|
|
@ParameterizedTest(name = "{0}") |
|
|
|
@ -195,7 +197,7 @@ class ArchitectureCheckTests { |
|
|
|
void whenClassCallsObjectsRequireNonNullWithSupplierAndProhibitObjectsRequireNonNullIsFalseShouldSucceedAndWriteEmptyReport( |
|
|
|
void whenClassCallsObjectsRequireNonNullWithSupplierAndProhibitObjectsRequireNonNullIsFalseShouldSucceedAndWriteEmptyReport( |
|
|
|
Task task) throws IOException { |
|
|
|
Task task) throws IOException { |
|
|
|
prepareTask(task, "objects/requireNonNullWithSupplier"); |
|
|
|
prepareTask(task, "objects/requireNonNullWithSupplier"); |
|
|
|
build(this.gradleBuild.withProhibitObjectsRequireNonNull(task, false), task); |
|
|
|
build(this.gradleBuild.withProhibitObjectsRequireNonNull(false), task); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ParameterizedTest(name = "{0}") |
|
|
|
@ParameterizedTest(name = "{0}") |
|
|
|
@ -295,6 +297,25 @@ class ArchitectureCheckTests { |
|
|
|
"should not have a value that is the same as the type of the method's first parameter"); |
|
|
|
"should not have a value that is the same as the type of the method's first parameter"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
|
|
void whenConditionalOnClassUsedOnBeanMethodsWithMainSourcesShouldFailAndWriteReport() throws IOException { |
|
|
|
|
|
|
|
prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "conditionalonclass", "annotations"); |
|
|
|
|
|
|
|
GradleBuild gradleBuild = this.gradleBuild.withDependencies(SPRING_CONTEXT) |
|
|
|
|
|
|
|
.withConditionalOnClassAnnotation(TestConditionalOnClass.class.getName()); |
|
|
|
|
|
|
|
buildAndFail(gradleBuild, Task.CHECK_ARCHITECTURE_MAIN, |
|
|
|
|
|
|
|
"because @ConditionalOnClass on @Bean methods is ineffective - it doesn't prevent" |
|
|
|
|
|
|
|
+ " the method signature from being loaded. Such condition need to be placed" |
|
|
|
|
|
|
|
+ " on a @Configuration class, allowing the condition to back off before the type is loaded"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
|
|
void whenConditionalOnClassUsedOnBeanMethodsWithTestSourcesShouldSucceedAndWriteEmptyReport() throws IOException { |
|
|
|
|
|
|
|
prepareTask(Task.CHECK_ARCHITECTURE_TEST, "conditionalonclass", "annotations"); |
|
|
|
|
|
|
|
GradleBuild gradleBuild = this.gradleBuild.withDependencies(SPRING_CONTEXT) |
|
|
|
|
|
|
|
.withConditionalOnClassAnnotation(TestConditionalOnClass.class.getName()); |
|
|
|
|
|
|
|
build(gradleBuild, Task.CHECK_ARCHITECTURE_TEST); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void prepareTask(Task task, String... sourceDirectories) throws IOException { |
|
|
|
private void prepareTask(Task task, String... sourceDirectories) throws IOException { |
|
|
|
for (String sourceDirectory : sourceDirectories) { |
|
|
|
for (String sourceDirectory : sourceDirectories) { |
|
|
|
FileSystemUtils.copyRecursively( |
|
|
|
FileSystemUtils.copyRecursively( |
|
|
|
@ -310,7 +331,7 @@ class ArchitectureCheckTests { |
|
|
|
private void build(GradleBuild gradleBuild, Task task) throws IOException { |
|
|
|
private void build(GradleBuild gradleBuild, Task task) throws IOException { |
|
|
|
try { |
|
|
|
try { |
|
|
|
BuildResult buildResult = gradleBuild.build(task.toString()); |
|
|
|
BuildResult buildResult = gradleBuild.build(task.toString()); |
|
|
|
assertThat(buildResult.taskPaths(TaskOutcome.SUCCESS)).contains(":" + task); |
|
|
|
assertThat(buildResult.taskPaths(TaskOutcome.SUCCESS)).as(buildResult.getOutput()).contains(":" + task); |
|
|
|
assertThat(task.getFailureReport(gradleBuild.getProjectDir())).isEmpty(); |
|
|
|
assertThat(task.getFailureReport(gradleBuild.getProjectDir())).isEmpty(); |
|
|
|
} |
|
|
|
} |
|
|
|
catch (UnexpectedBuildFailure ex) { |
|
|
|
catch (UnexpectedBuildFailure ex) { |
|
|
|
@ -326,7 +347,7 @@ class ArchitectureCheckTests { |
|
|
|
private void buildAndFail(GradleBuild gradleBuild, Task task, String... messages) throws IOException { |
|
|
|
private void buildAndFail(GradleBuild gradleBuild, Task task, String... messages) throws IOException { |
|
|
|
try { |
|
|
|
try { |
|
|
|
BuildResult buildResult = gradleBuild.buildAndFail(task.toString()); |
|
|
|
BuildResult buildResult = gradleBuild.buildAndFail(task.toString()); |
|
|
|
assertThat(buildResult.taskPaths(TaskOutcome.FAILED)).contains(":" + task); |
|
|
|
assertThat(buildResult.taskPaths(TaskOutcome.FAILED)).as(buildResult.getOutput()).contains(":" + task); |
|
|
|
assertThat(task.getFailureReport(gradleBuild.getProjectDir())).contains(messages); |
|
|
|
assertThat(task.getFailureReport(gradleBuild.getProjectDir())).contains(messages); |
|
|
|
} |
|
|
|
} |
|
|
|
catch (UnexpectedBuildSuccess ex) { |
|
|
|
catch (UnexpectedBuildSuccess ex) { |
|
|
|
@ -371,7 +392,7 @@ class ArchitectureCheckTests { |
|
|
|
|
|
|
|
|
|
|
|
private final Set<String> dependencies = new LinkedHashSet<>(); |
|
|
|
private final Set<String> dependencies = new LinkedHashSet<>(); |
|
|
|
|
|
|
|
|
|
|
|
private final Map<Task, Boolean> prohibitObjectsRequireNonNull = new LinkedHashMap<>(); |
|
|
|
private final Map<Task, TaskConfiguration> taskConfigurations = new LinkedHashMap<>(); |
|
|
|
|
|
|
|
|
|
|
|
private GradleBuild(Path projectDir) { |
|
|
|
private GradleBuild(Path projectDir) { |
|
|
|
this.projectDir = projectDir; |
|
|
|
this.projectDir = projectDir; |
|
|
|
@ -381,12 +402,28 @@ class ArchitectureCheckTests { |
|
|
|
return this.projectDir; |
|
|
|
return this.projectDir; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
GradleBuild withProhibitObjectsRequireNonNull(Task task, boolean prohibitObjectsRequireNonNull) { |
|
|
|
GradleBuild withProhibitObjectsRequireNonNull(Boolean prohibitObjectsRequireNonNull) { |
|
|
|
this.prohibitObjectsRequireNonNull.put(task, prohibitObjectsRequireNonNull); |
|
|
|
for (Task task : Task.values()) { |
|
|
|
|
|
|
|
configureTask(task, (configuration) -> configuration |
|
|
|
|
|
|
|
.withProhibitObjectsRequireNonNull(prohibitObjectsRequireNonNull)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return this; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
GradleBuild withConditionalOnClassAnnotation(String annotationName) { |
|
|
|
|
|
|
|
for (Task task : Task.values()) { |
|
|
|
|
|
|
|
configureTask(task, (configuration) -> configuration.withConditionalOnClassAnnotation(annotationName)); |
|
|
|
|
|
|
|
} |
|
|
|
return this; |
|
|
|
return this; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void configureTask(Task task, UnaryOperator<TaskConfiguration> configurer) { |
|
|
|
|
|
|
|
this.taskConfigurations.computeIfAbsent(task, (key) -> new TaskConfiguration(null, null)); |
|
|
|
|
|
|
|
this.taskConfigurations.compute(task, (key, value) -> configurer.apply(value)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
GradleBuild withDependencies(String... dependencies) { |
|
|
|
GradleBuild withDependencies(String... dependencies) { |
|
|
|
|
|
|
|
this.dependencies.clear(); |
|
|
|
this.dependencies.addAll(Arrays.asList(dependencies)); |
|
|
|
this.dependencies.addAll(Arrays.asList(dependencies)); |
|
|
|
return this; |
|
|
|
return this; |
|
|
|
} |
|
|
|
} |
|
|
|
@ -415,15 +452,22 @@ class ArchitectureCheckTests { |
|
|
|
if (!this.dependencies.isEmpty()) { |
|
|
|
if (!this.dependencies.isEmpty()) { |
|
|
|
buildFile.append("dependencies {\n"); |
|
|
|
buildFile.append("dependencies {\n"); |
|
|
|
for (String dependency : this.dependencies) { |
|
|
|
for (String dependency : this.dependencies) { |
|
|
|
buildFile.append(" implementation '%s'\n".formatted(dependency)); |
|
|
|
buildFile.append("\n implementation ").append(StringUtils.quote(dependency)); |
|
|
|
} |
|
|
|
} |
|
|
|
buildFile.append("}\n"); |
|
|
|
buildFile.append("}\n"); |
|
|
|
} |
|
|
|
} |
|
|
|
this.prohibitObjectsRequireNonNull.forEach((task, prohibitObjectsRequireNonNull) -> buildFile.append(task) |
|
|
|
this.taskConfigurations.forEach((task, configuration) -> { |
|
|
|
.append(" {\n") |
|
|
|
buildFile.append(task).append(" {"); |
|
|
|
.append(" prohibitObjectsRequireNonNull = ") |
|
|
|
if (configuration.conditionalOnClassAnnotation() != null) { |
|
|
|
.append(prohibitObjectsRequireNonNull) |
|
|
|
buildFile.append("\n conditionalOnClassAnnotation = ") |
|
|
|
.append("\n}\n\n")); |
|
|
|
.append(StringUtils.quote(configuration.conditionalOnClassAnnotation())); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (configuration.prohibitObjectsRequireNonNull() != null) { |
|
|
|
|
|
|
|
buildFile.append("\n prohibitObjectsRequireNonNull = ") |
|
|
|
|
|
|
|
.append(configuration.prohibitObjectsRequireNonNull()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
buildFile.append("\n}\n"); |
|
|
|
|
|
|
|
}); |
|
|
|
Files.writeString(this.projectDir.resolve("build.gradle"), buildFile, StandardCharsets.UTF_8); |
|
|
|
Files.writeString(this.projectDir.resolve("build.gradle"), buildFile, StandardCharsets.UTF_8); |
|
|
|
return GradleRunner.create() |
|
|
|
return GradleRunner.create() |
|
|
|
.withProjectDir(this.projectDir.toFile()) |
|
|
|
.withProjectDir(this.projectDir.toFile()) |
|
|
|
@ -431,6 +475,17 @@ class ArchitectureCheckTests { |
|
|
|
.withPluginClasspath(); |
|
|
|
.withPluginClasspath(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private record TaskConfiguration(Boolean prohibitObjectsRequireNonNull, String conditionalOnClassAnnotation) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private TaskConfiguration withConditionalOnClassAnnotation(String annotationName) { |
|
|
|
|
|
|
|
return new TaskConfiguration(this.prohibitObjectsRequireNonNull, annotationName); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private TaskConfiguration withProhibitObjectsRequireNonNull(Boolean prohibitObjectsRequireNonNull) { |
|
|
|
|
|
|
|
return new TaskConfiguration(prohibitObjectsRequireNonNull, this.conditionalOnClassAnnotation); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|