From c675d9c76e08f97a0e9e9c36a98b229c017f62f4 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Tue, 21 May 2013 09:26:35 +0100 Subject: [PATCH] [bs-22] Add tests for Yaml processing * Gap in logic identified, so DocumentMatcher refactored to return an enum [#48127729] [bs-22] Add missing unit tests --- .../batch/JobExecutionExitCodeGenerator.java | 4 +- .../batch/JobLauncherCommandLineRunner.java | 17 +- .../bootstrap/config/YamlProcessor.java | 74 +++--- ...nfigFileApplicationContextInitializer.java | 20 +- .../config/YamlMapFactoryBeanTests.java | 90 ++++++++ .../YamlPropertiesFactoryBeanTests.java | 212 ++++++++++++++++++ .../src/test/resources/logback-test.xml | 1 + 7 files changed, 355 insertions(+), 63 deletions(-) create mode 100644 spring-bootstrap/src/test/java/org/springframework/bootstrap/config/YamlMapFactoryBeanTests.java create mode 100644 spring-bootstrap/src/test/java/org/springframework/bootstrap/config/YamlPropertiesFactoryBeanTests.java diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/JobExecutionExitCodeGenerator.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/JobExecutionExitCodeGenerator.java index 829684dd59..52312093eb 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/JobExecutionExitCodeGenerator.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/JobExecutionExitCodeGenerator.java @@ -39,8 +39,8 @@ public class JobExecutionExitCodeGenerator implements @Override public int getExitCode() { for (JobExecution execution : this.executions) { - if (execution.getStatus().isUnsuccessful()) { - return 2; + if (execution.getStatus().ordinal() > 0) { + return execution.getStatus().ordinal(); } } return 0; diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/JobLauncherCommandLineRunner.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/JobLauncherCommandLineRunner.java index 7c48167cde..cd09e2300d 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/JobLauncherCommandLineRunner.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/JobLauncherCommandLineRunner.java @@ -16,6 +16,8 @@ package org.springframework.bootstrap.autoconfigure.batch; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.Properties; import org.apache.commons.logging.Log; @@ -34,7 +36,6 @@ import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @Component -// FIXME: what to do with more than one Job? public class JobLauncherCommandLineRunner implements CommandLineRunner, ApplicationEventPublisherAware { @@ -46,8 +47,8 @@ public class JobLauncherCommandLineRunner implements CommandLineRunner, @Autowired private JobLauncher jobLauncher; - @Autowired - private Job job; + @Autowired(required = false) + private Collection jobs = Collections.emptySet(); private ApplicationEventPublisher publisher; @@ -63,10 +64,12 @@ public class JobLauncherCommandLineRunner implements CommandLineRunner, protected void launchJobFromProperties(Properties properties) throws JobExecutionException { - JobExecution execution = this.jobLauncher.run(this.job, - this.converter.getJobParameters(properties)); - if (this.publisher != null) { - this.publisher.publishEvent(new JobExecutionEvent(execution)); + for (Job job : this.jobs) { + JobExecution execution = this.jobLauncher.run(job, + this.converter.getJobParameters(properties)); + if (this.publisher != null) { + this.publisher.publishEvent(new JobExecutionEvent(execution)); + } } } } \ No newline at end of file diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/config/YamlProcessor.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/config/YamlProcessor.java index fd34cfd9ea..8bd347e444 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/config/YamlProcessor.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/config/YamlProcessor.java @@ -44,7 +44,7 @@ public class YamlProcessor { } public interface DocumentMatcher { - boolean matches(Properties properties); + MatchStatus matches(Properties properties); } private static final Log logger = LogFactory.getLog(YamlProcessor.class); @@ -53,6 +53,22 @@ public class YamlProcessor { OVERRIDE, OVERRIDE_AND_IGNORE, FIRST_FOUND } + public static enum MatchStatus { + + /** + * A match was found. + */ + FOUND, + /** + * A match was not found. + */ + NOT_FOUND, + /** + * Not enough information to decide. + */ + ABSTAIN + } + private ResolutionMethod resolutionMethod = ResolutionMethod.OVERRIDE; private Resource[] resources = new Resource[0]; @@ -93,8 +109,9 @@ public class YamlProcessor { } /** - * Flag indicating that a document that contains none of the keys in the - * {@link #setDocumentMatchers(List) document matchers} will nevertheless match. + * Flag indicating that a document for which all the + * {@link #setDocumentMatchers(List) document matchers} abstain will nevertheless + * match. * * @param matchDefault the flag to set (default true) */ @@ -185,15 +202,19 @@ public class YamlProcessor { callback.process(properties, map); } else { boolean valueFound = false; + MatchStatus result = MatchStatus.ABSTAIN; for (DocumentMatcher matcher : this.documentMatchers) { - if (matcher.matches(properties)) { + MatchStatus match = matcher.matches(properties); + result = match.ordinal() < result.ordinal() ? match : result; + if (match == MatchStatus.FOUND) { + logger.debug("Matched document with document matcher: " + properties); callback.process(properties, map); valueFound = true; // No need to check for more matches break; } } - if (!valueFound && this.matchDefault) { + if (result == MatchStatus.ABSTAIN && this.matchDefault) { logger.debug("Matched document with default matcher: " + map); callback.process(properties, map); } else if (!valueFound) { @@ -240,41 +261,6 @@ public class YamlProcessor { } } - /** - * Matches a document containing a given key and where the value of that key matches - * one of the given values (interpreted as a regex). - * - * @author Dave Syer - * - */ - public static class SimpleDocumentMatcher implements DocumentMatcher { - - private String key; - - private String[] patterns; - - public SimpleDocumentMatcher(final String key, final String... patterns) { - this.key = key; - this.patterns = patterns; - - } - - @Override - public boolean matches(Properties properties) { - if (!properties.containsKey(this.key)) { - return false; - } - String value = properties.getProperty(this.key); - for (String pattern : this.patterns) { - if (value == null || value.matches(pattern)) { - return true; - } - } - return false; - } - - } - /** * Matches a document containing a given key and where the value of that key is an * array containing one of the given values, or where one of the values matches one of @@ -296,20 +282,20 @@ public class YamlProcessor { } @Override - public boolean matches(Properties properties) { + public MatchStatus matches(Properties properties) { if (!properties.containsKey(this.key)) { - return false; + return MatchStatus.ABSTAIN; } Set values = StringUtils.commaDelimitedListToSet(properties .getProperty(this.key)); for (String pattern : this.patterns) { for (String value : values) { if (value.matches(pattern)) { - return true; + return MatchStatus.FOUND; } } } - return false; + return MatchStatus.NOT_FOUND; } } diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/initializer/ConfigFileApplicationContextInitializer.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/initializer/ConfigFileApplicationContextInitializer.java index e522ee1ca4..bd1754fac0 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/initializer/ConfigFileApplicationContextInitializer.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/initializer/ConfigFileApplicationContextInitializer.java @@ -24,6 +24,7 @@ import java.util.Set; import org.springframework.bootstrap.config.YamlProcessor.ArrayDocumentMatcher; import org.springframework.bootstrap.config.YamlProcessor.DocumentMatcher; +import org.springframework.bootstrap.config.YamlProcessor.MatchStatus; import org.springframework.bootstrap.config.YamlPropertiesFactoryBean; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; @@ -226,21 +227,20 @@ public class ConfigFileApplicationContextInitializer implements List matchers = new ArrayList(); matchers.add(new DocumentMatcher() { @Override - public boolean matches(Properties properties) { + public MatchStatus matches(Properties properties) { String[] profiles = applicationContext.getEnvironment() .getActiveProfiles(); - if (profiles.length > 0) { - return new ArrayDocumentMatcher("spring.profiles", profiles) - .matches(properties); - } else { - return properties.getProperty("spring.profiles", "NONE") - .contains("default"); + if (profiles.length == 0) { + profiles = new String[] { "default" }; } + return new ArrayDocumentMatcher("spring.profiles", profiles) + .matches(properties); + } }); matchers.add(new DocumentMatcher() { @Override - public boolean matches(Properties properties) { + public MatchStatus matches(Properties properties) { if (!properties.containsKey("spring.profiles")) { Set profiles = StringUtils .commaDelimitedListToSet(properties.getProperty( @@ -250,9 +250,9 @@ public class ConfigFileApplicationContextInitializer implements applicationContext.getEnvironment().addActiveProfile(profile); } // matches default profile - return true; + return MatchStatus.FOUND; } else { - return false; + return MatchStatus.NOT_FOUND; } } }); diff --git a/spring-bootstrap/src/test/java/org/springframework/bootstrap/config/YamlMapFactoryBeanTests.java b/spring-bootstrap/src/test/java/org/springframework/bootstrap/config/YamlMapFactoryBeanTests.java new file mode 100644 index 0000000000..b9d66b5349 --- /dev/null +++ b/spring-bootstrap/src/test/java/org/springframework/bootstrap/config/YamlMapFactoryBeanTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2012-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.bootstrap.config; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +import org.junit.Test; +import org.springframework.bootstrap.config.YamlProcessor.ResolutionMethod; +import org.springframework.core.io.AbstractResource; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; + +import static org.junit.Assert.assertEquals; + +/** + * @author Dave Syer + * + */ +public class YamlMapFactoryBeanTests { + + private YamlMapFactoryBean factory = new YamlMapFactoryBean(); + + @Test + public void testSetIgnoreResourceNotFound() throws Exception { + this.factory + .setResolutionMethod(YamlMapFactoryBean.ResolutionMethod.OVERRIDE_AND_IGNORE); + this.factory.setResources(new FileSystemResource[] { new FileSystemResource( + "non-exsitent-file.yml") }); + assertEquals(0, this.factory.getObject().size()); + } + + @Test(expected = IllegalStateException.class) + public void testSetBarfOnResourceNotFound() throws Exception { + this.factory.setResources(new FileSystemResource[] { new FileSystemResource( + "non-exsitent-file.yml") }); + assertEquals(0, this.factory.getObject().size()); + } + + @Test + public void testGetObject() throws Exception { + this.factory.setResources(new ByteArrayResource[] { new ByteArrayResource( + "foo: bar".getBytes()) }); + assertEquals(1, this.factory.getObject().size()); + } + + @SuppressWarnings("unchecked") + @Test + public void testOverrideAndremoveDefaults() throws Exception { + this.factory.setResources(new ByteArrayResource[] { + new ByteArrayResource("foo:\n bar: spam".getBytes()), + new ByteArrayResource("foo:\n spam: bar".getBytes()) }); + assertEquals(1, this.factory.getObject().size()); + assertEquals(2, + ((Map) this.factory.getObject().get("foo")).size()); + } + + @Test + public void testFirstFound() throws Exception { + this.factory.setResolutionMethod(ResolutionMethod.FIRST_FOUND); + this.factory.setResources(new Resource[] { new AbstractResource() { + @Override + public String getDescription() { + return "non-existent"; + } + + @Override + public InputStream getInputStream() throws IOException { + throw new IOException("planned"); + } + }, new ByteArrayResource("foo:\n spam: bar".getBytes()) }); + assertEquals(1, this.factory.getObject().size()); + } + +} diff --git a/spring-bootstrap/src/test/java/org/springframework/bootstrap/config/YamlPropertiesFactoryBeanTests.java b/spring-bootstrap/src/test/java/org/springframework/bootstrap/config/YamlPropertiesFactoryBeanTests.java new file mode 100644 index 0000000000..de92a189fd --- /dev/null +++ b/spring-bootstrap/src/test/java/org/springframework/bootstrap/config/YamlPropertiesFactoryBeanTests.java @@ -0,0 +1,212 @@ +/* + * Copyright 2012-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.bootstrap.config; + +import java.util.Arrays; +import java.util.Map; +import java.util.Properties; + +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.bootstrap.config.YamlProcessor.DocumentMatcher; +import org.springframework.bootstrap.config.YamlProcessor.MatchStatus; +import org.springframework.bootstrap.config.YamlProcessor.ResolutionMethod; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.yaml.snakeyaml.Yaml; + +import static org.junit.Assert.assertEquals; + +/** + * @author Dave Syer + * + */ +public class YamlPropertiesFactoryBeanTests { + + @Test + public void testLoadResource() throws Exception { + YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); + factory.setResources(new Resource[] { new ByteArrayResource( + "foo: bar\nspam:\n foo: baz".getBytes()) }); + Properties properties = factory.getObject(); + assertEquals("bar", properties.get("foo")); + assertEquals("baz", properties.get("spam.foo")); + } + + @Test + public void testLoadResourcesWithOverride() throws Exception { + YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); + factory.setResources(new Resource[] { + new ByteArrayResource("foo: bar\nspam:\n foo: baz".getBytes()), + new ByteArrayResource("foo:\n bar: spam".getBytes()) }); + Properties properties = factory.getObject(); + assertEquals("bar", properties.get("foo")); + assertEquals("baz", properties.get("spam.foo")); + assertEquals("spam", properties.get("foo.bar")); + } + + @Test + @Ignore + // We can't fail on duplicate keys because the Map is created by the YAML library + public void testLoadResourcesWithInternalOverride() throws Exception { + YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); + factory.setResources(new Resource[] { new ByteArrayResource( + "foo: bar\nspam:\n foo: baz\nfoo: bucket".getBytes()) }); + Properties properties = factory.getObject(); + assertEquals("bar", properties.get("foo")); + } + + @Test + @Ignore + // We can't fail on duplicate keys because the Map is created by the YAML library + public void testLoadResourcesWithNestedInternalOverride() throws Exception { + YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); + factory.setResources(new Resource[] { new ByteArrayResource( + "foo:\n bar: spam\n foo: baz\nbreak: it\nfoo: bucket".getBytes()) }); + Properties properties = factory.getObject(); + assertEquals("spam", properties.get("foo.bar")); + } + + @Test + public void testLoadResourceWithMultipleDocuments() throws Exception { + YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); + factory.setResources(new Resource[] { new ByteArrayResource( + "foo: bar\nspam: baz\n---\nfoo: bag".getBytes()) }); + Properties properties = factory.getObject(); + assertEquals("bag", properties.get("foo")); + assertEquals("baz", properties.get("spam")); + } + + @Test + public void testLoadResourceWithSelectedDocuments() throws Exception { + YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); + factory.setResources(new Resource[] { new ByteArrayResource( + "foo: bar\nspam: baz\n---\nfoo: bag\nspam: bad".getBytes()) }); + factory.setDocumentMatchers(Arrays + . asList(new DocumentMatcher() { + @Override + public MatchStatus matches(Properties properties) { + return "bag".equals(properties.getProperty("foo")) ? MatchStatus.FOUND + : MatchStatus.NOT_FOUND; + } + })); + Properties properties = factory.getObject(); + assertEquals("bag", properties.get("foo")); + assertEquals("bad", properties.get("spam")); + } + + @Test + public void testLoadResourceWithDefaultMatch() throws Exception { + YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); + factory.setMatchDefault(true); + factory.setResources(new Resource[] { new ByteArrayResource( + "one: two\n---\nfoo: bar\nspam: baz\n---\nfoo: bag\nspam: bad".getBytes()) }); + factory.setDocumentMatchers(Arrays + . asList(new DocumentMatcher() { + @Override + public MatchStatus matches(Properties properties) { + if (!properties.containsKey("foo")) { + return MatchStatus.ABSTAIN; + } + return "bag".equals(properties.getProperty("foo")) ? MatchStatus.FOUND + : MatchStatus.NOT_FOUND; + } + })); + Properties properties = factory.getObject(); + assertEquals("bag", properties.get("foo")); + assertEquals("bad", properties.get("spam")); + assertEquals("two", properties.get("one")); + } + + @Test + public void testLoadResourceWithDefaultMatchSkippingMissedMatch() throws Exception { + YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); + factory.setMatchDefault(true); + factory.setResources(new Resource[] { new ByteArrayResource( + "one: two\n---\nfoo: bag\nspam: bad\n---\nfoo: bar\nspam: baz".getBytes()) }); + factory.setDocumentMatchers(Arrays + . asList(new DocumentMatcher() { + @Override + public MatchStatus matches(Properties properties) { + if (!properties.containsKey("foo")) { + return MatchStatus.ABSTAIN; + } + return "bag".equals(properties.getProperty("foo")) ? MatchStatus.FOUND + : MatchStatus.NOT_FOUND; + } + })); + Properties properties = factory.getObject(); + assertEquals("bag", properties.get("foo")); + assertEquals("bad", properties.get("spam")); + assertEquals("two", properties.get("one")); + } + + @Test + public void testLoadNonExistentResource() throws Exception { + YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); + factory.setResolutionMethod(ResolutionMethod.OVERRIDE_AND_IGNORE); + factory.setResources(new Resource[] { new ClassPathResource("no-such-file.yml") }); + Properties properties = factory.getObject(); + assertEquals(0, properties.size()); + } + + @Test + public void testLoadNull() throws Exception { + YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); + factory.setResources(new Resource[] { new ByteArrayResource("foo: bar\nspam:" + .getBytes()) }); + Properties properties = factory.getObject(); + assertEquals("bar", properties.get("foo")); + assertEquals("", properties.get("spam")); + } + + @Test + public void testLoadArrayOfString() throws Exception { + YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); + factory.setResources(new Resource[] { new ByteArrayResource("foo:\n- bar\n- baz" + .getBytes()) }); + Properties properties = factory.getObject(); + assertEquals("bar", properties.get("foo[0]")); + assertEquals("baz", properties.get("foo[1]")); + assertEquals("bar,baz", properties.get("foo")); + } + + @Test + public void testLoadArrayOfObject() throws Exception { + YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); + factory.setResources(new Resource[] { new ByteArrayResource( + "foo:\n- bar:\n spam: crap\n- baz\n- one: two\n three: four" + .getBytes()) }); + Properties properties = factory.getObject(); + // System.err.println(properties); + assertEquals("crap", properties.get("foo[0].bar.spam")); + assertEquals("baz", properties.get("foo[1]")); + assertEquals("two", properties.get("foo[2].one")); + assertEquals("four", properties.get("foo[2].three")); + assertEquals("{bar={spam=crap}},baz,{one=two, three=four}", properties.get("foo")); + } + + @SuppressWarnings("unchecked") + @Test + public void testYaml() { + Yaml yaml = new Yaml(); + Map map = yaml.loadAs("foo: bar\nspam:\n foo: baz", Map.class); + assertEquals("bar", map.get("foo")); + assertEquals("baz", ((Map) map.get("spam")).get("foo")); + } + +} diff --git a/spring-bootstrap/src/test/resources/logback-test.xml b/spring-bootstrap/src/test/resources/logback-test.xml index b3dd4319cf..465daf8a1c 100644 --- a/spring-bootstrap/src/test/resources/logback-test.xml +++ b/spring-bootstrap/src/test/resources/logback-test.xml @@ -8,6 +8,7 @@ +