From c6f55ef46d4b959d5b243f3fe429ea349b7e5b9d Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 19 Jul 2017 08:57:40 -0700 Subject: [PATCH] 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 --- .../boot/test/util/TestPropertyValues.java | 133 ++++++++++++++++-- .../test/util/TestPropertyValuesTests.java | 44 ++++++ 2 files changed, 166 insertions(+), 11 deletions(-) diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/util/TestPropertyValues.java b/spring-boot-test/src/main/java/org/springframework/boot/test/util/TestPropertyValues.java index fcb27c7bfc..4b56091b92 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/util/TestPropertyValues.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/util/TestPropertyValues.java @@ -16,29 +16,36 @@ 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.concurrent.Callable; + +import com.google.common.collect.Streams; import org.springframework.boot.context.properties.source.ConfigurationPropertySources; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; import org.springframework.core.env.SystemEnvironmentPropertySource; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** - * Test utilities for adding properties to the environment. The type of - * {@link PropertySource} to be added can be specified by {@link Type}. + * Test utilities for adding properties. Properties can be applied to a Spring + * {@link Environment} or to the {@link System#getProperties() system environment}. * * @author Madhura Bhave + * @author Phillip Webb * @since 2.0.0 */ public final class TestPropertyValues { - private final Map properties = new HashMap<>(); + private final Map properties = new LinkedHashMap<>(); private TestPropertyValues(String[] pairs) { addProperties(pairs); @@ -46,13 +53,23 @@ public final class TestPropertyValues { private void addProperties(String[] pairs) { for (String pair : pairs) { - int index = getSeparatorIndex(pair); - String key = pair.substring(0, index > 0 ? index : pair.length()); - String value = index > 0 ? pair.substring(index + 1) : ""; - this.properties.put(key.trim(), value.trim()); + 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); + 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; + } + private int getSeparatorIndex(String pair) { int colonIndex = pair.indexOf(":"); int equalIndex = pair.indexOf("="); @@ -67,12 +84,16 @@ public final class TestPropertyValues { /** * 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 * @return the existing instance of {@link TestPropertyValues} */ - public TestPropertyValues and(String key, String value) { - this.properties.put(key, value); + 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; } @@ -120,6 +141,25 @@ public final class TestPropertyValues { 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 the result type + * @param call the call to make + * @return the result of the call + */ + public T applyToSystemProperties(Callable call) { + try (SystemPropertiesHandler handler = new SystemPropertiesHandler()) { + return call.call(); + } + catch (RuntimeException ex) { + throw ex; + } + catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + @SuppressWarnings("unchecked") private void addToSources(MutablePropertySources sources, Type type, String name) { if (sources.contains(name)) { @@ -136,6 +176,41 @@ public final class TestPropertyValues { 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 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 * 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 properties; + + private final Map previous; + + SystemPropertiesHandler() { + this.properties = new LinkedHashMap<>(TestPropertyValues.this.properties); + this.previous = apply(this.properties); + } + + private Map apply(Map properties) { + Map 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); + } + + } + } diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/util/TestPropertyValuesTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/util/TestPropertyValuesTests.java index 87cb6cdf8d..e5f7a238fe 100644 --- a/spring-boot-test/src/test/java/org/springframework/boot/test/util/TestPropertyValuesTests.java +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/util/TestPropertyValuesTests.java @@ -30,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link TestPropertyValues}. * * @author Madhura Bhave + * @author Phillip Webb */ public class TestPropertyValuesTests { @@ -95,4 +96,47 @@ public class TestPropertyValuesTests { 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"); + } + } + }