Check that BPP and BFPP bean methods won't cause eager initialization
Closes gh-35164pull/35401/head
parent
472afafd4b
commit
6b9bc012a5
@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022-2023 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.architecture;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import com.tngtech.archunit.base.DescribedPredicate;
|
||||||
|
import com.tngtech.archunit.core.domain.JavaClass;
|
||||||
|
import com.tngtech.archunit.core.domain.JavaClass.Predicates;
|
||||||
|
import com.tngtech.archunit.core.domain.JavaClasses;
|
||||||
|
import com.tngtech.archunit.core.domain.JavaMethod;
|
||||||
|
import com.tngtech.archunit.core.domain.JavaParameter;
|
||||||
|
import com.tngtech.archunit.core.domain.properties.CanBeAnnotated;
|
||||||
|
import com.tngtech.archunit.core.importer.ClassFileImporter;
|
||||||
|
import com.tngtech.archunit.lang.ArchCondition;
|
||||||
|
import com.tngtech.archunit.lang.ArchRule;
|
||||||
|
import com.tngtech.archunit.lang.ConditionEvents;
|
||||||
|
import com.tngtech.archunit.lang.EvaluationResult;
|
||||||
|
import com.tngtech.archunit.lang.SimpleConditionEvent;
|
||||||
|
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
|
||||||
|
import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition;
|
||||||
|
import org.gradle.api.DefaultTask;
|
||||||
|
import org.gradle.api.GradleException;
|
||||||
|
import org.gradle.api.Task;
|
||||||
|
import org.gradle.api.file.DirectoryProperty;
|
||||||
|
import org.gradle.api.file.FileCollection;
|
||||||
|
import org.gradle.api.file.FileTree;
|
||||||
|
import org.gradle.api.tasks.IgnoreEmptyDirectories;
|
||||||
|
import org.gradle.api.tasks.InputFiles;
|
||||||
|
import org.gradle.api.tasks.Internal;
|
||||||
|
import org.gradle.api.tasks.OutputDirectory;
|
||||||
|
import org.gradle.api.tasks.PathSensitive;
|
||||||
|
import org.gradle.api.tasks.PathSensitivity;
|
||||||
|
import org.gradle.api.tasks.SkipWhenEmpty;
|
||||||
|
import org.gradle.api.tasks.TaskAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Task} that checks for architecture problems.
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
*/
|
||||||
|
public abstract class ArchitectureCheck extends DefaultTask {
|
||||||
|
|
||||||
|
private FileCollection classes;
|
||||||
|
|
||||||
|
public ArchitectureCheck() {
|
||||||
|
getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@TaskAction
|
||||||
|
void checkArchitecture() throws IOException {
|
||||||
|
JavaClasses javaClasses = new ClassFileImporter()
|
||||||
|
.importPaths(this.classes.getFiles().stream().map(File::toPath).collect(Collectors.toList()));
|
||||||
|
List<EvaluationResult> violations = Stream.of(allPackagesShouldBeFreeOfTangles(),
|
||||||
|
allBeanPostProcessorBeanMethodsShouldBeStaticAndHaveParametersThatWillNotCausePrematureInitialization(),
|
||||||
|
allBeanFactoryPostProcessorBeanMethodsShouldBeStaticAndHaveNoParameters())
|
||||||
|
.map((rule) -> rule.evaluate(javaClasses))
|
||||||
|
.filter(EvaluationResult::hasViolation)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile();
|
||||||
|
outputFile.getParentFile().mkdirs();
|
||||||
|
if (!violations.isEmpty()) {
|
||||||
|
StringBuilder report = new StringBuilder();
|
||||||
|
for (EvaluationResult violation : violations) {
|
||||||
|
report.append(violation.getFailureReport().toString());
|
||||||
|
report.append(String.format("%n"));
|
||||||
|
}
|
||||||
|
Files.write(outputFile.toPath(), report.toString().getBytes(StandardCharsets.UTF_8),
|
||||||
|
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||||
|
throw new GradleException("Architecture check failed. See '" + outputFile + "' for details.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
outputFile.createNewFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArchRule allPackagesShouldBeFreeOfTangles() {
|
||||||
|
return SlicesRuleDefinition.slices().matching("(**)").should().beFreeOfCycles();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArchRule allBeanPostProcessorBeanMethodsShouldBeStaticAndHaveParametersThatWillNotCausePrematureInitialization() {
|
||||||
|
return ArchRuleDefinition.methods()
|
||||||
|
.that()
|
||||||
|
.areAnnotatedWith("org.springframework.context.annotation.Bean")
|
||||||
|
.and()
|
||||||
|
.haveRawReturnType(Predicates.assignableTo("org.springframework.beans.factory.config.BeanPostProcessor"))
|
||||||
|
.should(onlyHaveParametersThatWillNotCauseEagerInitialization())
|
||||||
|
.andShould()
|
||||||
|
.beStatic()
|
||||||
|
.allowEmptyShould(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArchCondition<JavaMethod> onlyHaveParametersThatWillNotCauseEagerInitialization() {
|
||||||
|
DescribedPredicate<CanBeAnnotated> notAnnotatedWithLazy = DescribedPredicate
|
||||||
|
.not(CanBeAnnotated.Predicates.annotatedWith("org.springframework.context.annotation.Lazy"));
|
||||||
|
DescribedPredicate<JavaClass> notOfASafeType = DescribedPredicate
|
||||||
|
.not(Predicates.assignableTo("org.springframework.beans.factory.ObjectProvider")
|
||||||
|
.or(Predicates.assignableTo("org.springframework.context.ApplicationContext"))
|
||||||
|
.or(Predicates.assignableTo("org.springframework.core.env.Environment")));
|
||||||
|
return new ArchCondition<JavaMethod>("not have parameters that will cause eager initialization") {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void check(JavaMethod item, ConditionEvents events) {
|
||||||
|
item.getParameters()
|
||||||
|
.stream()
|
||||||
|
.filter(notAnnotatedWithLazy)
|
||||||
|
.filter((parameter) -> notOfASafeType.test(parameter.getRawType()))
|
||||||
|
.forEach((parameter) -> events.add(SimpleConditionEvent.violated(parameter,
|
||||||
|
parameter.getDescription() + " will cause eager initialization as it is "
|
||||||
|
+ notAnnotatedWithLazy.getDescription() + " and is "
|
||||||
|
+ notOfASafeType.getDescription())));
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArchRule allBeanFactoryPostProcessorBeanMethodsShouldBeStaticAndHaveNoParameters() {
|
||||||
|
return ArchRuleDefinition.methods()
|
||||||
|
.that()
|
||||||
|
.areAnnotatedWith("org.springframework.context.annotation.Bean")
|
||||||
|
.and()
|
||||||
|
.haveRawReturnType(
|
||||||
|
Predicates.assignableTo("org.springframework.beans.factory.config.BeanFactoryPostProcessor"))
|
||||||
|
.should(haveNoParameters())
|
||||||
|
.andShould()
|
||||||
|
.beStatic()
|
||||||
|
.allowEmptyShould(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArchCondition<JavaMethod> haveNoParameters() {
|
||||||
|
return new ArchCondition<JavaMethod>("have no parameters") {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void check(JavaMethod item, ConditionEvents events) {
|
||||||
|
List<JavaParameter> parameters = item.getParameters();
|
||||||
|
if (!parameters.isEmpty()) {
|
||||||
|
events
|
||||||
|
.add(SimpleConditionEvent.violated(item, item.getDescription() + " should have no parameters"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClasses(FileCollection classes) {
|
||||||
|
this.classes = classes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Internal
|
||||||
|
public FileCollection getClasses() {
|
||||||
|
return this.classes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@InputFiles
|
||||||
|
@SkipWhenEmpty
|
||||||
|
@IgnoreEmptyDirectories
|
||||||
|
@PathSensitive(PathSensitivity.RELATIVE)
|
||||||
|
final FileTree getInputClasses() {
|
||||||
|
return this.classes.getAsFileTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
@OutputDirectory
|
||||||
|
public abstract DirectoryProperty getOutputDirectory();
|
||||||
|
|
||||||
|
}
|
@ -1,102 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2022-2023 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.architecture;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.StandardOpenOption;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import com.tngtech.archunit.core.domain.JavaClasses;
|
|
||||||
import com.tngtech.archunit.core.importer.ClassFileImporter;
|
|
||||||
import com.tngtech.archunit.lang.EvaluationResult;
|
|
||||||
import com.tngtech.archunit.library.dependencies.SliceRule;
|
|
||||||
import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition;
|
|
||||||
import org.gradle.api.DefaultTask;
|
|
||||||
import org.gradle.api.GradleException;
|
|
||||||
import org.gradle.api.Task;
|
|
||||||
import org.gradle.api.file.DirectoryProperty;
|
|
||||||
import org.gradle.api.file.FileCollection;
|
|
||||||
import org.gradle.api.file.FileTree;
|
|
||||||
import org.gradle.api.tasks.IgnoreEmptyDirectories;
|
|
||||||
import org.gradle.api.tasks.InputFiles;
|
|
||||||
import org.gradle.api.tasks.Internal;
|
|
||||||
import org.gradle.api.tasks.OutputDirectory;
|
|
||||||
import org.gradle.api.tasks.PathSensitive;
|
|
||||||
import org.gradle.api.tasks.PathSensitivity;
|
|
||||||
import org.gradle.api.tasks.SkipWhenEmpty;
|
|
||||||
import org.gradle.api.tasks.TaskAction;
|
|
||||||
|
|
||||||
import org.springframework.util.FileCopyUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link Task} that checks for package tangles.
|
|
||||||
*
|
|
||||||
* @author Andy Wilkinson
|
|
||||||
*/
|
|
||||||
public abstract class PackageTangleCheck extends DefaultTask {
|
|
||||||
|
|
||||||
private FileCollection classes;
|
|
||||||
|
|
||||||
public PackageTangleCheck() {
|
|
||||||
getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@TaskAction
|
|
||||||
void checkForPackageTangles() throws IOException {
|
|
||||||
JavaClasses javaClasses = new ClassFileImporter()
|
|
||||||
.importPaths(this.classes.getFiles().stream().map(File::toPath).collect(Collectors.toList()));
|
|
||||||
SliceRule freeOfCycles = SlicesRuleDefinition.slices().matching("(**)").should().beFreeOfCycles();
|
|
||||||
EvaluationResult result = freeOfCycles.evaluate(javaClasses);
|
|
||||||
File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile();
|
|
||||||
outputFile.getParentFile().mkdirs();
|
|
||||||
if (result.hasViolation()) {
|
|
||||||
Files.write(outputFile.toPath(), result.getFailureReport().toString().getBytes(StandardCharsets.UTF_8),
|
|
||||||
StandardOpenOption.CREATE);
|
|
||||||
FileWriter writer = new FileWriter(outputFile);
|
|
||||||
FileCopyUtils.copy(result.getFailureReport().toString(), writer);
|
|
||||||
throw new GradleException("Package tangle check failed. See '" + outputFile + "' for details.");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
outputFile.createNewFile();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setClasses(FileCollection classes) {
|
|
||||||
this.classes = classes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Internal
|
|
||||||
public FileCollection getClasses() {
|
|
||||||
return this.classes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@InputFiles
|
|
||||||
@SkipWhenEmpty
|
|
||||||
@IgnoreEmptyDirectories
|
|
||||||
@PathSensitive(PathSensitivity.RELATIVE)
|
|
||||||
final FileTree getInputClasses() {
|
|
||||||
return this.classes.getAsFileTree();
|
|
||||||
}
|
|
||||||
|
|
||||||
@OutputDirectory
|
|
||||||
public abstract DirectoryProperty getOutputDirectory();
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 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.architecture;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import com.tngtech.archunit.base.DescribedPredicate;
|
||||||
|
import com.tngtech.archunit.core.domain.JavaClass.Predicates;
|
||||||
|
import com.tngtech.archunit.lang.ArchRule;
|
||||||
|
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
|
||||||
|
import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Architecture rules evaluated by {@link ArchitectureCheck}.
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
*/
|
||||||
|
final class Rules {
|
||||||
|
|
||||||
|
private Rules() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<ArchRule> stream() {
|
||||||
|
return Stream.of(allPackagesShouldBeFreeOfTangles(),
|
||||||
|
allBeanPostProcessorBeanMethodsShouldBeStaticAndHaveParametersThatWillNotCausePrematureInitialization());
|
||||||
|
}
|
||||||
|
|
||||||
|
static ArchRule allPackagesShouldBeFreeOfTangles() {
|
||||||
|
return SlicesRuleDefinition.slices().matching("(**)").should().beFreeOfCycles();
|
||||||
|
}
|
||||||
|
|
||||||
|
static ArchRule allBeanPostProcessorBeanMethodsShouldBeStaticAndHaveParametersThatWillNotCausePrematureInitialization() {
|
||||||
|
return ArchRuleDefinition.methods()
|
||||||
|
.that()
|
||||||
|
.areAnnotatedWith("org.springframework.context.annotation.Bean")
|
||||||
|
.and()
|
||||||
|
.haveRawReturnType(Predicates.assignableTo("org.springframework.beans.factory.config.BeanPostProcessor"))
|
||||||
|
.should()
|
||||||
|
.beStatic()
|
||||||
|
.andShould()
|
||||||
|
.haveRawParameterTypes(DescribedPredicate
|
||||||
|
.allElements(Predicates.assignableTo("org.springframework.beans.factory.ObjectProvider")
|
||||||
|
.or(Predicates.assignableTo("org.springframework.context.ApplicationContext"))))
|
||||||
|
.allowEmptyShould(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 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.architecture;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.gradle.api.GradleException;
|
||||||
|
import org.gradle.api.Project;
|
||||||
|
import org.gradle.testfixtures.ProjectBuilder;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||||
|
import org.springframework.util.FileSystemUtils;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link ArchitectureCheck}.
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
*/
|
||||||
|
class ArchitectureCheckTests {
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
File temp;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenPackagesAreTangledTaskFailsAndWritesAReport() throws Exception {
|
||||||
|
prepareTask("tangled", (architectureCheck) -> {
|
||||||
|
assertThatExceptionOfType(GradleException.class).isThrownBy(() -> architectureCheck.checkArchitecture());
|
||||||
|
assertThat(failureReport(architectureCheck).length()).isGreaterThan(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenPackagesAreNotTangledTaskSucceedsAndWritesAnEmptyReport() throws Exception {
|
||||||
|
prepareTask("untangled", (architectureCheck) -> {
|
||||||
|
architectureCheck.checkArchitecture();
|
||||||
|
assertThat(failureReport(architectureCheck).length()).isZero();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
File failureReport(ArchitectureCheck architectureCheck) {
|
||||||
|
return new File(architectureCheck.getProject().getBuildDir(), "checkArchitecture/failure-report.txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenBeanPostProcessorBeanMethodIsNotStaticTaskFailsAndWritesAReport() throws Exception {
|
||||||
|
prepareTask("bpp/nonstatic", (architectureCheck) -> {
|
||||||
|
assertThatExceptionOfType(GradleException.class).isThrownBy(() -> architectureCheck.checkArchitecture());
|
||||||
|
assertThat(failureReport(architectureCheck).length()).isGreaterThan(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenBeanPostProcessorBeanMethodIsStaticAndHasUnsafeParametersTaskFailsAndWritesAReport() throws Exception {
|
||||||
|
prepareTask("bpp/unsafeparameters", (architectureCheck) -> {
|
||||||
|
assertThatExceptionOfType(GradleException.class).isThrownBy(() -> architectureCheck.checkArchitecture());
|
||||||
|
assertThat(failureReport(architectureCheck).length()).isGreaterThan(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenBeanPostProcessorBeanMethodIsStaticAndHasSafeParametersTaskSucceedsAndWritesAnEmptyReport()
|
||||||
|
throws Exception {
|
||||||
|
prepareTask("bpp/safeparameters", (architectureCheck) -> {
|
||||||
|
architectureCheck.checkArchitecture();
|
||||||
|
assertThat(failureReport(architectureCheck).length()).isZero();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenBeanPostProcessorBeanMethodIsStaticAndHasNoParametersTaskSucceedsAndWritesAnEmptyReport()
|
||||||
|
throws Exception {
|
||||||
|
prepareTask("bpp/noparameters", (architectureCheck) -> {
|
||||||
|
architectureCheck.checkArchitecture();
|
||||||
|
assertThat(failureReport(architectureCheck).length()).isZero();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenBeanFactoryPostProcessorBeanMethodIsNotStaticTaskFailsAndWritesAReport() throws Exception {
|
||||||
|
prepareTask("bfpp/nonstatic", (architectureCheck) -> {
|
||||||
|
assertThatExceptionOfType(GradleException.class).isThrownBy(() -> architectureCheck.checkArchitecture());
|
||||||
|
assertThat(failureReport(architectureCheck).length()).isGreaterThan(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenBeanFactoryPostProcessorBeanMethodIsStaticAndHasParametersTaskFailsAndWritesAReport() throws Exception {
|
||||||
|
prepareTask("bfpp/parameters", (architectureCheck) -> {
|
||||||
|
assertThatExceptionOfType(GradleException.class).isThrownBy(() -> architectureCheck.checkArchitecture());
|
||||||
|
assertThat(failureReport(architectureCheck).length()).isGreaterThan(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenBeanFactoryPostProcessorBeanMethodIsStaticAndHasNoParametersTaskSucceedsAndWritesAnEmptyReport()
|
||||||
|
throws Exception {
|
||||||
|
prepareTask("bfpp/noparameters", (architectureCheck) -> {
|
||||||
|
architectureCheck.checkArchitecture();
|
||||||
|
assertThat(failureReport(architectureCheck).length()).isZero();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareTask(String classes, Callback<ArchitectureCheck> callback) throws Exception {
|
||||||
|
File projectDir = new File(this.temp, "project");
|
||||||
|
projectDir.mkdirs();
|
||||||
|
copyClasses(classes, projectDir);
|
||||||
|
Project project = ProjectBuilder.builder().withProjectDir(projectDir).build();
|
||||||
|
ArchitectureCheck architectureCheck = project.getTasks()
|
||||||
|
.create("checkArchitecture", ArchitectureCheck.class, (task) -> task.setClasses(project.files("classes")));
|
||||||
|
callback.accept(architectureCheck);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyClasses(String name, File projectDir) throws IOException {
|
||||||
|
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
||||||
|
Resource root = resolver.getResource("classpath:org/springframework/boot/build/architecture/" + name);
|
||||||
|
FileSystemUtils.copyRecursively(root.getFile(),
|
||||||
|
new File(projectDir, "classes/org/springframework/boot/build/architecture/" + name));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface Callback<T> {
|
||||||
|
|
||||||
|
void accept(T item) throws Exception;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,93 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2012-2023 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.architecture;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.gradle.api.GradleException;
|
|
||||||
import org.gradle.api.Project;
|
|
||||||
import org.gradle.testfixtures.ProjectBuilder;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
|
||||||
|
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
|
||||||
import org.springframework.util.FileSystemUtils;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for {@link PackageTangleCheck}.
|
|
||||||
*
|
|
||||||
* @author Andy Wilkinson
|
|
||||||
*/
|
|
||||||
class PackageTangleCheckTests {
|
|
||||||
|
|
||||||
@TempDir
|
|
||||||
File temp;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void whenPackagesAreTangledTaskFailsAndWritesAReport() throws Exception {
|
|
||||||
prepareTask("tangled", (packageTangleCheck) -> {
|
|
||||||
assertThatExceptionOfType(GradleException.class)
|
|
||||||
.isThrownBy(() -> packageTangleCheck.checkForPackageTangles());
|
|
||||||
assertThat(
|
|
||||||
new File(packageTangleCheck.getProject().getBuildDir(), "checkForPackageTangles/failure-report.txt")
|
|
||||||
.length())
|
|
||||||
.isGreaterThan(0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void whenPackagesAreNotTangledTaskSucceedsAndWritesAnEmptyReport() throws Exception {
|
|
||||||
prepareTask("untangled", (packageTangleCheck) -> {
|
|
||||||
packageTangleCheck.checkForPackageTangles();
|
|
||||||
assertThat(
|
|
||||||
new File(packageTangleCheck.getProject().getBuildDir(), "checkForPackageTangles/failure-report.txt")
|
|
||||||
.length())
|
|
||||||
.isEqualTo(0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void prepareTask(String classes, Callback<PackageTangleCheck> callback) throws Exception {
|
|
||||||
File projectDir = new File(this.temp, "project");
|
|
||||||
projectDir.mkdirs();
|
|
||||||
copyClasses(classes, projectDir);
|
|
||||||
Project project = ProjectBuilder.builder().withProjectDir(projectDir).build();
|
|
||||||
PackageTangleCheck packageTangleCheck = project.getTasks()
|
|
||||||
.create("checkForPackageTangles", PackageTangleCheck.class,
|
|
||||||
(task) -> task.setClasses(project.files("classes")));
|
|
||||||
callback.accept(packageTangleCheck);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void copyClasses(String name, File projectDir) throws IOException {
|
|
||||||
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
|
||||||
Resource root = resolver.getResource("classpath:org/springframework/boot/build/architecture/" + name);
|
|
||||||
FileSystemUtils.copyRecursively(root.getFile(),
|
|
||||||
new File(projectDir, "classes/org/springframework/boot/build/architecture/" + name));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private interface Callback<T> {
|
|
||||||
|
|
||||||
void accept(T item) throws Exception;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 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.architecture.bfpp.nonstatic;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
||||||
|
class NonStaticBeanFactoryPostProcessorConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
BeanFactoryPostProcessor nonStaticBeanFactoryPostProcessor() {
|
||||||
|
return (beanFactory) -> {
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 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.architecture.bfpp.noparameters;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
||||||
|
class NoParametersBeanFactoryPostProcessorConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
static BeanFactoryPostProcessor noParametersBeanFactoryPostProcessor() {
|
||||||
|
return (beanFactory) -> {
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Integer beanOne() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
String beanTwo() {
|
||||||
|
return "test";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 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.architecture.bfpp.parameters;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
||||||
|
class ParametersBeanFactoryPostProcessorConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
static BeanFactoryPostProcessor parametersBeanFactoryPostProcessor(Integer param) {
|
||||||
|
return (beanFactory) -> {
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Integer beanOne() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
String beanTwo() {
|
||||||
|
return "test";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 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.architecture.bpp.nonstatic;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
||||||
|
class NonStaticBeanPostProcessorConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
BeanPostProcessor nonStaticBeanPostProcessor() {
|
||||||
|
return new BeanPostProcessor() {
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 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.architecture.bpp.noparameters;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
||||||
|
class NoParametersBeanPostProcessorConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
static BeanPostProcessor noParametersBeanPostProcessor() {
|
||||||
|
return new BeanPostProcessor() {
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Integer beanOne() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
String beanTwo() {
|
||||||
|
return "test";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 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.architecture.bpp.safeparameters;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
|
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
|
||||||
|
class SafeParametersBeanPostProcessorConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
static BeanPostProcessor safeParametersBeanPostProcessor(ApplicationContext context, ObjectProvider<String> beanOne,
|
||||||
|
ObjectProvider<String> beanTwo, Environment environment, @Lazy String beanThree) {
|
||||||
|
return new BeanPostProcessor() {
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Integer beanOne() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
String beanTwo() {
|
||||||
|
return "test";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 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.architecture.bpp.unsafeparameters;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
||||||
|
class UnsafeParametersBeanPostProcessorConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
static BeanPostProcessor unsafeParametersBeanPostProcessor(ApplicationContext context, Integer beanOne,
|
||||||
|
String beanTwo) {
|
||||||
|
return new BeanPostProcessor() {
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Integer beanOne() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
String beanTwo() {
|
||||||
|
return "test";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue