Make TestPropertyValues immutable

Update `TestPropertyValues` so that it is totally immutable. Methods
now return a new instance rather than changing existing state.

See gh-9875
This commit is contained in:
Phillip Webb 2017-07-26 15:38:58 -07:00
parent 07556cda51
commit ad9f28110c
8 changed files with 103 additions and 107 deletions

View File

@ -93,7 +93,7 @@ public class MixedNeo4jRepositoriesAutoConfigurationTests {
private void load(Class<?> config, String... environment) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
TestPropertyValues.of(environment).and("spring.datasource.initialize", "false")
TestPropertyValues.of(environment).and("spring.datasource.initialize=false")
.applyTo(context);
context.register(config);
context.register(DataSourceAutoConfiguration.class,

View File

@ -130,7 +130,7 @@ public class DataSourceJmxConfigurationTests {
private void load(Class<?> config, String... environment) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
String jdbcUrl = "jdbc:hsqldb:mem:test-" + UUID.randomUUID().toString();
TestPropertyValues.of(environment).and("spring.datasource.url", jdbcUrl)
TestPropertyValues.of(environment).and("spring.datasource.url=" + jdbcUrl)
.applyTo(context);
if (config != null) {
context.register(config);

View File

@ -104,8 +104,8 @@ public class HikariDataSourceConfigurationTests {
private void load(String... environment) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
TestPropertyValues.of(environment).and("spring.datasource.initialize", "false")
.and("spring.datasource.type", HikariDataSource.class.getName())
TestPropertyValues.of(environment).and("spring.datasource.initialize=false")
.and("spring.datasource.type=" + HikariDataSource.class.getName())
.applyTo(ctx);
ctx.register(DataSourceAutoConfiguration.class);
ctx.refresh();

View File

@ -213,7 +213,7 @@ public abstract class AbstractJpaAutoConfigurationTests {
ctx.setClassLoader(classLoader);
}
TestPropertyValues.of(environment)
.and("spring.datasource.generate-unique-name", "true").applyTo(ctx);
.and("spring.datasource.generate-unique-name=true").applyTo(ctx);
ctx.register(TestConfiguration.class);
if (!ObjectUtils.isEmpty(configs)) {
ctx.register(configs);

View File

@ -17,7 +17,6 @@
package org.springframework.boot.test.context.runner;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
@ -99,9 +98,9 @@ abstract class AbstractApplicationContextRunner<SELF extends AbstractApplication
private final Supplier<C> contextFactory;
private final TestPropertyValues environmentProperties;
private TestPropertyValues environmentProperties;
private final TestPropertyValues systemProperties;
private TestPropertyValues systemProperties;
private ClassLoader classLoader;
@ -131,20 +130,7 @@ abstract class AbstractApplicationContextRunner<SELF extends AbstractApplication
* @see #withSystemProperties(String...)
*/
public SELF withPropertyValues(String... pairs) {
Arrays.stream(pairs).forEach(this.environmentProperties::and);
return self();
}
/**
* Add the specified {@link Environment} property.
* @param name the name of the property
* @param value the value of the property
* @return this instance
* @see TestPropertyValues
* @see #withSystemProperties(String...)
*/
public SELF withPropertyValue(String name, String value) {
this.environmentProperties.and(name, value);
this.environmentProperties = this.environmentProperties.and(pairs);
return self();
}
@ -159,22 +145,7 @@ abstract class AbstractApplicationContextRunner<SELF extends AbstractApplication
* @see #withSystemProperties(String...)
*/
public SELF withSystemProperties(String... pairs) {
Arrays.stream(pairs).forEach(this.systemProperties::and);
return self();
}
/**
* Add the specified {@link System} property. System properties are added before the
* context is {@link #run(ContextConsumer) run} and restored when the context is
* closed.
* @param name the property name
* @param value the property value
* @return this instance
* @see TestPropertyValues
* @see #withSystemProperties(String...)
*/
public SELF withSystemProperty(String name, String value) {
this.systemProperties.and(name, value);
this.systemProperties = this.systemProperties.and(pairs);
return self();
}

View File

@ -17,9 +17,13 @@
package org.springframework.boot.test.util;
import java.io.Closeable;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.stream.Stream;
import com.google.common.collect.Streams;
@ -45,56 +49,28 @@ import org.springframework.util.StringUtils;
*/
public final class TestPropertyValues {
private final Map<String, Object> properties = new LinkedHashMap<>();
private static final TestPropertyValues EMPTY = new TestPropertyValues(
Collections.emptyMap());
private TestPropertyValues(String[] pairs) {
addProperties(pairs);
}
private final Map<String, Object> properties;
private void addProperties(String[] pairs) {
for (String pair : pairs) {
and(pair);
}
private TestPropertyValues(Map<String, Object> properties) {
this.properties = Collections.unmodifiableMap(properties);
}
/**
* Builder method to append another property pair the underlying map of properties.
* @param pair The property pair to add
* @return the existing instance of {@link TestPropertyValues}
* Builder method to add more properties.
* @param pairs The property pairs to add
* @return a new {@link TestPropertyValues} instance
*/
public TestPropertyValues and(String pair) {
int index = getSeparatorIndex(pair);
String key = pair.substring(0, index > 0 ? index : pair.length());
String value = index > 0 ? pair.substring(index + 1) : "";
and(key.trim(), value.trim());
return this;
public TestPropertyValues and(String... pairs) {
return and(Arrays.stream(pairs).map(Pair::parse));
}
private int getSeparatorIndex(String pair) {
int colonIndex = pair.indexOf(":");
int equalIndex = pair.indexOf("=");
if (colonIndex == -1) {
return equalIndex;
}
if (equalIndex == -1) {
return colonIndex;
}
return Math.min(colonIndex, equalIndex);
}
/**
* Builder method to append another property to the underlying map of properties.
* @param name The property name
* @param value The property value
* @return the existing instance of {@link TestPropertyValues}
*/
public TestPropertyValues and(String name, String value) {
if (StringUtils.isEmpty(name) && StringUtils.isEmpty(value)) {
return this;
}
Assert.hasLength(name, "Name must not be empty");
this.properties.put(name, value);
return this;
private TestPropertyValues and(Stream<Pair> pairs) {
Map<String, Object> properties = new LinkedHashMap<>(this.properties);
pairs.filter(Objects::nonNull).forEach((pair) -> pair.addTo(properties));
return new TestPropertyValues(properties);
}
/**
@ -170,30 +146,21 @@ public final class TestPropertyValues {
return;
}
}
MapPropertySource source = (type.equals(Type.MAP)
? new MapPropertySource(name, this.properties)
: new SystemEnvironmentPropertySource(name, this.properties));
sources.addFirst(source);
}
/**
* Return a new empty {@link TestPropertyValues} instance.
* @return an empty instance
*/
public static TestPropertyValues empty() {
return of();
Map<String, Object> source = new LinkedHashMap<>(this.properties);
sources.addFirst((type.equals(Type.MAP) ? new MapPropertySource(name, source)
: new SystemEnvironmentPropertySource(name, source)));
}
/**
* Return a new {@link TestPropertyValues} with the underlying map populated with the
* given property pair.
* @param name the property name
* @param value the property value
* given property pairs. Name-value pairs can be specified with colon (":") or equals
* ("=") separators.
* @param pairs The key value pairs for properties that need to be added to the
* environment
* @return the new instance
*/
public static TestPropertyValues ofPair(String name, String value) {
return of().and(name, value);
public static TestPropertyValues of(String... pairs) {
return of(Stream.of(pairs));
}
/**
@ -206,9 +173,9 @@ public final class TestPropertyValues {
*/
public static TestPropertyValues of(Iterable<String> pairs) {
if (pairs == null) {
return of();
return empty();
}
return of(Streams.stream(pairs).toArray(String[]::new));
return of(Streams.stream(pairs));
}
/**
@ -219,8 +186,19 @@ public final class TestPropertyValues {
* environment
* @return the new instance
*/
public static TestPropertyValues of(String... pairs) {
return new TestPropertyValues(pairs);
public static TestPropertyValues of(Stream<String> pairs) {
if (pairs == null) {
return empty();
}
return empty().and(pairs.map(Pair::parse));
}
/**
* Return a new empty {@link TestPropertyValues} instance.
* @return an empty instance
*/
public static TestPropertyValues empty() {
return EMPTY;
}
/**
@ -250,6 +228,53 @@ public final class TestPropertyValues {
}
/**
* A single name value pair.
*/
public static class Pair {
private String name;
private String value;
public Pair(String name, String value) {
Assert.hasLength(name, "Name must not be empty");
this.name = name;
this.value = value;
}
public void addTo(Map<String, Object> properties) {
properties.put(this.name, this.value);
}
public static Pair parse(String pair) {
int index = getSeparatorIndex(pair);
String key = pair.substring(0, index > 0 ? index : pair.length());
String value = index > 0 ? pair.substring(index + 1) : "";
return of(key.trim(), value.trim());
}
private static int getSeparatorIndex(String pair) {
int colonIndex = pair.indexOf(":");
int equalIndex = pair.indexOf("=");
if (colonIndex == -1) {
return equalIndex;
}
if (equalIndex == -1) {
return colonIndex;
}
return Math.min(colonIndex, equalIndex);
}
private static Pair of(String name, String value) {
if (StringUtils.isEmpty(name) && StringUtils.isEmpty(value)) {
return null;
}
return new Pair(name, value);
}
}
/**
* Handler to apply and restore system properties.
*/
@ -278,7 +303,7 @@ public final class TestPropertyValues {
private String setOrClear(String name, String value) {
Assert.notNull(name, "Name must not be null");
if (value == null) {
if (StringUtils.isEmpty(value)) {
return (String) System.getProperties().remove(name);
}
return (String) System.getProperties().setProperty(name, value);

View File

@ -95,7 +95,7 @@ public abstract class AbstractApplicationContextRunnerTests<T extends AbstractAp
System.setProperty(key, "value");
try {
assertThat(System.getProperties().getProperty(key)).isEqualTo("value");
get().withSystemProperty(key, null).run((loaded) -> {
get().withSystemProperties(key + "=").run((loaded) -> {
assertThat(System.getProperties()).doesNotContainKey(key);
});
assertThat(System.getProperties().getProperty(key)).isEqualTo("value");

View File

@ -89,8 +89,8 @@ public class TestPropertyValuesTests {
@Test
public void andShouldChainAndAddSingleKeyValue() throws Exception {
TestPropertyValues.of("foo.bar=baz").and("hello.world", "hi")
.and("bling.blah", "bing").applyTo(this.environment, Type.MAP);
TestPropertyValues.of("foo.bar=baz").and("hello.world=hi").and("bling.blah=bing")
.applyTo(this.environment, Type.MAP);
assertThat(this.environment.getProperty("foo.bar")).isEqualTo("baz");
assertThat(this.environment.getProperty("hello.world")).isEqualTo("hi");
assertThat(this.environment.getProperty("bling.blah")).isEqualTo("bing");
@ -127,7 +127,7 @@ public class TestPropertyValuesTests {
throws Exception {
System.setProperty("foo", "bar1");
try {
TestPropertyValues.ofPair("foo", null).applyToSystemProperties(() -> {
TestPropertyValues.of("foo").applyToSystemProperties(() -> {
assertThat(System.getProperties()).doesNotContainKey("foo");
return null;
});