diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java index 0a002d40973..dc608162e6a 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -16,13 +16,8 @@ package org.springframework.test.context.support; -import java.io.IOException; -import java.io.StringReader; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Properties; import java.util.Set; import org.apache.commons.logging.Log; @@ -34,12 +29,8 @@ import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.GenericTypeResolver; import org.springframework.core.annotation.AnnotationAwareOrderComparator; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.PropertySource; import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.ResourcePropertySource; import org.springframework.test.context.ContextConfigurationAttributes; import org.springframework.test.context.ContextLoader; import org.springframework.test.context.MergedContextConfiguration; @@ -64,7 +55,6 @@ import org.springframework.util.ResourceUtils; * * @author Sam Brannen * @author Juergen Hoeller - * @author Dave Syer * @since 2.5 * @see #generateDefaultLocations * @see #getResourceSuffixes @@ -74,8 +64,6 @@ public abstract class AbstractContextLoader implements SmartContextLoader { private static final String[] EMPTY_STRING_ARRAY = new String[0]; - private static final String LINE_SEPARATOR = System.getProperty("line.separator"); - private static final Log logger = LogFactory.getLog(AbstractContextLoader.class); @@ -137,74 +125,11 @@ public abstract class AbstractContextLoader implements SmartContextLoader { */ protected void prepareContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) { context.getEnvironment().setActiveProfiles(mergedConfig.getActiveProfiles()); - addResourcePropertySourcesToEnvironment(context, mergedConfig); - addInlinedPropertiesToEnvironment(context, mergedConfig); + TestPropertySourceUtils.addResourcePropertySourcesToEnvironment(context, mergedConfig.getPropertySourceLocations()); + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context, mergedConfig.getPropertySourceProperties()); invokeApplicationContextInitializers(context, mergedConfig); } - /** - * @since 4.1 - */ - private void addResourcePropertySourcesToEnvironment(ConfigurableApplicationContext context, - MergedContextConfiguration mergedConfig) { - try { - ConfigurableEnvironment environment = context.getEnvironment(); - String[] locations = mergedConfig.getPropertySourceLocations(); - for (String location : locations) { - String resolvedLocation = environment.resolveRequiredPlaceholders(location); - Resource resource = context.getResource(resolvedLocation); - ResourcePropertySource ps = new ResourcePropertySource(resource); - environment.getPropertySources().addFirst(ps); - } - } - catch (IOException e) { - throw new IllegalStateException("Failed to add PropertySource to Environment", e); - } - } - - /** - * @since 4.1 - */ - private void addInlinedPropertiesToEnvironment(ConfigurableApplicationContext context, - MergedContextConfiguration mergedConfig) { - String[] keyValuePairs = mergedConfig.getPropertySourceProperties(); - if (!ObjectUtils.isEmpty(keyValuePairs)) { - String name = "test properties " + ObjectUtils.nullSafeToString(keyValuePairs); - MapPropertySource ps = new MapPropertySource(name, extractEnvironmentProperties(keyValuePairs)); - context.getEnvironment().getPropertySources().addFirst(ps); - } - } - - /** - * Extract environment properties from the supplied key/value pairs. - *

Parsing of the key/value pairs is achieved by converting all pairs - * into a single virtual properties file in memory and delegating - * to {@link Properties#load(java.io.Reader)} to parse that virtual file. - *

This code has been adapted from Spring Boot's - * {@link org.springframework.boot.test.SpringApplicationContextLoader SpringApplicationContextLoader}. - * @since 4.1 - */ - private Map extractEnvironmentProperties(String[] keyValuePairs) { - StringBuilder sb = new StringBuilder(); - for (String keyValuePair : keyValuePairs) { - sb.append(keyValuePair).append(LINE_SEPARATOR); - } - String content = sb.toString(); - Properties props = new Properties(); - try { - props.load(new StringReader(content)); - } - catch (IOException e) { - throw new IllegalStateException("Failed to load test environment properties from: " + content, e); - } - - Map properties = new HashMap(); - for (String name : props.stringPropertyNames()) { - properties.put(name, props.getProperty(name)); - } - return properties; - } - @SuppressWarnings("unchecked") private void invokeApplicationContextInitializers(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) { diff --git a/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java index c77ec1cb528..90b8002d24e 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -16,24 +16,39 @@ package org.springframework.test.context.support; +import java.io.IOException; +import java.io.StringReader; import java.util.ArrayList; import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.Properties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.ResourcePropertySource; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.util.TestContextResourceUtils; -import org.springframework.test.util.MetaAnnotationUtils.*; +import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import static org.springframework.test.util.MetaAnnotationUtils.*; /** - * Utility methods for working with {@link TestPropertySource @TestPropertySource}. + * Utility methods for working with {@link TestPropertySource @TestPropertySource} + * and adding test {@link PropertySource PropertySources} to the {@code Environment}. + * + *

Primarily intended for use within the framework. * * @author Sam Brannen * @since 4.1 @@ -131,4 +146,76 @@ abstract class TestPropertySourceUtils { return StringUtils.toStringArray(properties); } + /** + * @since 4.1.5 + */ + static void addResourcePropertySourcesToEnvironment(ConfigurableApplicationContext context, + String[] propertySourceLocations) { + try { + ConfigurableEnvironment environment = context.getEnvironment(); + String[] locations = propertySourceLocations; + for (String location : locations) { + String resolvedLocation = environment.resolveRequiredPlaceholders(location); + Resource resource = context.getResource(resolvedLocation); + ResourcePropertySource ps = new ResourcePropertySource(resource); + environment.getPropertySources().addFirst(ps); + } + } + catch (IOException e) { + throw new IllegalStateException("Failed to add PropertySource to Environment", e); + } + } + + /** + * @since 4.1.5 + */ + static void addInlinedPropertiesToEnvironment(ConfigurableApplicationContext context, + String[] propertySourceProperties) { + addInlinedPropertiesToEnvironment(context.getEnvironment(), propertySourceProperties); + } + + /** + * @since 4.1.5 + */ + static void addInlinedPropertiesToEnvironment(ConfigurableEnvironment environment, String[] propertySourceProperties) { + if (!ObjectUtils.isEmpty(propertySourceProperties)) { + String name = "test properties " + ObjectUtils.nullSafeToString(propertySourceProperties); + MapPropertySource ps = new MapPropertySource(name, extractEnvironmentProperties(propertySourceProperties)); + environment.getPropertySources().addFirst(ps); + } + } + + /** + * Extract environment properties from the supplied key/value pairs, + * preserving the ordering of property names in the returned map. + *

Parsing of the key/value pairs is achieved by converting all pairs + * into virtual properties files in memory and delegating to + * {@link Properties#load(java.io.Reader)} to parse each virtual file. + */ + private static Map extractEnvironmentProperties(String[] keyValuePairs) { + Map map = new LinkedHashMap(); + + Properties props = new Properties(); + for (String pair : keyValuePairs) { + if (!StringUtils.hasText(pair)) { + continue; + } + + try { + props.load(new StringReader(pair)); + } + catch (Exception e) { + throw new IllegalStateException("Failed to load test environment property from [" + pair + "].", e); + } + Assert.state(props.size() == 1, "Failed to load exactly one test environment property from [" + pair + "]."); + + for (String name : props.stringPropertyNames()) { + map.put(name, props.getProperty(name)); + } + props.clear(); + } + + return map; + } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/env/InlinedPropertiesTestPropertySourceTests.java b/spring-test/src/test/java/org/springframework/test/context/env/InlinedPropertiesTestPropertySourceTests.java index a1721e854a8..fa4a04ec177 100644 --- a/spring-test/src/test/java/org/springframework/test/context/env/InlinedPropertiesTestPropertySourceTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/env/InlinedPropertiesTestPropertySourceTests.java @@ -21,7 +21,10 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.Environment; +import org.springframework.core.env.PropertySource; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -29,28 +32,31 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import static org.junit.Assert.*; /** - * Integration tests for {@link TestPropertySource @TestPropertySource} - * support with an inlined properties. + * Integration tests for {@link TestPropertySource @TestPropertySource} support with + * inlined properties. * * @author Sam Brannen * @since 4.1 */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration -@TestPropertySource(properties = { "foo = bar", "baz quux", "enigma: 42", - "x.y.z = a=b=c", "server.url = http://example.com", "key.value.1: key=value", - "key.value.2 key=value", "key.value.3 key:value" }) +@TestPropertySource(properties = { "", "foo = bar", "baz quux", "enigma: 42", "x.y.z = a=b=c", + "server.url = http://example.com", "key.value.1: key=value", "key.value.2 key=value", "key.value.3 key:value" }) public class InlinedPropertiesTestPropertySourceTests { @Autowired - protected Environment env; + private Environment env; @Test - public void verifyPropertiesAreAvailableInEnvironment() { + public void propertiesAreAvailableInEnvironment() { + + // Simple key/value pairs assertEquals("bar", env.getProperty("foo")); assertEquals("quux", env.getProperty("baz")); assertEquals(42, env.getProperty("enigma", Integer.class).intValue()); + + // Values containing key/value delimiters (":", "=", " ") assertEquals("a=b=c", env.getProperty("x.y.z")); assertEquals("http://example.com", env.getProperty("server.url")); assertEquals("key=value", env.getProperty("key.value.1")); @@ -58,6 +64,28 @@ public class InlinedPropertiesTestPropertySourceTests { assertEquals("key:value", env.getProperty("key.value.3")); } + @Test + @SuppressWarnings("rawtypes") + public void propertyNameOrderingIsPreservedInEnvironment() { + String[] propertyNames = null; + + ConfigurableEnvironment configurableEnvironment = (ConfigurableEnvironment) env; + for (PropertySource propertySource : configurableEnvironment.getPropertySources()) { + if (propertySource instanceof EnumerablePropertySource) { + EnumerablePropertySource eps = (EnumerablePropertySource) propertySource; + if (eps.getName().startsWith("test properties")) { + propertyNames = eps.getPropertyNames(); + break; + } + } + } + + final String[] expectedPropertyNames = new String[] { "foo", "baz", "enigma", "x.y.z", "server.url", + "key.value.1", "key.value.2", "key.value.3" }; + + assertArrayEquals(expectedPropertyNames, propertyNames); + } + // ------------------------------------------------------------------- diff --git a/spring-test/src/test/java/org/springframework/test/context/support/TestPropertySourceUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/support/TestPropertySourceUtilsTests.java index 8d4769ec732..9a300628caa 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/TestPropertySourceUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/TestPropertySourceUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -16,10 +16,16 @@ package org.springframework.test.context.support; +import java.util.Map; + import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.mock.env.MockEnvironment; +import org.springframework.mock.env.MockPropertySource; import org.springframework.test.context.TestPropertySource; import static org.hamcrest.CoreMatchers.*; @@ -113,6 +119,41 @@ public class TestPropertySourceUtilsTests { new String[] { "classpath:/baz.properties" }, new String[] { "key = value" }); } + /** + * @since 4.1.5 + */ + @Test + @SuppressWarnings("rawtypes") + public void emptyInlinedProperty() { + ConfigurableEnvironment environment = new MockEnvironment(); + MutablePropertySources propertySources = environment.getPropertySources(); + propertySources.remove(MockPropertySource.MOCK_PROPERTIES_PROPERTY_SOURCE_NAME); + assertEquals(0, propertySources.size()); + addInlinedPropertiesToEnvironment(environment, new String[] { " " }); + assertEquals(1, propertySources.size()); + assertEquals(0, ((Map) propertySources.iterator().next().getSource()).size()); + } + + /** + * @since 4.1.5 + */ + @Test + public void inlinedPropertyWithMalformedUnicodeInValue() { + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("Failed to load test environment property"); + addInlinedPropertiesToEnvironment(new MockEnvironment(), new String[] { "key = \\uZZZZ" }); + } + + /** + * @since 4.1.5 + */ + @Test + public void inlinedPropertyWithMultipleKeyValuePairs() { + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("Failed to load exactly one test environment property"); + addInlinedPropertiesToEnvironment(new MockEnvironment(), new String[] { "a=b\nx=y" }); + } + // -------------------------------------------------------------------