Merge branch '3.3.x'

Closes gh-43382
This commit is contained in:
Andy Wilkinson
2024-12-04 17:00:01 +00:00
2 changed files with 87 additions and 17 deletions

View File

@@ -16,16 +16,18 @@
package org.springframework.boot.diagnostics.analyzer;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.diagnostics.FailureAnalyzer;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginLookup;
import org.springframework.boot.origin.PropertySourceOrigin;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
@@ -61,18 +63,23 @@ class InvalidConfigurationPropertyValueFailureAnalyzer
}
private List<Descriptor> getDescriptors(String propertyName) {
Set<Origin> seen = new HashSet<>();
return getPropertySources().filter((source) -> source.containsProperty(propertyName))
.map((source) -> Descriptor.get(source, propertyName))
.filter((descriptor) -> seen.add(getOrigin(descriptor)))
.toList();
}
private Stream<PropertySource<?>> getPropertySources() {
if (this.environment == null) {
return Stream.empty();
private Origin getOrigin(Descriptor descriptor) {
Origin origin = descriptor.origin;
if (origin instanceof PropertySourceOrigin propertySourceOrigin) {
origin = propertySourceOrigin.getOrigin();
}
return this.environment.getPropertySources()
.stream()
.filter((source) -> !ConfigurationPropertySources.isAttachedConfigurationPropertySource(source));
return origin;
}
private Stream<PropertySource<?>> getPropertySources() {
return (this.environment != null) ? this.environment.getPropertySources().stream() : Stream.empty();
}
private void appendDetails(StringBuilder message, InvalidConfigurationPropertyValueException cause,

View File

@@ -20,13 +20,16 @@ import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginLookup;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.SystemEnvironmentPropertySource;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.util.ObjectUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -63,6 +66,26 @@ class InvalidConfigurationPropertyValueFailureAnalyzerTests {
.doesNotContain("Additionally, this property is also set");
}
@Test
void analysisWithKnownPropertyFromSystemEnvironment() {
MapPropertySource source = new SystemEnvironmentPropertySource("systemEnvironment",
Collections.singletonMap("COM_EXAMPLE_TESTPROPERTY", "invalid"));
this.environment.getPropertySources().addFirst(source);
ConfigurationPropertySources.attach(this.environment);
assertThat(this.environment.getProperty("com.example.test-property")).isEqualTo("invalid");
InvalidConfigurationPropertyValueException failure = new InvalidConfigurationPropertyValueException(
"com.example.test-property", "invalid", "This is not valid.");
FailureAnalysis analysis = performAnalysis(failure);
assertThat(analysis.getDescription()).contains("com.example.test-property")
.contains("invalid")
.contains("property source \"systemEnvironment\"");
assertThat(analysis.getCause()).isSameAs(failure);
assertThat(analysis.getAction()).contains("Review the value of the property with the provided reason.");
assertThat(analysis.getDescription()).contains("Validation failed for the following reason")
.contains("This is not valid.")
.doesNotContain("Additionally, this property is also set");
}
@Test
void analysisWithKnownPropertyAndNoReason() {
MapPropertySource source = new MapPropertySource("test", Collections.singletonMap("test.property", "invalid"));
@@ -84,6 +107,8 @@ class InvalidConfigurationPropertyValueFailureAnalyzerTests {
this.environment.getPropertySources().addFirst(OriginCapablePropertySource.get(source));
this.environment.getPropertySources().addLast(additional);
this.environment.getPropertySources().addLast(OriginCapablePropertySource.get(another));
this.environment.getPropertySources().addLast(OriginCapablePropertySource.get("another-again", another));
ConfigurationPropertySources.attach(this.environment);
InvalidConfigurationPropertyValueException failure = new InvalidConfigurationPropertyValueException(
"test.property", "invalid", "This is not valid.");
FailureAnalysis analysis = performAnalysis(failure);
@@ -92,7 +117,8 @@ class InvalidConfigurationPropertyValueFailureAnalyzerTests {
assertThat(analysis.getDescription())
.contains("Additionally, this property is also set in the following property sources:")
.contains("In 'additional' with the value 'valid'")
.contains("In 'another' with the value 'test' (originating from 'TestOrigin test.property')");
.contains("In 'another' with the value 'test' (originating from 'TestOrigin test.property')")
.doesNotContain("another-again");
}
@Test
@@ -122,7 +148,11 @@ class InvalidConfigurationPropertyValueFailureAnalyzerTests {
private final EnumerablePropertySource<T> propertySource;
OriginCapablePropertySource(EnumerablePropertySource<T> propertySource) {
super(propertySource.getName(), propertySource.getSource());
this(propertySource.getName(), propertySource);
}
OriginCapablePropertySource(String name, EnumerablePropertySource<T> propertySource) {
super(name, propertySource.getSource());
this.propertySource = propertySource;
}
@@ -138,20 +168,53 @@ class InvalidConfigurationPropertyValueFailureAnalyzerTests {
@Override
public Origin getOrigin(String name) {
return new Origin() {
@Override
public String toString() {
return "TestOrigin " + name;
}
};
return new TestOrigin(name, this.propertySource.getName());
}
static <T> OriginCapablePropertySource<T> get(EnumerablePropertySource<T> propertySource) {
return new OriginCapablePropertySource<>(propertySource);
}
static <T> OriginCapablePropertySource<T> get(String name, EnumerablePropertySource<T> propertySource) {
return new OriginCapablePropertySource<>(name, propertySource);
}
static final class TestOrigin implements Origin {
private final String name;
private final String sourceName;
private TestOrigin(String name, String sourceName) {
this.name = name;
this.sourceName = sourceName;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
TestOrigin other = (TestOrigin) obj;
return ObjectUtils.nullSafeEquals(this.name, other.name)
&& ObjectUtils.nullSafeEquals(this.sourceName, other.sourceName);
}
@Override
public int hashCode() {
return ObjectUtils.nullSafeHashCode(this.name);
}
@Override
public String toString() {
return "TestOrigin " + this.name;
}
}
}
}