[bs-62] Add integration tests for CLI samples

[Fixes #48658503]
This commit is contained in:
Dave Syer 2013-04-24 16:09:17 +01:00
parent c91e83c7d2
commit 38f0cf1ed2
14 changed files with 265 additions and 33 deletions

View File

@ -1,12 +1,6 @@
package org.test package org.test
@GrabResolver(name='spring-milestone', root='http://repo.springframework.org/milestone') @Component
@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
class Example implements org.springframework.bootstrap.CommandLineRunner { class Example implements org.springframework.bootstrap.CommandLineRunner {
@org.springframework.beans.factory.annotation.Autowired @org.springframework.beans.factory.annotation.Autowired
@ -18,7 +12,7 @@ class Example implements org.springframework.bootstrap.CommandLineRunner {
} }
@org.springframework.stereotype.Service @Service
class MyService { class MyService {
public String sayWorld() { public String sayWorld() {

View File

@ -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!"]
}
}

View File

@ -1,4 +1,3 @@
@org.springframework.bootstrap.context.annotation.EnableAutoConfiguration
@Controller @Controller
class Example { class Example {

View File

@ -48,7 +48,9 @@ public class RunCommand extends OptionParsingCommand {
private OptionSpec<Void> verboseOption; private OptionSpec<Void> verboseOption;
private OptionSpec<Void> quiteOption; private OptionSpec<Void> quietOption;
private BootstrapRunner runner;
public RunCommand() { public RunCommand() {
super("run", "Run a spring groovy script"); super("run", "Run a spring groovy script");
@ -59,6 +61,12 @@ public class RunCommand extends OptionParsingCommand {
return "[options] <file>"; return "[options] <file>";
} }
public void stop() {
if (this.runner != null) {
this.runner.stop();
}
}
@Override @Override
protected OptionParser createOptionParser() { protected OptionParser createOptionParser() {
OptionParser parser = new OptionParser(); OptionParser parser = new OptionParser();
@ -71,7 +79,7 @@ public class RunCommand extends OptionParsingCommand {
this.noGuessDependenciesOption = parser.accepts("no-guess-dependencies", this.noGuessDependenciesOption = parser.accepts("no-guess-dependencies",
"Do not attempt to guess dependencies"); "Do not attempt to guess dependencies");
this.verboseOption = parser.acceptsAll(asList("verbose", "v"), "Verbose logging"); 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; return parser;
} }
@ -87,8 +95,9 @@ public class RunCommand extends OptionParsingCommand {
BootstrapRunnerConfiguration configuration = new BootstrapRunnerConfigurationAdapter( BootstrapRunnerConfiguration configuration = new BootstrapRunnerConfigurationAdapter(
options); options);
new BootstrapRunner(configuration, file, args.toArray(new String[args.size()])) this.runner = new BootstrapRunner(configuration, file,
.compileAndRun(); args.toArray(new String[args.size()]));
this.runner.compileAndRun();
} }
private File getFileArgument(List<String> nonOptionArguments) { private File getFileArgument(List<String> nonOptionArguments) {
@ -136,7 +145,7 @@ public class RunCommand extends OptionParsingCommand {
if (this.options.has(RunCommand.this.verboseOption)) { if (this.options.has(RunCommand.this.verboseOption)) {
return Level.FINEST; return Level.FINEST;
} }
if (this.options.has(RunCommand.this.quiteOption)) { if (this.options.has(RunCommand.this.quietOption)) {
return Level.OFF; return Level.OFF;
} }
return Level.INFO; return Level.INFO;

View File

@ -60,12 +60,34 @@ public class DependencyCustomizer {
} }
/** /**
* Create a nested {@link DependencyCustomizer} that only applies if the specified * Create a nested {@link DependencyCustomizer} that only applies if any of the
* class names are not on the class path. * specified class names are not on the class path.
* @param classNames the class names to test * @param classNames the class names to test
* @return a nested {@link DependencyCustomizer} * @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) { return new DependencyCustomizer(this) {
@Override @Override
protected boolean canAdd() { 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<String, Object> dependency : this.dependencies) {
if (group.equals(dependency.get("group"))
&& module.equals(dependency.get("module"))) {
return true;
}
}
return false;
}
/** /**
* Add a single dependencies. * Add a single dependencies.
* @param group the group ID * @param group the group ID
@ -113,7 +215,7 @@ public class DependencyCustomizer {
/** /**
* Strategy called to test if dependencies can be added. Subclasses override as * Strategy called to test if dependencies can be added. Subclasses override as
* requred. * required.
*/ */
protected boolean canAdd() { protected boolean canAdd() {
return true; return true;

View File

@ -37,7 +37,7 @@ public class SpringBatchCompilerAutoConfiguration extends CompilerAutoConfigurat
@Override @Override
public void applyDependencies(DependencyCustomizer dependencies) { 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"); "org.springframework.batch", "spring-batch-core", "2.1.9.RELEASE");
} }

View File

@ -38,15 +38,27 @@ public class SpringBootstrapCompilerAutoConfiguration extends CompilerAutoConfig
@Override @Override
public void applyDependencies(DependencyCustomizer dependencies) { public void applyDependencies(DependencyCustomizer dependencies) {
dependencies.ifMissingClasses("org.springframework.bootstrap.SpringApplication") DependencyCustomizer customizer = dependencies.ifAnyMissingClasses(
.add("org.springframework.bootstrap", "spring-bootstrap-application", "org.springframework.bootstrap.SpringApplication").add(
"0.0.1-SNAPSHOT"); "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 // FIXME get the version
} }
@Override @Override
public void applyImports(ImportCustomizer imports) { public void applyImports(ImportCustomizer imports) {
imports.addImports("javax.sql.DataSource", 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.Autowired",
"org.springframework.beans.factory.annotation.Value", "org.springframework.beans.factory.annotation.Value",
"org.springframework.context.annotation.Import", "org.springframework.context.annotation.Import",

View File

@ -32,10 +32,10 @@ public class SpringMvcCompilerAutoConfiguration extends CompilerAutoConfiguratio
@Override @Override
public void applyDependencies(DependencyCustomizer dependencies) { 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"); .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", "org.eclipse.jetty.server.Server").add("org.eclipse.jetty",
"jetty-webapp", "8.1.10.v20130312"); "jetty-webapp", "8.1.10.v20130312");

View File

@ -29,6 +29,7 @@ import org.springframework.bootstrap.cli.compiler.GroovyCompiler;
* changes. * changes.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Dave Syer
*/ */
public class BootstrapRunner { public class BootstrapRunner {
@ -71,11 +72,7 @@ public class BootstrapRunner {
public synchronized void compileAndRun() throws Exception { public synchronized void compileAndRun() throws Exception {
try { try {
// Shutdown gracefully any running container stop();
if (this.runThread != null) {
this.runThread.shutdown();
this.runThread = null;
}
// Compile // Compile
Class<?>[] classes = this.compiler.compile(this.file); 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() { public synchronized void shutdown() {
if (this.applicationContext != null) { if (this.applicationContext != null) {
@ -188,4 +185,12 @@ public class BootstrapRunner {
} }
} }
public void stop() {
if (this.runThread != null) {
this.runThread.shutdown();
this.runThread = null;
}
}
} }

View File

@ -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<RunCommand> future = Executors.newSingleThreadExecutor().submit(
new Callable<RunCommand>() {
@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");
}
}

View File

@ -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

View File

@ -7,7 +7,7 @@
<version>0.0.1-SNAPSHOT</version> <version>0.0.1-SNAPSHOT</version>
</parent> </parent>
<artifactId>spring-bootstrap-trad-sample</artifactId> <artifactId>spring-bootstrap-trad-sample</artifactId>
<name>Spring Bootstrap Trad Sample</name> <name>spring-bootstrap-trad-sample</name>
<packaging>war</packaging> <packaging>war</packaging>
<properties> <properties>

View File

@ -15,7 +15,6 @@
*/ */
package org.springframework.bootstrap.autoconfigure.service; package org.springframework.bootstrap.autoconfigure.service;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -41,7 +40,7 @@ public class ManagementAutoConfiguration implements ApplicationContextAware,
private ApplicationContext parent; private ApplicationContext parent;
private ConfigurableApplicationContext context; private ConfigurableApplicationContext context;
@ConditionalOnExpression("${container.port} == ${container.management_port}") @ConditionalOnExpression("${container.port:8080} == ${container.management_port:8080}")
@Configuration @Configuration
@Import({ VarzAutoConfiguration.class, HealthzAutoConfiguration.class }) @Import({ VarzAutoConfiguration.class, HealthzAutoConfiguration.class })
public static class ManagementEndpointsConfiguration { public static class ManagementEndpointsConfiguration {

View File

@ -15,12 +15,15 @@
*/ */
package org.springframework.bootstrap.context.annotation; 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.BeanExpressionContext;
import org.springframework.beans.factory.config.BeanExpressionResolver; import org.springframework.beans.factory.config.BeanExpressionResolver;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.ClassMetadata;
/** /**
* A Condition that evaluates a SpEL expression. * A Condition that evaluates a SpEL expression.
@ -30,6 +33,8 @@ import org.springframework.core.type.AnnotatedTypeMetadata;
*/ */
public class ExpressionCondition implements Condition { public class ExpressionCondition implements Condition {
private static Log logger = LogFactory.getLog(ExpressionCondition.class);
@Override @Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String value = (String) metadata.getAnnotationAttributes( 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 // For convenience allow user to provide bare expression with no #{} wrapper
value = "#{" + value + "}"; 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 // Explicitly allow environment placeholders inside the expression
value = context.getEnvironment().resolvePlaceholders(value); value = context.getEnvironment().resolvePlaceholders(value);
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();