From 38f0cf1ed2d53d6772e3dbe352e581962f9bad9c Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 24 Apr 2013 16:09:17 +0100 Subject: [PATCH] [bs-62] Add integration tests for CLI samples [Fixes #48658503] --- spring-bootstrap-cli/samples/app.groovy | 10 +- spring-bootstrap-cli/samples/service.groovy | 16 +++ spring-bootstrap-cli/samples/web.groovy | 1 - .../bootstrap/cli/RunCommand.java | 19 ++- .../cli/compiler/DependencyCustomizer.java | 110 +++++++++++++++++- .../SpringBatchCompilerAutoConfiguration.java | 2 +- ...ingBootstrapCompilerAutoConfiguration.java | 18 ++- .../SpringMvcCompilerAutoConfiguration.java | 4 +- .../bootstrap/cli/runner/BootstrapRunner.java | 17 ++- .../bootstrap/cli/SampleIntegrationTests.java | 76 ++++++++++++ .../src/test/resources/log4j.properties | 7 ++ .../spring-bootstrap-trad-sample/pom.xml | 2 +- .../service/ManagementAutoConfiguration.java | 3 +- .../annotation/ExpressionCondition.java | 13 +++ 14 files changed, 265 insertions(+), 33 deletions(-) create mode 100644 spring-bootstrap-cli/samples/service.groovy create mode 100644 spring-bootstrap-cli/src/test/java/org/springframework/bootstrap/cli/SampleIntegrationTests.java create mode 100644 spring-bootstrap-cli/src/test/resources/log4j.properties diff --git a/spring-bootstrap-cli/samples/app.groovy b/spring-bootstrap-cli/samples/app.groovy index 9d2864a4d66..6db47574af3 100644 --- a/spring-bootstrap-cli/samples/app.groovy +++ b/spring-bootstrap-cli/samples/app.groovy @@ -1,12 +1,6 @@ package org.test -@GrabResolver(name='spring-milestone', root='http://repo.springframework.org/milestone') -@GrabResolver(name='spring-snapshot', root='http://repo.springframework.org/snapshot') -@Grab("org.springframework.bootstrap:spring-bootstrap:0.0.1-SNAPSHOT") -@Grab("org.springframework:spring-context:4.0.0.BOOTSTRAP-SNAPSHOT") - -@org.springframework.bootstrap.context.annotation.EnableAutoConfiguration -@org.springframework.stereotype.Component +@Component class Example implements org.springframework.bootstrap.CommandLineRunner { @org.springframework.beans.factory.annotation.Autowired @@ -18,7 +12,7 @@ class Example implements org.springframework.bootstrap.CommandLineRunner { } -@org.springframework.stereotype.Service +@Service class MyService { public String sayWorld() { diff --git a/spring-bootstrap-cli/samples/service.groovy b/spring-bootstrap-cli/samples/service.groovy new file mode 100644 index 00000000000..218af45fcb1 --- /dev/null +++ b/spring-bootstrap-cli/samples/service.groovy @@ -0,0 +1,16 @@ +package org.test + +@GrabResolver(name='spring-snapshot', root='http://repo.springframework.org/snapshot') +@Grab("org.springframework.bootstrap:spring-bootstrap-service:0.0.1-SNAPSHOT") + +@Controller +class SampleController { + + @RequestMapping("/") + @ResponseBody + public def hello() { + [message: "Hello World!"] + } +} + + diff --git a/spring-bootstrap-cli/samples/web.groovy b/spring-bootstrap-cli/samples/web.groovy index 041e5f60f08..50535186f41 100644 --- a/spring-bootstrap-cli/samples/web.groovy +++ b/spring-bootstrap-cli/samples/web.groovy @@ -1,4 +1,3 @@ -@org.springframework.bootstrap.context.annotation.EnableAutoConfiguration @Controller class Example { diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/RunCommand.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/RunCommand.java index 32c426760b7..e96125b952e 100644 --- a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/RunCommand.java +++ b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/RunCommand.java @@ -48,7 +48,9 @@ public class RunCommand extends OptionParsingCommand { private OptionSpec verboseOption; - private OptionSpec quiteOption; + private OptionSpec quietOption; + + private BootstrapRunner runner; public RunCommand() { super("run", "Run a spring groovy script"); @@ -59,6 +61,12 @@ public class RunCommand extends OptionParsingCommand { return "[options] "; } + public void stop() { + if (this.runner != null) { + this.runner.stop(); + } + } + @Override protected OptionParser createOptionParser() { OptionParser parser = new OptionParser(); @@ -71,7 +79,7 @@ public class RunCommand extends OptionParsingCommand { this.noGuessDependenciesOption = parser.accepts("no-guess-dependencies", "Do not attempt to guess dependencies"); this.verboseOption = parser.acceptsAll(asList("verbose", "v"), "Verbose logging"); - this.quiteOption = parser.acceptsAll(asList("quiet", "q"), "Quiet logging"); + this.quietOption = parser.acceptsAll(asList("quiet", "q"), "Quiet logging"); return parser; } @@ -87,8 +95,9 @@ public class RunCommand extends OptionParsingCommand { BootstrapRunnerConfiguration configuration = new BootstrapRunnerConfigurationAdapter( options); - new BootstrapRunner(configuration, file, args.toArray(new String[args.size()])) - .compileAndRun(); + this.runner = new BootstrapRunner(configuration, file, + args.toArray(new String[args.size()])); + this.runner.compileAndRun(); } private File getFileArgument(List nonOptionArguments) { @@ -136,7 +145,7 @@ public class RunCommand extends OptionParsingCommand { if (this.options.has(RunCommand.this.verboseOption)) { return Level.FINEST; } - if (this.options.has(RunCommand.this.quiteOption)) { + if (this.options.has(RunCommand.this.quietOption)) { return Level.OFF; } return Level.INFO; diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/DependencyCustomizer.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/DependencyCustomizer.java index b8b5222259c..a0975b6a690 100644 --- a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/DependencyCustomizer.java +++ b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/DependencyCustomizer.java @@ -60,12 +60,34 @@ public class DependencyCustomizer { } /** - * Create a nested {@link DependencyCustomizer} that only applies if the specified - * class names are not on the class path. + * Create a nested {@link DependencyCustomizer} that only applies if any of the + * specified class names are not on the class path. * @param classNames the class names to test * @return a nested {@link DependencyCustomizer} */ - public DependencyCustomizer ifMissingClasses(final String... classNames) { + public DependencyCustomizer ifAnyMissingClasses(final String... classNames) { + return new DependencyCustomizer(this) { + @Override + protected boolean canAdd() { + for (String classname : classNames) { + try { + DependencyCustomizer.this.loader.loadClass(classname); + } catch (Exception e) { + return true; + } + } + return DependencyCustomizer.this.canAdd(); + } + }; + } + + /** + * Create a nested {@link DependencyCustomizer} that only applies if all of the + * specified class names are not on the class path. + * @param classNames the class names to test + * @return a nested {@link DependencyCustomizer} + */ + public DependencyCustomizer ifAllMissingClasses(final String... classNames) { return new DependencyCustomizer(this) { @Override protected boolean canAdd() { @@ -81,6 +103,86 @@ public class DependencyCustomizer { }; } + /** + * Create a nested {@link DependencyCustomizer} that only applies if the specified + * paths are on the class path. + * @param paths the paths to test + * @return a nested {@link DependencyCustomizer} + */ + public DependencyCustomizer ifAllResourcesPresent(final String... paths) { + return new DependencyCustomizer(this) { + @Override + protected boolean canAdd() { + for (String path : paths) { + try { + if (DependencyCustomizer.this.loader.getResource(path) == null) { + return false; + } + return true; + } catch (Exception e) { + } + } + return DependencyCustomizer.this.canAdd(); + } + }; + } + + /** + * Create a nested {@link DependencyCustomizer} that only applies at least one of the + * specified paths is on the class path. + * @param paths the paths to test + * @return a nested {@link DependencyCustomizer} + */ + public DependencyCustomizer ifAnyResourcesPresent(final String... paths) { + return new DependencyCustomizer(this) { + @Override + protected boolean canAdd() { + for (String path : paths) { + try { + if (DependencyCustomizer.this.loader.getResource(path) != null) { + return true; + } + return false; + } catch (Exception e) { + } + } + return DependencyCustomizer.this.canAdd(); + } + }; + } + + /** + * Create a nested {@link DependencyCustomizer} that only applies the specified one + * was not yet added. + * @return a nested {@link DependencyCustomizer} + */ + public DependencyCustomizer ifNotAdded(final String group, final String module) { + return new DependencyCustomizer(this) { + @Override + protected boolean canAdd() { + if (DependencyCustomizer.this.contains(group, module)) { + return false; + } + return DependencyCustomizer.this.canAdd(); + } + }; + } + + /** + * @param group the group ID + * @param module the module ID + * @return true if this module is already in the dependencies + */ + protected boolean contains(String group, String module) { + for (Map dependency : this.dependencies) { + if (group.equals(dependency.get("group")) + && module.equals(dependency.get("module"))) { + return true; + } + } + return false; + } + /** * Add a single dependencies. * @param group the group ID @@ -113,7 +215,7 @@ public class DependencyCustomizer { /** * Strategy called to test if dependencies can be added. Subclasses override as - * requred. + * required. */ protected boolean canAdd() { return true; diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/autoconfigure/SpringBatchCompilerAutoConfiguration.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/autoconfigure/SpringBatchCompilerAutoConfiguration.java index 0c79d2c15e0..54aaa9cc894 100644 --- a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/autoconfigure/SpringBatchCompilerAutoConfiguration.java +++ b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/autoconfigure/SpringBatchCompilerAutoConfiguration.java @@ -37,7 +37,7 @@ public class SpringBatchCompilerAutoConfiguration extends CompilerAutoConfigurat @Override public void applyDependencies(DependencyCustomizer dependencies) { - dependencies.ifMissingClasses("org.springframework.batch.core.Job").add( + dependencies.ifAnyMissingClasses("org.springframework.batch.core.Job").add( "org.springframework.batch", "spring-batch-core", "2.1.9.RELEASE"); } diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/autoconfigure/SpringBootstrapCompilerAutoConfiguration.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/autoconfigure/SpringBootstrapCompilerAutoConfiguration.java index 49fcd7a99a1..18a401110aa 100644 --- a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/autoconfigure/SpringBootstrapCompilerAutoConfiguration.java +++ b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/autoconfigure/SpringBootstrapCompilerAutoConfiguration.java @@ -38,15 +38,27 @@ public class SpringBootstrapCompilerAutoConfiguration extends CompilerAutoConfig @Override public void applyDependencies(DependencyCustomizer dependencies) { - dependencies.ifMissingClasses("org.springframework.bootstrap.SpringApplication") - .add("org.springframework.bootstrap", "spring-bootstrap-application", - "0.0.1-SNAPSHOT"); + DependencyCustomizer customizer = dependencies.ifAnyMissingClasses( + "org.springframework.bootstrap.SpringApplication").add( + "org.springframework.bootstrap", "spring-bootstrap", "0.0.1-SNAPSHOT"); + customizer = customizer.ifAnyResourcesPresent("logback.xml").add( + "ch.qos.logback", "logback-classic", "1.0.7"); + customizer = customizer.ifNotAdded("cg.qos.logback", "logback-classic") + .ifAnyResourcesPresent("log4j.properties", "log4j.xml") + .add("org.slf4j", "slf4j-log4j12", "1.7.1") + .add("log4j", "log4j", "1.2.16"); + customizer = customizer.ifNotAdded("ch.qos.logback", "logback-classic") + .ifNotAdded("org.slf4j", "slf4j-log4j12") + .add("org.slf4j", "slf4j-jdk14", "1.7.1"); // FIXME get the version } @Override public void applyImports(ImportCustomizer imports) { imports.addImports("javax.sql.DataSource", + "org.springframework.stereotype.Controller", + "org.springframework.stereotype.Service", + "org.springframework.stereotype.Component", "org.springframework.beans.factory.annotation.Autowired", "org.springframework.beans.factory.annotation.Value", "org.springframework.context.annotation.Import", diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/autoconfigure/SpringMvcCompilerAutoConfiguration.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/autoconfigure/SpringMvcCompilerAutoConfiguration.java index b1d3cdd7a16..5e2cc53d814 100644 --- a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/autoconfigure/SpringMvcCompilerAutoConfiguration.java +++ b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/autoconfigure/SpringMvcCompilerAutoConfiguration.java @@ -32,10 +32,10 @@ public class SpringMvcCompilerAutoConfiguration extends CompilerAutoConfiguratio @Override public void applyDependencies(DependencyCustomizer dependencies) { - dependencies.ifMissingClasses("org.springframework.web.servlet.mvc.Controller") + dependencies.ifAnyMissingClasses("org.springframework.web.servlet.mvc.Controller") .add("org.springframework", "spring-webmvc", "4.0.0.BOOTSTRAP-SNAPSHOT"); - dependencies.ifMissingClasses("org.apache.catalina.startup.Tomcat", + dependencies.ifAnyMissingClasses("org.apache.catalina.startup.Tomcat", "org.eclipse.jetty.server.Server").add("org.eclipse.jetty", "jetty-webapp", "8.1.10.v20130312"); diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/runner/BootstrapRunner.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/runner/BootstrapRunner.java index a445bf8b04c..84b3e5a562f 100644 --- a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/runner/BootstrapRunner.java +++ b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/runner/BootstrapRunner.java @@ -29,6 +29,7 @@ import org.springframework.bootstrap.cli.compiler.GroovyCompiler; * changes. * * @author Phillip Webb + * @author Dave Syer */ public class BootstrapRunner { @@ -71,11 +72,7 @@ public class BootstrapRunner { public synchronized void compileAndRun() throws Exception { try { - // Shutdown gracefully any running container - if (this.runThread != null) { - this.runThread.shutdown(); - this.runThread = null; - } + stop(); // Compile Class[] classes = this.compiler.compile(this.file); @@ -140,7 +137,7 @@ public class BootstrapRunner { } /** - * Shutdown the thread, closing any previously opened appplication context. + * Shutdown the thread, closing any previously opened application context. */ public synchronized void shutdown() { if (this.applicationContext != null) { @@ -188,4 +185,12 @@ public class BootstrapRunner { } } + + public void stop() { + if (this.runThread != null) { + this.runThread.shutdown(); + this.runThread = null; + } + } + } diff --git a/spring-bootstrap-cli/src/test/java/org/springframework/bootstrap/cli/SampleIntegrationTests.java b/spring-bootstrap-cli/src/test/java/org/springframework/bootstrap/cli/SampleIntegrationTests.java new file mode 100644 index 00000000000..02cae2ab195 --- /dev/null +++ b/spring-bootstrap-cli/src/test/java/org/springframework/bootstrap/cli/SampleIntegrationTests.java @@ -0,0 +1,76 @@ +/* + * 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.cli; + +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.junit.After; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * @author Dave Syer + * + */ +public class SampleIntegrationTests { + + private RunCommand command; + + private void start(final String sample) throws Exception { + Future future = Executors.newSingleThreadExecutor().submit( + new Callable() { + @Override + public RunCommand call() throws Exception { + RunCommand command = new RunCommand(); + command.run(sample); + return command; + } + }); + this.command = future.get(10, TimeUnit.SECONDS); + } + + @After + public void stop() { + if (this.command != null) { + this.command.stop(); + } + } + + @BeforeClass + public static void clean() { + // SpringBootstrapCli.main("clean"); + // System.setProperty("ivy.message.logger.level", "4"); + } + + @Test + public void appSample() throws Exception { + start("samples/app.groovy"); + } + + @Test + public void webSample() throws Exception { + start("samples/web.groovy"); + } + + @Test + public void serviceSample() throws Exception { + start("samples/service.groovy"); + } + +} diff --git a/spring-bootstrap-cli/src/test/resources/log4j.properties b/spring-bootstrap-cli/src/test/resources/log4j.properties new file mode 100644 index 00000000000..23e8a102dad --- /dev/null +++ b/spring-bootstrap-cli/src/test/resources/log4j.properties @@ -0,0 +1,7 @@ +log4j.rootCategory=INFO, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n + +log4j.category.org.springframework.bootstrap=DEBUG diff --git a/spring-bootstrap-samples/spring-bootstrap-trad-sample/pom.xml b/spring-bootstrap-samples/spring-bootstrap-trad-sample/pom.xml index 66def073b6a..d2100cf255e 100644 --- a/spring-bootstrap-samples/spring-bootstrap-trad-sample/pom.xml +++ b/spring-bootstrap-samples/spring-bootstrap-trad-sample/pom.xml @@ -7,7 +7,7 @@ 0.0.1-SNAPSHOT spring-bootstrap-trad-sample - Spring Bootstrap Trad Sample + spring-bootstrap-trad-sample war diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/ManagementAutoConfiguration.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/ManagementAutoConfiguration.java index fff5dd5fba1..544d90f7e79 100644 --- a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/ManagementAutoConfiguration.java +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/ManagementAutoConfiguration.java @@ -15,7 +15,6 @@ */ package org.springframework.bootstrap.autoconfigure.service; - import org.springframework.beans.BeansException; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.annotation.Autowired; @@ -41,7 +40,7 @@ public class ManagementAutoConfiguration implements ApplicationContextAware, private ApplicationContext parent; private ConfigurableApplicationContext context; - @ConditionalOnExpression("${container.port} == ${container.management_port}") + @ConditionalOnExpression("${container.port:8080} == ${container.management_port:8080}") @Configuration @Import({ VarzAutoConfiguration.class, HealthzAutoConfiguration.class }) public static class ManagementEndpointsConfiguration { diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/annotation/ExpressionCondition.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/annotation/ExpressionCondition.java index df912dd48cc..b1b00c20ba7 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/annotation/ExpressionCondition.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/annotation/ExpressionCondition.java @@ -15,12 +15,15 @@ */ package org.springframework.bootstrap.context.annotation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.config.BeanExpressionContext; import org.springframework.beans.factory.config.BeanExpressionResolver; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.core.type.ClassMetadata; /** * A Condition that evaluates a SpEL expression. @@ -30,6 +33,8 @@ import org.springframework.core.type.AnnotatedTypeMetadata; */ public class ExpressionCondition implements Condition { + private static Log logger = LogFactory.getLog(ExpressionCondition.class); + @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { String value = (String) metadata.getAnnotationAttributes( @@ -38,6 +43,14 @@ public class ExpressionCondition implements Condition { // For convenience allow user to provide bare expression with no #{} wrapper value = "#{" + value + "}"; } + if (logger.isDebugEnabled()) { + StringBuilder builder = new StringBuilder("Evaluating expression"); + if (metadata instanceof ClassMetadata) { + builder.append(" on " + ((ClassMetadata) metadata).getClassName()); + } + builder.append(": " + value); + logger.debug(builder.toString()); + } // Explicitly allow environment placeholders inside the expression value = context.getEnvironment().resolvePlaceholders(value); ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();