Browse Source

Merge branch '3.5.x'

Closes gh-48071
pull/48101/head
Stéphane Nicoll 1 month ago
parent
commit
73087f369b
  1. 4
      buildSrc/build.gradle
  2. 104
      buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckAdditionalSpringConfigurationMetadata.java
  3. 77
      buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckManualSpringConfigurationMetadata.java
  4. 93
      buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckSpringConfigurationMetadata.java
  5. 76
      buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationMetadataPlugin.java
  6. 252
      buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationPropertiesAnalyzer.java
  7. 185
      buildSrc/src/test/java/org/springframework/boot/build/context/properties/ConfigurationPropertiesAnalyzerTests.java
  8. 1
      core/spring-boot-test-autoconfigure/build.gradle
  9. 1
      module/spring-boot-cache-test/build.gradle
  10. 1
      module/spring-boot-micrometer-metrics-test/build.gradle
  11. 1
      module/spring-boot-micrometer-tracing-test/build.gradle
  12. 1
      module/spring-boot-restclient-test/build.gradle
  13. 1
      module/spring-boot-webflux-test/build.gradle

4
buildSrc/build.gradle

@ -114,6 +114,10 @@ gradlePlugin { @@ -114,6 +114,10 @@ gradlePlugin {
id = "org.springframework.boot.bom"
implementationClass = "org.springframework.boot.build.bom.BomPlugin"
}
configurationMetadataPlugin {
id = "org.springframework.boot.configuration-metadata"
implementationClass = "org.springframework.boot.build.context.properties.ConfigurationMetadataPlugin"
}
configurationPropertiesPlugin {
id = "org.springframework.boot.configuration-properties"
implementationClass = "org.springframework.boot.build.context.properties.ConfigurationPropertiesPlugin"

104
buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckAdditionalSpringConfigurationMetadata.java

@ -18,15 +18,6 @@ package org.springframework.boot.build.context.properties; @@ -18,15 +18,6 @@ package org.springframework.boot.build.context.properties;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.gradle.api.file.FileTree;
import org.gradle.api.file.RegularFileProperty;
@ -37,8 +28,8 @@ import org.gradle.api.tasks.PathSensitivity; @@ -37,8 +28,8 @@ import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.SourceTask;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.VerificationException;
import tools.jackson.core.StreamReadFeature;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.boot.build.context.properties.ConfigurationPropertiesAnalyzer.Report;
/**
* {@link SourceTask} that checks additional Spring configuration metadata files.
@ -65,98 +56,15 @@ public abstract class CheckAdditionalSpringConfigurationMetadata extends SourceT @@ -65,98 +56,15 @@ public abstract class CheckAdditionalSpringConfigurationMetadata extends SourceT
@TaskAction
void check() throws IOException {
Report report = createReport();
ConfigurationPropertiesAnalyzer analyzer = new ConfigurationPropertiesAnalyzer(getSource().getFiles());
Report report = new Report(this.projectDir);
analyzer.analyzeSort(report);
File reportFile = getReportLocation().get().getAsFile();
Files.write(reportFile.toPath(), report, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
report.write(reportFile);
if (report.hasProblems()) {
throw new VerificationException(
"Problems found in additional Spring configuration metadata. See " + reportFile + " for details.");
}
}
@SuppressWarnings("unchecked")
private Report createReport() {
JsonMapper jsonMapper = JsonMapper.builder().enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION).build();
Report report = new Report();
for (File file : getSource().getFiles()) {
Analysis analysis = report.analysis(this.projectDir.toPath().relativize(file.toPath()));
Map<String, Object> json = jsonMapper.readValue(file, Map.class);
check("groups", json, analysis);
check("properties", json, analysis);
check("hints", json, analysis);
}
return report;
}
@SuppressWarnings("unchecked")
private void check(String key, Map<String, Object> json, Analysis analysis) {
List<Map<String, Object>> groups = (List<Map<String, Object>>) json.getOrDefault(key, Collections.emptyList());
List<String> names = groups.stream().map((group) -> (String) group.get("name")).toList();
List<String> sortedNames = sortedCopy(names);
for (int i = 0; i < names.size(); i++) {
String actual = names.get(i);
String expected = sortedNames.get(i);
if (!actual.equals(expected)) {
analysis.problems.add("Wrong order at $." + key + "[" + i + "].name - expected '" + expected
+ "' but found '" + actual + "'");
}
}
}
private List<String> sortedCopy(Collection<String> original) {
List<String> copy = new ArrayList<>(original);
Collections.sort(copy);
return copy;
}
private static final class Report implements Iterable<String> {
private final List<Analysis> analyses = new ArrayList<>();
private Analysis analysis(Path path) {
Analysis analysis = new Analysis(path);
this.analyses.add(analysis);
return analysis;
}
private boolean hasProblems() {
for (Analysis analysis : this.analyses) {
if (!analysis.problems.isEmpty()) {
return true;
}
}
return false;
}
@Override
public Iterator<String> iterator() {
List<String> lines = new ArrayList<>();
for (Analysis analysis : this.analyses) {
lines.add(analysis.source.toString());
lines.add("");
if (analysis.problems.isEmpty()) {
lines.add("No problems found.");
}
else {
lines.addAll(analysis.problems);
}
lines.add("");
}
return lines.iterator();
}
}
private static final class Analysis {
private final List<String> problems = new ArrayList<>();
private final Path source;
private Analysis(Path source) {
this.source = source;
}
}
}

77
buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckManualSpringConfigurationMetadata.java

@ -0,0 +1,77 @@ @@ -0,0 +1,77 @@
/*
* Copyright 2012-present 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.context.properties;
import java.io.File;
import java.io.IOException;
import java.util.List;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.SourceTask;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.VerificationException;
import org.springframework.boot.build.context.properties.ConfigurationPropertiesAnalyzer.Report;
/**
* {@link SourceTask} that checks manual Spring configuration metadata files.
*
* @author Andy Wilkinson
* @author Stephane Nicoll
*/
public abstract class CheckManualSpringConfigurationMetadata extends DefaultTask {
private final File projectDir;
public CheckManualSpringConfigurationMetadata() {
this.projectDir = getProject().getProjectDir();
}
@OutputFile
public abstract RegularFileProperty getReportLocation();
@InputFile
@PathSensitive(PathSensitivity.RELATIVE)
public abstract Property<File> getMetadataLocation();
@Input
public abstract ListProperty<String> getExclusions();
@TaskAction
void check() throws IOException {
ConfigurationPropertiesAnalyzer analyzer = new ConfigurationPropertiesAnalyzer(
List.of(getMetadataLocation().get()));
Report report = new Report(this.projectDir);
analyzer.analyzeSort(report);
analyzer.analyzePropertyDescription(report, getExclusions().get());
File reportFile = getReportLocation().get().getAsFile();
report.write(reportFile);
if (report.hasProblems()) {
throw new VerificationException(
"Problems found in manual Spring configuration metadata. See " + reportFile + " for details.");
}
}
}

93
buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckSpringConfigurationMetadata.java

@ -18,13 +18,7 @@ package org.springframework.boot.build.context.properties; @@ -18,13 +18,7 @@ package org.springframework.boot.build.context.properties;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.RegularFileProperty;
@ -37,7 +31,8 @@ import org.gradle.api.tasks.PathSensitivity; @@ -37,7 +31,8 @@ import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.SourceTask;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.VerificationException;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.boot.build.context.properties.ConfigurationPropertiesAnalyzer.Report;
/**
* {@link SourceTask} that checks {@code spring-configuration-metadata.json} files.
@ -46,10 +41,10 @@ import tools.jackson.databind.json.JsonMapper; @@ -46,10 +41,10 @@ import tools.jackson.databind.json.JsonMapper;
*/
public abstract class CheckSpringConfigurationMetadata extends DefaultTask {
private final Path projectRoot;
private final File projectRoot;
public CheckSpringConfigurationMetadata() {
this.projectRoot = getProject().getProjectDir().toPath();
this.projectRoot = getProject().getProjectDir();
}
@OutputFile
@ -64,86 +59,16 @@ public abstract class CheckSpringConfigurationMetadata extends DefaultTask { @@ -64,86 +59,16 @@ public abstract class CheckSpringConfigurationMetadata extends DefaultTask {
@TaskAction
void check() throws IOException {
Report report = createReport();
Report report = new Report(this.projectRoot);
ConfigurationPropertiesAnalyzer analyzer = new ConfigurationPropertiesAnalyzer(
List.of(getMetadataLocation().get().getAsFile()));
analyzer.analyzePropertyDescription(report, getExclusions().get());
File reportFile = getReportLocation().get().getAsFile();
Files.write(reportFile.toPath(), report, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
report.write(reportFile);
if (report.hasProblems()) {
throw new VerificationException(
"Problems found in Spring configuration metadata. See " + reportFile + " for details.");
}
}
@SuppressWarnings("unchecked")
private Report createReport() {
JsonMapper jsonMapper = new JsonMapper();
File file = getMetadataLocation().get().getAsFile();
Report report = new Report(this.projectRoot.relativize(file.toPath()));
Map<String, Object> json = jsonMapper.readValue(file, Map.class);
List<Map<String, Object>> properties = (List<Map<String, Object>>) json.get("properties");
for (Map<String, Object> property : properties) {
String name = (String) property.get("name");
if (!isDeprecated(property) && !isDescribed(property) && !isExcluded(name)) {
report.propertiesWithNoDescription.add(name);
}
}
return report;
}
private boolean isExcluded(String propertyName) {
for (String exclusion : getExclusions().get()) {
if (propertyName.equals(exclusion)) {
return true;
}
if (exclusion.endsWith(".*")) {
if (propertyName.startsWith(exclusion.substring(0, exclusion.length() - 2))) {
return true;
}
}
}
return false;
}
@SuppressWarnings("unchecked")
private boolean isDeprecated(Map<String, Object> property) {
return (Map<String, Object>) property.get("deprecation") != null;
}
private boolean isDescribed(Map<String, Object> property) {
return property.get("description") != null;
}
private static final class Report implements Iterable<String> {
private final List<String> propertiesWithNoDescription = new ArrayList<>();
private final Path source;
private Report(Path source) {
this.source = source;
}
private boolean hasProblems() {
return !this.propertiesWithNoDescription.isEmpty();
}
@Override
public Iterator<String> iterator() {
List<String> lines = new ArrayList<>();
lines.add(this.source.toString());
lines.add("");
if (this.propertiesWithNoDescription.isEmpty()) {
lines.add("No problems found.");
}
else {
lines.add("The following properties have no description:");
lines.add("");
lines.addAll(this.propertiesWithNoDescription.stream().map((line) -> "\t" + line).toList());
}
lines.add("");
return lines.iterator();
}
}
}

76
buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationMetadataPlugin.java

@ -0,0 +1,76 @@ @@ -0,0 +1,76 @@
/*
* Copyright 2012-present 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.context.properties;
import java.io.File;
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.tasks.SourceSet;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.language.base.plugins.LifecycleBasePlugin;
import org.gradle.language.jvm.tasks.ProcessResources;
/**
* {@link Plugin} for projects that <em>only</em> define manual configuration metadata.
* When applied, the plugin registers a {@link CheckManualSpringConfigurationMetadata}
* task and configures the {@code check} task to depend upon it.
*
* @author Andy Wilkinson
* @author Stephane Nicoll
*/
public class ConfigurationMetadataPlugin implements Plugin<Project> {
/**
* Name of the {@link CheckAdditionalSpringConfigurationMetadata} task.
*/
public static final String CHECK_MANUAL_SPRING_CONFIGURATION_METADATA_TASK_NAME = "checkManualSpringConfigurationMetadata";
@Override
public void apply(Project project) {
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> registerCheckAdditionalMetadataTask(project));
}
private void registerCheckAdditionalMetadataTask(Project project) {
TaskProvider<CheckManualSpringConfigurationMetadata> checkConfigurationMetadata = project.getTasks()
.register(CHECK_MANUAL_SPRING_CONFIGURATION_METADATA_TASK_NAME,
CheckManualSpringConfigurationMetadata.class);
checkConfigurationMetadata.configure((check) -> {
SourceSet mainSourceSet = project.getExtensions()
.getByType(JavaPluginExtension.class)
.getSourceSets()
.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
Provider<File> manualMetadataLocation = project.getTasks()
.named(mainSourceSet.getProcessResourcesTaskName(), ProcessResources.class)
.map((processResources) -> new File(processResources.getDestinationDir(),
"META-INF/spring-configuration-metadata.json"));
check.getMetadataLocation().set(manualMetadataLocation);
check.getReportLocation()
.set(project.getLayout()
.getBuildDirectory()
.file("reports/manual-spring-configuration-metadata/check.txt"));
});
project.getTasks()
.named(LifecycleBasePlugin.CHECK_TASK_NAME)
.configure((check) -> check.dependsOn(checkConfigurationMetadata));
}
}

252
buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationPropertiesAnalyzer.java

@ -0,0 +1,252 @@ @@ -0,0 +1,252 @@
/*
* Copyright 2012-present 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.context.properties;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import tools.jackson.core.StreamReadFeature;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.function.SingletonSupplier;
/**
* Check configuration metadata for inconsistencies. The available checks are:
* <ul>
* <li>Metadata element should be sorted alphabetically: {@link #analyzeSort(Report)}</li>
* <li>Property must have a description:
* {@link #analyzePropertyDescription(Report, List)}</li>
* </ul>
*
* @author Stephane Nicoll
*/
class ConfigurationPropertiesAnalyzer {
private final Collection<File> sources;
private final SingletonSupplier<JsonMapper> jsonMapperSupplier;
ConfigurationPropertiesAnalyzer(Collection<File> sources) {
if (sources.isEmpty()) {
throw new IllegalArgumentException("At least one source should be provided");
}
this.sources = sources;
this.jsonMapperSupplier = SingletonSupplier
.of(() -> JsonMapper.builder().enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION).build());
}
void analyzeSort(Report report) {
for (File source : this.sources) {
report.registerAnalysis(source, analyzeSort(source));
}
}
private Analysis analyzeSort(File source) {
Map<String, Object> json = readJsonContent(source);
Analysis analysis = new Analysis("Metadata element order:");
analyzeMetadataElementsSort("groups", json, analysis);
analyzeMetadataElementsSort("properties", json, analysis);
analyzeMetadataElementsSort("hints", json, analysis);
return analysis;
}
@SuppressWarnings("unchecked")
private void analyzeMetadataElementsSort(String key, Map<String, Object> json, Analysis analysis) {
List<Map<String, Object>> groups = (List<Map<String, Object>>) json.getOrDefault(key, Collections.emptyList());
List<String> names = groups.stream().map((group) -> (String) group.get("name")).toList();
List<String> sortedNames = names.stream().sorted().toList();
for (int i = 0; i < names.size(); i++) {
String actual = names.get(i);
String expected = sortedNames.get(i);
if (!actual.equals(expected)) {
analysis.addItem("Wrong order at $." + key + "[" + i + "].name - expected '" + expected
+ "' but found '" + actual + "'");
}
}
}
void analyzePropertyDescription(Report report, List<String> exclusions) {
for (File source : this.sources) {
report.registerAnalysis(source, analyzePropertyDescription(source, exclusions));
}
}
@SuppressWarnings("unchecked")
private Analysis analyzePropertyDescription(File source, List<String> exclusions) {
Map<String, Object> json = readJsonContent(source);
Analysis analysis = new Analysis("The following properties have no description:");
List<Map<String, Object>> properties = (List<Map<String, Object>>) json.get("properties");
for (Map<String, Object> property : properties) {
String name = (String) property.get("name");
if (!isDeprecated(property) && !isDescribed(property) && !isExcluded(exclusions, name)) {
analysis.addItem(name);
}
}
return analysis;
}
private boolean isExcluded(List<String> exclusions, String propertyName) {
for (String exclusion : exclusions) {
if (propertyName.equals(exclusion)) {
return true;
}
if (exclusion.endsWith(".*")) {
if (propertyName.startsWith(exclusion.substring(0, exclusion.length() - 2))) {
return true;
}
}
}
return false;
}
private boolean isDeprecated(Map<String, Object> property) {
return property.get("deprecation") != null;
}
private boolean isDescribed(Map<String, Object> property) {
return property.get("description") != null;
}
@SuppressWarnings("unchecked")
private Map<String, Object> readJsonContent(File source) {
return this.jsonMapperSupplier.obtain().readValue(source, Map.class);
}
private static <T> void writeAll(PrintWriter writer, Iterable<T> elements, Consumer<T> itemWriter) {
Iterator<T> it = elements.iterator();
while (it.hasNext()) {
itemWriter.accept(it.next());
if (it.hasNext()) {
writer.println();
}
}
}
static class Report {
private final File baseDirectory;
private final MultiValueMap<File, Analysis> analyses = new LinkedMultiValueMap<>();
Report(File baseDirectory) {
this.baseDirectory = baseDirectory;
}
void registerAnalysis(File path, Analysis analysis) {
this.analyses.add(path, analysis);
}
boolean hasProblems() {
return this.analyses.values()
.stream()
.anyMatch((candidates) -> candidates.stream().anyMatch(Analysis::hasProblems));
}
List<Analysis> getAnalyses(File source) {
return this.analyses.getOrDefault(source, Collections.emptyList());
}
/**
* Write this report to the given {@code file}.
* @param file the file to write the report to
*/
void write(File file) throws IOException {
Files.writeString(file.toPath(), createContent(), StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING);
}
private String createContent() {
if (this.analyses.isEmpty()) {
return "No problems found.";
}
StringWriter out = new StringWriter();
try (PrintWriter writer = new PrintWriter(out)) {
writeAll(writer, this.analyses.entrySet(), (entry) -> {
writer.println(this.baseDirectory.toPath().relativize(entry.getKey().toPath()));
boolean hasProblems = entry.getValue().stream().anyMatch(Analysis::hasProblems);
if (hasProblems) {
writeAll(writer, entry.getValue(), (analysis) -> analysis.createDetails(writer));
}
else {
writer.println("No problems found.");
}
});
}
return out.toString();
}
}
static class Analysis {
private final String header;
private final List<String> items;
Analysis(String header) {
this.header = header;
this.items = new ArrayList<>();
}
void addItem(String item) {
this.items.add(item);
}
boolean hasProblems() {
return !this.items.isEmpty();
}
List<String> getItems() {
return this.items;
}
void createDetails(PrintWriter writer) {
writer.println(this.header);
if (this.items.isEmpty()) {
writer.println("No problems found.");
}
else {
for (String item : this.items) {
writer.println("\t- " + item);
}
}
}
@Override
public String toString() {
StringWriter out = new StringWriter();
PrintWriter writer = new PrintWriter(out);
createDetails(writer);
return out.toString();
}
}
}

185
buildSrc/src/test/java/org/springframework/boot/build/context/properties/ConfigurationPropertiesAnalyzerTests.java

@ -0,0 +1,185 @@ @@ -0,0 +1,185 @@
/*
* Copyright 2012-present 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.context.properties;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.build.context.properties.ConfigurationPropertiesAnalyzer.Analysis;
import org.springframework.boot.build.context.properties.ConfigurationPropertiesAnalyzer.Report;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link ConfigurationPropertiesAnalyzer}.
*
* @author Stephane Nicoll
*/
class ConfigurationPropertiesAnalyzerTests {
@Test
void createAnalyzerWithNoSource() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new ConfigurationPropertiesAnalyzer(Collections.emptyList()))
.withMessage("At least one source should be provided");
}
@Test
void analyzeSortWithAlphabeticalOrder(@TempDir File tempDir) throws IOException {
File metadata = new File(tempDir, "metadata.json");
Files.writeString(metadata.toPath(), """
{ "properties": [
{ "name": "abc"}, {"name": "def"}, {"name": "xyz"}
]
}""");
Report report = new Report(tempDir);
ConfigurationPropertiesAnalyzer analyzer = new ConfigurationPropertiesAnalyzer(List.of(metadata));
analyzer.analyzeSort(report);
assertThat(report.hasProblems()).isFalse();
assertThat(report.getAnalyses(metadata)).singleElement()
.satisfies(((analysis) -> assertThat(analysis.getItems()).isEmpty()));
}
@Test
void analyzeSortWithViolations(@TempDir File tempDir) throws IOException {
File metadata = new File(tempDir, "metadata.json");
Files.writeString(metadata.toPath(), """
{ "properties": [
{ "name": "def"}, {"name": "abc"}, {"name": "xyz"}
]
}""");
Report report = new Report(tempDir);
ConfigurationPropertiesAnalyzer analyzer = new ConfigurationPropertiesAnalyzer(List.of(metadata));
analyzer.analyzeSort(report);
assertThat(report.hasProblems()).isTrue();
assertThat(report.getAnalyses(metadata)).singleElement()
.satisfies((analysis) -> assertThat(analysis.getItems()).containsExactly(
"Wrong order at $.properties[0].name - expected 'abc' but found 'def'",
"Wrong order at $.properties[1].name - expected 'def' but found 'abc'"));
}
@Test
void analyzePropertyDescription(@TempDir File tempDir) throws IOException {
File metadata = new File(tempDir, "metadata.json");
Files.writeString(metadata.toPath(), """
{ "properties": [
{ "name": "abc", "description": "This is abc." },
{ "name": "def", "description": "This is def." },
{ "name": "xyz", "description": "This is xyz." }
]
}""");
Report report = new Report(tempDir);
ConfigurationPropertiesAnalyzer analyzer = new ConfigurationPropertiesAnalyzer(List.of(metadata));
analyzer.analyzePropertyDescription(report, List.of());
assertThat(report.hasProblems()).isFalse();
assertThat(report.getAnalyses(metadata)).singleElement()
.satisfies(((analysis) -> assertThat(analysis.getItems()).isEmpty()));
}
@Test
void analyzePropertyDescriptionWithMissingDescription(@TempDir File tempDir) throws IOException {
File metadata = new File(tempDir, "metadata.json");
Files.writeString(metadata.toPath(), """
{ "properties": [
{ "name": "abc", "description": "This is abc." },
{ "name": "def" },
{ "name": "xyz", "description": "This is xyz." }
]
}""");
Report report = new Report(tempDir);
ConfigurationPropertiesAnalyzer analyzer = new ConfigurationPropertiesAnalyzer(List.of(metadata));
analyzer.analyzePropertyDescription(report, List.of());
assertThat(report.hasProblems()).isTrue();
assertThat(report.getAnalyses(metadata)).singleElement()
.satisfies(((analysis) -> assertThat(analysis.getItems()).containsExactly("def")));
}
@Test
void writeEmptyReport(@TempDir File tempDir) throws IOException {
assertThat(writeToFile(tempDir, new Report(tempDir))).hasContent("No problems found.");
}
@Test
void writeReportWithNoProblemsFound(@TempDir File tempDir) throws IOException {
Report report = new Report(tempDir);
File first = new File(tempDir, "metadata-1.json");
report.registerAnalysis(first, new Analysis("Check for things:"));
File second = new File(tempDir, "metadata-2.json");
report.registerAnalysis(second, new Analysis("Check for other things:"));
assertThat(writeToFile(tempDir, report)).content().isEqualTo("""
metadata-1.json
No problems found.
metadata-2.json
No problems found.
""");
}
@Test
void writeReportWithOneProblem(@TempDir File tempDir) throws IOException {
Report report = new Report(tempDir);
File metadata = new File(tempDir, "metadata-1.json");
Analysis analysis = new Analysis("Check for things:");
analysis.addItem("Should not be deprecated");
report.registerAnalysis(metadata, analysis);
report.registerAnalysis(metadata, new Analysis("Check for other things:"));
assertThat(writeToFile(tempDir, report)).content().isEqualTo("""
metadata-1.json
Check for things:
- Should not be deprecated
Check for other things:
No problems found.
""");
}
@Test
void writeReportWithSeveralProblems(@TempDir File tempDir) throws IOException {
Report report = new Report(tempDir);
File metadata = new File(tempDir, "metadata-1.json");
Analysis firstAnalysis = new Analysis("Check for things:");
firstAnalysis.addItem("Should not be deprecated");
firstAnalysis.addItem("Should not be public");
report.registerAnalysis(metadata, firstAnalysis);
Analysis secondAnalysis = new Analysis("Check for other things:");
secondAnalysis.addItem("Field 'this' not expected");
report.registerAnalysis(metadata, secondAnalysis);
assertThat(writeToFile(tempDir, report)).content().isEqualTo("""
metadata-1.json
Check for things:
- Should not be deprecated
- Should not be public
Check for other things:
- Field 'this' not expected
""");
}
private File writeToFile(File directory, Report report) throws IOException {
File file = new File(directory, "report.txt");
report.write(file);
return file;
}
}

1
core/spring-boot-test-autoconfigure/build.gradle

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
plugins {
id "java-library"
id "org.springframework.boot.configuration-metadata"
id "org.springframework.boot.deployed"
id "org.springframework.boot.optional-dependencies"
}

1
module/spring-boot-cache-test/build.gradle

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
plugins {
id "java-library"
id "org.springframework.boot.configuration-metadata"
id "org.springframework.boot.deployed"
id "org.springframework.boot.optional-dependencies"
id "org.springframework.boot.test-auto-configuration"

1
module/spring-boot-micrometer-metrics-test/build.gradle

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
plugins {
id "java-library"
id "org.springframework.boot.configuration-metadata"
id "org.springframework.boot.deployed"
id "org.springframework.boot.optional-dependencies"
id "org.springframework.boot.test-slice"

1
module/spring-boot-micrometer-tracing-test/build.gradle

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
plugins {
id "java-library"
id "org.springframework.boot.configuration-metadata"
id "org.springframework.boot.deployed"
id "org.springframework.boot.optional-dependencies"
id "org.springframework.boot.test-slice"

1
module/spring-boot-restclient-test/build.gradle

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
plugins {
id "java-library"
id "org.springframework.boot.configuration-metadata"
id "org.springframework.boot.deployed"
id "org.springframework.boot.optional-dependencies"
id "org.springframework.boot.test-slice"

1
module/spring-boot-webflux-test/build.gradle

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
plugins {
id "java-library"
id "org.springframework.boot.configuration-metadata"
id "org.springframework.boot.deployed"
id "org.springframework.boot.optional-dependencies"
id "org.springframework.boot.test-slice"

Loading…
Cancel
Save