Add system property support to TestPropertyValues

Update `TestPropertyValues` so that it can also be used to update
system properties.  Properties are set before the call is made and
restored to their previous value afterwards.

Fixes gh-9792
This commit is contained in:
Phillip Webb 2017-07-19 08:57:40 -07:00
parent 2f0f25f5ad
commit c6f55ef46d
2 changed files with 166 additions and 11 deletions

View File

@ -16,29 +16,36 @@
package org.springframework.boot.test.util; package org.springframework.boot.test.util;
import java.util.HashMap; import java.io.Closeable;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Callable;
import com.google.common.collect.Streams;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources; import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
import org.springframework.core.env.SystemEnvironmentPropertySource; import org.springframework.core.env.SystemEnvironmentPropertySource;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/** /**
* Test utilities for adding properties to the environment. The type of * Test utilities for adding properties. Properties can be applied to a Spring
* {@link PropertySource} to be added can be specified by {@link Type}. * {@link Environment} or to the {@link System#getProperties() system environment}.
* *
* @author Madhura Bhave * @author Madhura Bhave
* @author Phillip Webb
* @since 2.0.0 * @since 2.0.0
*/ */
public final class TestPropertyValues { public final class TestPropertyValues {
private final Map<String, Object> properties = new HashMap<>(); private final Map<String, Object> properties = new LinkedHashMap<>();
private TestPropertyValues(String[] pairs) { private TestPropertyValues(String[] pairs) {
addProperties(pairs); addProperties(pairs);
@ -46,11 +53,21 @@ public final class TestPropertyValues {
private void addProperties(String[] pairs) { private void addProperties(String[] pairs) {
for (String pair : pairs) { for (String pair : pairs) {
and(pair);
}
}
/**
* 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}
*/
public TestPropertyValues and(String pair) {
int index = getSeparatorIndex(pair); int index = getSeparatorIndex(pair);
String key = pair.substring(0, index > 0 ? index : pair.length()); String key = pair.substring(0, index > 0 ? index : pair.length());
String value = index > 0 ? pair.substring(index + 1) : ""; String value = index > 0 ? pair.substring(index + 1) : "";
this.properties.put(key.trim(), value.trim()); and(key.trim(), value.trim());
} return this;
} }
private int getSeparatorIndex(String pair) { private int getSeparatorIndex(String pair) {
@ -67,12 +84,16 @@ public final class TestPropertyValues {
/** /**
* Builder method to append another property to the underlying map of properties. * Builder method to append another property to the underlying map of properties.
* @param key The property key * @param name The property name
* @param value The property value * @param value The property value
* @return the existing instance of {@link TestPropertyValues} * @return the existing instance of {@link TestPropertyValues}
*/ */
public TestPropertyValues and(String key, String value) { public TestPropertyValues and(String name, String value) {
this.properties.put(key, 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; return this;
} }
@ -120,6 +141,25 @@ public final class TestPropertyValues {
ConfigurationPropertySources.attach(environment); ConfigurationPropertySources.attach(environment);
} }
/**
* Add the properties to the {@link System#getProperties() system properties} for the
* duration of the {@code call}, restoring previous values then the call completes.
* @param <T> the result type
* @param call the call to make
* @return the result of the call
*/
public <T> T applyToSystemProperties(Callable<T> call) {
try (SystemPropertiesHandler handler = new SystemPropertiesHandler()) {
return call.call();
}
catch (RuntimeException ex) {
throw ex;
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void addToSources(MutablePropertySources sources, Type type, String name) { private void addToSources(MutablePropertySources sources, Type type, String name) {
if (sources.contains(name)) { if (sources.contains(name)) {
@ -136,6 +176,41 @@ public final class TestPropertyValues {
sources.addFirst(source); sources.addFirst(source);
} }
/**
* Return a new empty {@link TestPropertyValues} instance.
* @return an empty instance
*/
public static TestPropertyValues empty() {
return of();
}
/**
* 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
* @return the new instance
*/
public static TestPropertyValues ofPair(String name, String value) {
return of().and(name, value);
}
/**
* Return a new {@link TestPropertyValues} with the underlying map populated with the
* 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 of(Iterable<String> pairs) {
if (pairs == null) {
return of();
}
return of(Streams.stream(pairs).toArray(String[]::new));
}
/** /**
* Return a new {@link TestPropertyValues} with the underlying map populated with the * Return a new {@link TestPropertyValues} with the underlying map populated with the
* given property pairs. Name-value pairs can be specified with colon (":") or equals * given property pairs. Name-value pairs can be specified with colon (":") or equals
@ -175,4 +250,40 @@ public final class TestPropertyValues {
} }
/**
* Handler to apply and restore system properties.
*/
private class SystemPropertiesHandler implements Closeable {
private final Map<String, Object> properties;
private final Map<String, String> previous;
SystemPropertiesHandler() {
this.properties = new LinkedHashMap<>(TestPropertyValues.this.properties);
this.previous = apply(this.properties);
}
private Map<String, String> apply(Map<String, ?> properties) {
Map<String, String> previous = new LinkedHashMap<>();
properties.forEach((name, value) -> previous.put(name,
setOrClear(name, (String) value)));
return previous;
}
@Override
public void close() {
this.previous.forEach(this::setOrClear);
};
private String setOrClear(String name, String value) {
Assert.notNull(name, "Name must not be null");
if (value == null) {
return (String) System.getProperties().remove(name);
}
return (String) System.getProperties().setProperty(name, value);
}
}
} }

View File

@ -30,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link TestPropertyValues}. * Tests for {@link TestPropertyValues}.
* *
* @author Madhura Bhave * @author Madhura Bhave
* @author Phillip Webb
*/ */
public class TestPropertyValuesTests { public class TestPropertyValuesTests {
@ -95,4 +96,47 @@ public class TestPropertyValuesTests {
assertThat(this.environment.getProperty("bling.blah")).isEqualTo("bing"); assertThat(this.environment.getProperty("bling.blah")).isEqualTo("bing");
} }
@Test
public void applyToSystemPropertiesShouldSetSystemProperties() throws Exception {
TestPropertyValues.of("foo=bar").applyToSystemProperties(() -> {
assertThat(System.getProperty("foo")).isEqualTo("bar");
return null;
});
}
@Test
public void applyToSystemPropertiesShouldRestoreSystemProperties() throws Exception {
System.setProperty("foo", "bar1");
System.clearProperty("baz");
try {
TestPropertyValues.of("foo=bar2", "baz=bing").applyToSystemProperties(() -> {
assertThat(System.getProperty("foo")).isEqualTo("bar2");
assertThat(System.getProperty("baz")).isEqualTo("bing");
return null;
});
assertThat(System.getProperty("foo")).isEqualTo("bar1");
assertThat(System.getProperties()).doesNotContainKey("baz");
}
finally {
System.clearProperty("foo");
}
}
@Test
public void applyToSystemPropertiesWhenValueIsNullShouldRemoveProperty()
throws Exception {
System.setProperty("foo", "bar1");
try {
TestPropertyValues.ofPair("foo", null).applyToSystemProperties(() -> {
assertThat(System.getProperties()).doesNotContainKey("foo");
return null;
});
assertThat(System.getProperty("foo")).isEqualTo("bar1");
assertThat(System.getProperties()).doesNotContainKey("baz");
}
finally {
System.clearProperty("foo");
}
}
} }