diff --git a/spring-boot-cli-properties/pom.xml b/spring-boot-cli-properties/pom.xml
index 66578ea7bc..69fe553845 100644
--- a/spring-boot-cli-properties/pom.xml
+++ b/spring-boot-cli-properties/pom.xml
@@ -492,6 +492,11 @@
geronimo-jms_1.1_spec
provided
+
+ org.spockframework
+ spock-core
+ provided
+
diff --git a/spring-boot-cli/.gitignore b/spring-boot-cli/.gitignore
new file mode 100644
index 0000000000..e2b3ee78ba
--- /dev/null
+++ b/spring-boot-cli/.gitignore
@@ -0,0 +1 @@
+results.txt
diff --git a/spring-boot-cli/pom.xml b/spring-boot-cli/pom.xml
index dd304597b6..02e73a71a0 100644
--- a/spring-boot-cli/pom.xml
+++ b/spring-boot-cli/pom.xml
@@ -33,12 +33,26 @@
org.codehaus.groovy
groovy
+
+ org.springframework
+ spring-core
+
org.codehaus.groovy
groovy-templates
true
+
+ junit
+ junit
+ test
+
+
+ org.spockframework
+ spock-core
+ test
+
org.springframework.integration
@@ -67,6 +81,16 @@
+
+
+ src/main/resources
+ true
+
+
+ testers
+ testers
+
+
maven-surefire-plugin
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/DefaultCommandFactory.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/DefaultCommandFactory.java
index 75e27406f1..8ac55ffc9b 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/DefaultCommandFactory.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/DefaultCommandFactory.java
@@ -31,7 +31,7 @@ import org.springframework.boot.cli.CommandFactory;
public class DefaultCommandFactory implements CommandFactory {
private static final List DEFAULT_COMMANDS = Arrays. asList(
- new VersionCommand(), new RunCommand(), new CleanCommand());
+ new VersionCommand(), new RunCommand(), new CleanCommand(), new TestCommand());
@Override
public Collection getCommands() {
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/TestCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/TestCommand.java
new file mode 100644
index 0000000000..f1a2b4ff3b
--- /dev/null
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/TestCommand.java
@@ -0,0 +1,264 @@
+/*
+ * 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.boot.cli.command;
+
+import groovy.lang.GroovyObject;
+import joptsimple.OptionSet;
+import org.apache.ivy.util.FileUtil;
+import org.springframework.boot.cli.Log;
+import org.springframework.boot.cli.command.tester.Failure;
+import org.springframework.boot.cli.command.tester.TestResults;
+import org.springframework.boot.cli.compiler.GroovyCompiler;
+import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Scanner;
+import java.util.Set;
+import java.util.logging.Level;
+
+/**
+ * Invokes testing for autocompiled scripts
+ *
+ * @author Greg Turnquist
+ */
+public class TestCommand extends OptionParsingCommand {
+
+ private TestOptionHandler testOptionHandler;
+
+ public TestCommand() {
+ super("test", "Test a groovy script", new TestOptionHandler());
+ this.testOptionHandler = (TestOptionHandler)this.getHandler();
+ }
+
+ @Override
+ public String getUsageHelp() {
+ return "[options] ";
+ }
+
+ public TestResults getResults() {
+ return testOptionHandler.results;
+ }
+
+ private static class TestGroovyCompilerConfiguration implements GroovyCompilerConfiguration {
+ @Override
+ public boolean isGuessImports() {
+ return true;
+ }
+
+ @Override
+ public boolean isGuessDependencies() {
+ return true;
+ }
+
+ @Override
+ public String getClasspath() {
+ return "";
+ }
+
+ public Level getLogLevel() {
+ return Level.INFO;
+ }
+ }
+
+ private static class TestOptionHandler extends OptionHandler {
+
+ private final GroovyCompiler compiler;
+
+ private TestResults results;
+
+ public TestOptionHandler() {
+ TestGroovyCompilerConfiguration configuration = new TestGroovyCompilerConfiguration();
+ this.compiler = new GroovyCompiler(configuration);
+ if (configuration.getLogLevel().intValue() <= Level.FINE.intValue()) {
+ System.setProperty("groovy.grape.report.downloads", "true");
+ }
+ }
+
+ @Override
+ protected void run(OptionSet options) throws Exception {
+ List> nonOptionArguments = options.nonOptionArguments();
+
+ Set testerFiles = new HashSet();
+ File[] files = getFileArguments(nonOptionArguments, testerFiles);
+
+ /*
+ * Need to compile the code twice: The first time automatically
+ * pulls in autoconfigured libraries including test tools. Then
+ * the compiled code can be scanned to see what libraries were
+ * activated. Then it can be recompiled, with appropriate tester
+ * groovy scripts included in the same classloading context. Then
+ * the testers can be fetched and invoked through reflection against
+ * the composite AST.
+ */
+ // Compile - Pass 1
+ Object[] sources = this.compiler.sources(files);
+
+ boolean testing = false;
+ try {
+ check("org.junit.Test", sources);
+ testerFiles.add(locateSourceFromUrl("junit", "testers/junit.groovy"));
+ testing = true;
+ } catch (ClassNotFoundException e) {
+ }
+
+ try {
+ check("spock.lang.Specification", sources);
+ testerFiles.add(locateSourceFromUrl("spock", "testers/spock.groovy"));
+ testing = true;
+ } catch (ClassNotFoundException e) {
+ }
+
+ if (testing) {
+ testerFiles.add(locateSourceFromUrl("tester", "testers/tester.groovy"));
+ }
+
+ // Compile - Pass 2 - with appropriate testers added in
+ files = getFileArguments(nonOptionArguments, testerFiles);
+ sources = this.compiler.sources(files);
+
+
+ if (sources.length == 0) {
+ throw new RuntimeException("No classes found in '" + files + "'");
+ }
+
+ List> testers = new ArrayList>();
+
+ // Extract list of compiled classes
+ List> compiled = new ArrayList>();
+ for (Object source : sources) {
+ if (source.getClass() == Class.class) {
+ Class> sourceClass = (Class>)source;
+ if (sourceClass.getSuperclass().getName().equals("AbstractTester")) {
+ testers.add(sourceClass);
+ } else {
+ compiled.add((Class>)source);
+ }
+ }
+ }
+
+ this.results = new TestResults();
+
+ for (Class> tester : testers) {
+ GroovyObject obj = (GroovyObject)tester.newInstance();
+ this.results.add((TestResults)obj.invokeMethod("findAndTest", compiled));
+ }
+
+ printReport(this.results);
+ }
+
+ private File locateSourceFromUrl(String name, String path) {
+ try {
+ File file = File.createTempFile(name, ".groovy");
+ file.deleteOnExit();
+ FileUtil.copy(getClass().getClassLoader().getResourceAsStream(path), file, null);
+ return file;
+ }
+ catch (IOException ex) {
+ throw new IllegalStateException("Could not create temp file for source: "
+ + name);
+ }
+ }
+
+ private Class> check(String className, Object[] sources) throws ClassNotFoundException {
+ Class> classToReturn = null;
+ ClassNotFoundException classNotFoundException = null;
+ for (Object source : sources) {
+ try {
+ classToReturn = ((Class>)source).getClassLoader().loadClass(className);
+ } catch (ClassNotFoundException e) {
+ classNotFoundException = e;
+ }
+ }
+ if (classToReturn != null) {
+ return classToReturn;
+ }
+ throw classNotFoundException;
+ }
+
+
+ private File[] getFileArguments(List> nonOptionArguments, Set testerFiles) {
+ List files = new ArrayList();
+ for (Object option : nonOptionArguments) {
+ if (option instanceof String) {
+ String filename = (String) option;
+ if ("--".equals(filename)) {
+ break;
+ }
+ if (filename.endsWith(".groovy") || filename.endsWith(".java")) {
+ File file = new File(filename);
+ if (file.isFile() && file.canRead()) {
+ files.add(file);
+ }
+ else {
+ URL url = getClass().getClassLoader().getResource(filename);
+ if (url != null) {
+ if (url.toString().startsWith("file:")) {
+ files.add(new File(url.toString().substring("file:".length())));
+ }
+ }
+ else {
+ throw new RuntimeException("Can't find " + filename);
+ }
+ }
+ }
+ }
+ }
+ if (files.size() == 0) {
+ throw new RuntimeException("Please specify a file to run");
+ }
+
+ for (File testerFile : testerFiles) {
+ files.add(testerFile);
+ }
+
+ return files.toArray(new File[files.size()]);
+ }
+
+ private void printReport(TestResults results) throws FileNotFoundException {
+ PrintWriter writer = new PrintWriter("results.txt");
+
+ String header = "Total: " + results.getRunCount() +
+ ", Success: " + (results.getRunCount()-results.getFailureCount()) +
+ ", : Failures: " + results.getFailureCount() + "\n" +
+ "Passed? " + results.wasSuccessful();
+
+ String trailer = "";
+ String trace = "";
+ for (Failure failure : results.getFailures()) {
+ trailer += failure.getDescription().toString();
+ trace += failure.getTrace() + "\n";
+ }
+
+ writer.println(header);
+ writer.println(trace);
+ writer.close();
+
+ Log.info(header);
+ Log.info(trailer);
+ }
+
+ }
+
+}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/tester/AbstractTester.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/tester/AbstractTester.java
new file mode 100644
index 0000000000..bef7dfbd86
--- /dev/null
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/tester/AbstractTester.java
@@ -0,0 +1,39 @@
+/*
+ * 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.boot.cli.command.tester;
+
+import java.io.FileNotFoundException;
+import java.util.List;
+import java.util.Set;
+
+public abstract class AbstractTester {
+
+ public TestResults findAndTest(List> compiled) throws FileNotFoundException {
+ Set> testable = findTestableClasses(compiled);
+
+ if (testable.size() == 0) {
+ return TestResults.none;
+ }
+
+ return test(testable.toArray(new Class>[]{}));
+ }
+
+ abstract protected Set> findTestableClasses(List> compiled);
+
+ abstract protected TestResults test(Class>[] testable);
+
+}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/tester/Failure.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/tester/Failure.java
new file mode 100644
index 0000000000..1dead7929d
--- /dev/null
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/tester/Failure.java
@@ -0,0 +1,48 @@
+/*
+ * 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.boot.cli.command.tester;
+
+/**
+ * Platform neutral way to capture a test failure
+ *
+ * NOTE: This is needed to avoid having to add JUnit jar file to the deployable artifacts
+ */
+public class Failure {
+ private String description;
+ private String trace;
+
+ public Failure(String description, String trace) {
+ this.description = description;
+ this.trace = trace;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getTrace() {
+ return trace;
+ }
+
+ public void setTrace(String trace) {
+ this.trace = trace;
+ }
+}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/tester/TestResults.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/tester/TestResults.java
new file mode 100644
index 0000000000..d5f6344e4d
--- /dev/null
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/tester/TestResults.java
@@ -0,0 +1,94 @@
+/*
+ * 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.boot.cli.command.tester;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Platform neutral way to collect test results
+ *
+ * NOTE: This is needed to avoid having to add JUnit's jar file to the deployable artifacts
+ */
+public class TestResults {
+
+ public static final NoTestResults none = new NoTestResults();
+
+ private int runCount;
+ private int failureCount;
+ private Failure[] failures = new Failure[0];
+
+ public void add(TestResults results) {
+ this.runCount += results.getRunCount();
+ this.failureCount += results.getFailureCount();
+
+ List failures = Arrays.asList(this.failures);
+ failures.addAll(Arrays.asList(results.getFailures()));
+ this.failures = failures.toArray(new Failure[]{});
+ }
+
+
+ public boolean wasSuccessful() {
+ return this.failureCount == 0;
+ }
+
+ public int getRunCount() {
+ return runCount;
+ }
+
+ public void setRunCount(int runCount) {
+ this.runCount = runCount;
+ }
+
+ public int getFailureCount() {
+ return failureCount;
+ }
+
+ public void setFailureCount(int failureCount) {
+ this.failureCount = failureCount;
+ }
+
+ public Failure[] getFailures() {
+ return failures;
+ }
+
+ public void setFailures(Failure[] failures) {
+ this.failures = failures;
+ }
+
+ private static class NoTestResults extends TestResults {
+ @Override
+ public int getRunCount() {
+ return 0;
+ }
+
+ @Override
+ public int getFailureCount() {
+ return 0;
+ }
+
+ @Override
+ public Failure[] getFailures() {
+ return new Failure[0];
+ }
+
+ @Override
+ public boolean wasSuccessful() {
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/AstUtils.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/AstUtils.java
index 406ca862f3..58935d27a6 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/AstUtils.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/AstUtils.java
@@ -32,6 +32,7 @@ import org.codehaus.groovy.ast.MethodNode;
*
* @author Phillip Webb
* @author Dave Syer
+ * @author Greg Turnquist
*/
public abstract class AstUtils {
@@ -49,12 +50,40 @@ public abstract class AstUtils {
}
}
}
+
return false;
}
+ /**
+ * Determine if a {@link ClassNode} has one or more of the specified annotations on the class
+ * or any of its methods.
+ * N.B. the type names are not normally fully qualified.
+ */
+ public static boolean hasAtLeastOneAnnotation(ClassNode node, String... annotations) {
+ for (AnnotationNode annotationNode : node.getAnnotations()) {
+ for (String annotation : annotations) {
+ if (annotation.equals(annotationNode.getClassNode().getName())) {
+ return true;
+ }
+ }
+ }
+
+ List methods = node.getMethods();
+ for (MethodNode method : methods) {
+ for (AnnotationNode annotationNode : method.getAnnotations()) {
+ for (String annotation : annotations) {
+ if (annotation.equals(annotationNode.getClassNode().getName())) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
/**
- * Determine if an {@link ClassNode} has one or more fields of the specified types or
+ * Determine if a {@link ClassNode} has one or more fields of the specified types or
* method returning one or more of the specified types. N.B. the type names are not
* normally fully qualified.
*/
@@ -73,8 +102,23 @@ public abstract class AstUtils {
return true;
}
}
+
return false;
}
+ /**
+ * Determine if a {@link ClassNode} subclasses any of the specified types
+ * N.B. the type names are not normally fully qualified.
+ */
+ public static boolean subclasses(ClassNode node, String... types) {
+ for (String type : types) {
+ if (node.getSuperClass().getName().equals(type)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/JUnitCompilerAutoConfiguration.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/JUnitCompilerAutoConfiguration.java
new file mode 100644
index 0000000000..74ca976dee
--- /dev/null
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/JUnitCompilerAutoConfiguration.java
@@ -0,0 +1,52 @@
+/*
+ * 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.boot.cli.compiler.autoconfigure;
+
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.control.CompilationFailedException;
+import org.codehaus.groovy.control.customizers.ImportCustomizer;
+import org.springframework.boot.cli.compiler.AstUtils;
+import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
+import org.springframework.boot.cli.compiler.DependencyCustomizer;
+
+/**
+ * {@link CompilerAutoConfiguration} for JUnit
+ *
+ * @author Greg Turnquist
+ */
+public class JUnitCompilerAutoConfiguration extends CompilerAutoConfiguration {
+
+ @Override
+ public boolean matches(ClassNode classNode) {
+ return AstUtils.hasAtLeastOneAnnotation(classNode, "Test");
+ }
+
+ @Override
+ public void applyDependencies(DependencyCustomizer dependencies)
+ throws CompilationFailedException {
+ dependencies.add("junit").add("spring-test").add("hamcrest-library");
+ }
+
+ @Override
+ public void applyImports(ImportCustomizer imports) throws CompilationFailedException {
+ imports.addStarImports("org.junit")
+ .addStaticStars("org.junit.Assert").addImports()
+ .addStaticStars("org.hamcrest.MatcherAssert")
+ .addStaticStars("org.hamcrest.Matchers");
+ }
+
+}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpockCompilerAutoConfiguration.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpockCompilerAutoConfiguration.java
new file mode 100644
index 0000000000..49665010a0
--- /dev/null
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpockCompilerAutoConfiguration.java
@@ -0,0 +1,49 @@
+/*
+ * 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.boot.cli.compiler.autoconfigure;
+
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.control.CompilationFailedException;
+import org.codehaus.groovy.control.customizers.ImportCustomizer;
+import org.springframework.boot.cli.compiler.AstUtils;
+import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
+import org.springframework.boot.cli.compiler.DependencyCustomizer;
+
+/**
+ * {@link CompilerAutoConfiguration} for Spock test framework
+ *
+ * @author Greg Turnquist
+ */
+public class SpockCompilerAutoConfiguration extends CompilerAutoConfiguration {
+
+ @Override
+ public boolean matches(ClassNode classNode) {
+ return AstUtils.subclasses(classNode, "Specification");
+ }
+
+ @Override
+ public void applyDependencies(DependencyCustomizer dependencies)
+ throws CompilationFailedException {
+ dependencies.add("spock-core");
+ }
+
+ @Override
+ public void applyImports(ImportCustomizer imports) throws CompilationFailedException {
+ imports.addStarImports("spock.lang");
+ }
+
+}
diff --git a/spring-boot-cli/src/main/resources/META-INF/services/org.springframework.boot.cli.compiler.CompilerAutoConfiguration b/spring-boot-cli/src/main/resources/META-INF/services/org.springframework.boot.cli.compiler.CompilerAutoConfiguration
index 8d12d2dfe2..3e389e829c 100644
--- a/spring-boot-cli/src/main/resources/META-INF/services/org.springframework.boot.cli.compiler.CompilerAutoConfiguration
+++ b/spring-boot-cli/src/main/resources/META-INF/services/org.springframework.boot.cli.compiler.CompilerAutoConfiguration
@@ -5,6 +5,8 @@ org.springframework.boot.cli.compiler.autoconfigure.RabbitCompilerAutoConfigurat
org.springframework.boot.cli.compiler.autoconfigure.ReactorCompilerAutoConfiguration
org.springframework.boot.cli.compiler.autoconfigure.JdbcCompilerAutoConfiguration
org.springframework.boot.cli.compiler.autoconfigure.JmsCompilerAutoConfiguration
+org.springframework.boot.cli.compiler.autoconfigure.JUnitCompilerAutoConfiguration
+org.springframework.boot.cli.compiler.autoconfigure.SpockCompilerAutoConfiguration
org.springframework.boot.cli.compiler.autoconfigure.TransactionManagementCompilerAutoConfiguration
org.springframework.boot.cli.compiler.autoconfigure.SpringIntegrationCompilerAutoConfiguration
org.springframework.boot.cli.compiler.autoconfigure.SpringSecurityCompilerAutoConfiguration
diff --git a/spring-boot-cli/src/main/resources/META-INF/springcli.properties b/spring-boot-cli/src/main/resources/META-INF/springcli.properties
new file mode 100644
index 0000000000..d3c6869afd
--- /dev/null
+++ b/spring-boot-cli/src/main/resources/META-INF/springcli.properties
@@ -0,0 +1,14 @@
+groovy.version: ${groovy.version}
+hamcrest.version: ${hamcrest.version}
+jetty.version: ${jetty.version}
+junit.version: ${junit.version}
+reactor.version: ${reactor.version}
+spock.version: ${spock.version}
+spring.version: ${spring.version}
+spring-batch.version: ${spring-batch.version}
+spring-boot.version: ${project.version}
+spring-rabbit.version: ${spring-rabbit.version}
+spring-security.version: ${spring-security.version}
+spring-integration.version: ${spring-integration.version}
+spring-integration-groovydsl.version: ${spring-integration-groovydsl.version}
+tomcat.version: ${tomcat.version}
diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/SpringCliTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/SpringCliTests.java
index 8cd168bd21..176a6e380a 100644
--- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/SpringCliTests.java
+++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/SpringCliTests.java
@@ -1,21 +1,18 @@
package org.springframework.boot.cli;
-import java.util.Arrays;
-import java.util.EnumSet;
-import java.util.Set;
-
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.springframework.boot.cli.Command;
-import org.springframework.boot.cli.NoSuchCommandException;
-import org.springframework.boot.cli.SpringCli;
import org.springframework.boot.cli.SpringCli.NoArgumentsException;
import org.springframework.boot.cli.SpringCli.NoHelpCommandArgumentsException;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.Set;
+
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.given;
diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/TestCommandIntegrationTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/TestCommandIntegrationTests.java
new file mode 100644
index 0000000000..ebe2c0010a
--- /dev/null
+++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/TestCommandIntegrationTests.java
@@ -0,0 +1,125 @@
+/*
+ * 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.boot.cli;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.springframework.boot.cli.command.CleanCommand;
+import org.springframework.boot.cli.command.TestCommand;
+import org.springframework.boot.cli.command.tester.TestResults;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Integration tests to exercise the CLI's test command.
+ *
+ * @author Greg Turnquist
+ */
+public class TestCommandIntegrationTests {
+
+ @BeforeClass
+ public static void cleanGrapes() throws Exception {
+ GrapesCleaner.cleanIfNecessary();
+ // System.setProperty("ivy.message.logger.level", "3");
+ }
+
+ @Before
+ public void setup() throws Exception {
+ System.setProperty("disableSpringSnapshotRepos", "true");
+ new CleanCommand().run("org.springframework");
+ }
+
+ @After
+ public void teardown() {
+ System.clearProperty("disableSpringSnapshotRepos");
+ }
+
+ @Test
+ public void noTests() throws Throwable {
+ TestCommand command = new TestCommand();
+ command.run("testscripts/book.groovy");
+ TestResults results = command.getResults();
+ assertEquals(0, results.getRunCount());
+ assertEquals(0, results.getFailureCount());
+ assertTrue(results.wasSuccessful());
+ }
+
+ @Test
+ public void empty() throws Exception {
+ TestCommand command = new TestCommand();
+ command.run("testscripts/empty.groovy");
+ TestResults results = command.getResults();
+ assertEquals(0, results.getRunCount());
+ assertEquals(0, results.getFailureCount());
+ assertTrue(results.wasSuccessful());
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void noFile() throws Exception {
+ try {
+ TestCommand command = new TestCommand();
+ command.run("testscripts/nothing.groovy");
+ } catch (RuntimeException e) {
+ assertEquals("Can't find testscripts/nothing.groovy", e.getMessage());
+ throw e;
+ }
+ }
+
+ @Test
+ public void appAndTestsInOneFile() throws Exception {
+ TestCommand command = new TestCommand();
+ command.run("testscripts/book_and_tests.groovy");
+ TestResults results = command.getResults();
+ assertEquals(1, results.getRunCount());
+ assertEquals(0, results.getFailureCount());
+ assertTrue(results.wasSuccessful());
+ }
+
+ @Test
+ public void appInOneFileTestsInAnotherFile() throws Exception {
+ TestCommand command = new TestCommand();
+ command.run("testscripts/book.groovy", "testscripts/test.groovy");
+ TestResults results = command.getResults();
+ assertEquals(1, results.getRunCount());
+ assertEquals(0, results.getFailureCount());
+ assertTrue(results.wasSuccessful());
+ }
+
+ @Test
+ public void spockTester() throws Exception {
+ TestCommand command = new TestCommand();
+ command.run("testscripts/spock.groovy");
+ TestResults results = command.getResults();
+ assertEquals(1, results.getRunCount());
+ assertEquals(0, results.getFailureCount());
+ assertTrue(results.wasSuccessful());
+ }
+
+ @Test
+ public void spockAndJunitTester() throws Exception {
+ TestCommand command = new TestCommand();
+ command.run("testscripts/spock.groovy", "testscripts/book_and_tests.groovy");
+ TestResults results = command.getResults();
+ assertEquals(2, results.getRunCount());
+ assertEquals(0, results.getFailureCount());
+ assertTrue(results.wasSuccessful());
+ }
+
+}
diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/ScriptCommandTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/ScriptCommandTests.java
index ee035d9008..ed121654f8 100644
--- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/ScriptCommandTests.java
+++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/ScriptCommandTests.java
@@ -22,8 +22,6 @@ import groovy.lang.Script;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.boot.cli.GrapesCleaner;
-import org.springframework.boot.cli.command.OptionHandler;
-import org.springframework.boot.cli.command.ScriptCommand;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
diff --git a/spring-boot-cli/src/test/resources/commands/handler.groovy b/spring-boot-cli/src/test/resources/commands/handler.groovy
index 2b0e7b5b76..2c3eef136f 100644
--- a/spring-boot-cli/src/test/resources/commands/handler.groovy
+++ b/spring-boot-cli/src/test/resources/commands/handler.groovy
@@ -3,6 +3,9 @@ package org.test.command
@Grab("org.eclipse.jgit:org.eclipse.jgit:2.3.1.201302201838-r")
import org.eclipse.jgit.api.Git
+@Grab("org.eclipse.jgit:org.eclipse.jgit:2.3.1.201302201838-r") @Grab("org.eclipse.jgit:org.eclipse.jgit:2.3.1.201302201838-r") @Grab("org.eclipse.jgit:org.eclipse.jgit:2.3.1.201302201838-r") @Grab("org.eclipse.jgit:org.eclipse.jgit:2.3.1.201302201838-r") @Grab("org.eclipse.jgit:org.eclipse.jgit:2.3.1.201302201838-r")
+import java.lang.Object
+
class TestCommand extends OptionHandler {
void options() {
diff --git a/spring-boot-cli/testers/junit.groovy b/spring-boot-cli/testers/junit.groovy
new file mode 100644
index 0000000000..b21fd4662d
--- /dev/null
+++ b/spring-boot-cli/testers/junit.groovy
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+import org.junit.runner.JUnitCore
+import org.junit.runner.Result
+import org.springframework.boot.cli.command.tester.Failure
+import org.springframework.boot.cli.command.tester.TestResults
+
+import java.lang.annotation.Annotation
+import java.lang.reflect.Method
+
+/**
+ * Groovy script to run JUnit tests inside the {@link TestCommand}.
+ * Needs to be compiled along with the actual code to work properly.
+ *
+ * @author Greg Turnquist
+ */
+class JUnitTester extends AbstractTester {
+
+ @Override
+ protected Set> findTestableClasses(List> compiled) {
+ // Look for @Test methods
+ Set> testable = new LinkedHashSet>()
+ for (Class> clazz : compiled) {
+ for (Method method : clazz.getMethods()) {
+ for (Annotation annotation : method.getAnnotations()) {
+ if (annotation.toString().contains("Test")) {
+ testable.add(clazz)
+ }
+ }
+ }
+ }
+ return testable
+ }
+
+ @Override
+ protected TestResults test(Class>[] testable) {
+ Result results = JUnitCore.runClasses(testable)
+
+ TestResults testResults = new TestResults()
+ testResults.setFailureCount(results.getFailureCount())
+ testResults.setRunCount(results.getRunCount())
+
+ List failures =
+ new ArrayList()
+ for (org.junit.runner.notification.Failure failure : results.getFailures()) {
+ failures.add(new Failure(failure.getDescription().toString(), failure.getTrace()))
+ }
+
+ testResults.setFailures(failures.toArray(new Failure[0]))
+
+ return testResults
+ }
+
+}
diff --git a/spring-boot-cli/testers/spock.groovy b/spring-boot-cli/testers/spock.groovy
new file mode 100644
index 0000000000..5176021212
--- /dev/null
+++ b/spring-boot-cli/testers/spock.groovy
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+import org.junit.runner.Result
+import org.springframework.boot.cli.command.tester.Failure
+import org.springframework.boot.cli.command.tester.TestResults
+import spock.lang.Specification
+import spock.util.EmbeddedSpecRunner
+
+/**
+ * Groovy script to run Spock tests inside the {@link TestCommand}.
+ * Needs to be compiled along with the actual code to work properly.
+ *
+ * @author Greg Turnquist
+ */
+class SpockTester extends AbstractTester {
+
+ @Override
+ protected Set> findTestableClasses(List> compiled) {
+ // Look for classes that implement spock.lang.Specification
+ Set> testable = new LinkedHashSet>()
+ for (Class> clazz : compiled) {
+ if (Specification.class.isAssignableFrom(clazz)) {
+ testable.add(clazz)
+ }
+ }
+ return testable
+ }
+
+ @Override
+ protected TestResults test(Class>[] testable) {
+ Result results = new EmbeddedSpecRunner().runClasses(Arrays.asList(testable))
+
+ TestResults testResults = new TestResults()
+ testResults.setFailureCount(results.getFailureCount())
+ testResults.setRunCount(results.getRunCount())
+
+ List failures =
+ new ArrayList()
+ for (org.junit.runner.notification.Failure failure : results.getFailures()) {
+ failures.add(new Failure(failure.getDescription().toString(), failure.getTrace()))
+ }
+
+ testResults.setFailures(failures.toArray(new Failure[0]))
+
+ return testResults
+ }
+
+}
diff --git a/spring-boot-cli/testers/tester.groovy b/spring-boot-cli/testers/tester.groovy
new file mode 100644
index 0000000000..1ee3a1da6f
--- /dev/null
+++ b/spring-boot-cli/testers/tester.groovy
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+import org.springframework.boot.cli.command.tester.TestResults
+
+/**
+ * Groovy script define abstract basis for automated testers for {@link TestCommand}.
+ * Needs to be compiled along with the actual code to work properly.
+ *
+ * @author Greg Turnquist
+ */
+public abstract class AbstractTester {
+
+ public TestResults findAndTest(List> compiled) throws FileNotFoundException {
+ Set> testable = findTestableClasses(compiled)
+
+ if (testable.size() == 0) {
+ return TestResults.none
+ }
+
+ return test(testable.toArray(new Class>[0]))
+ }
+
+ abstract protected Set> findTestableClasses(List> compiled)
+
+ abstract protected TestResults test(Class>[] testable)
+
+}
diff --git a/spring-boot-cli/testscripts/book.groovy b/spring-boot-cli/testscripts/book.groovy
new file mode 100644
index 0000000000..ca50f38075
--- /dev/null
+++ b/spring-boot-cli/testscripts/book.groovy
@@ -0,0 +1,4 @@
+class Book {
+ String author
+ String title
+}
\ No newline at end of file
diff --git a/spring-boot-cli/testscripts/book_and_tests.groovy b/spring-boot-cli/testscripts/book_and_tests.groovy
new file mode 100644
index 0000000000..7283392946
--- /dev/null
+++ b/spring-boot-cli/testscripts/book_and_tests.groovy
@@ -0,0 +1,12 @@
+class Book {
+ String author
+ String title
+}
+
+class BookTests {
+ @Test
+ void testBooks() {
+ Book book = new Book(author: "Tom Clancy", title: "Threat Vector")
+ assertEquals("Tom Clancy", book.author)
+ }
+}
\ No newline at end of file
diff --git a/spring-boot-cli/testscripts/empty.groovy b/spring-boot-cli/testscripts/empty.groovy
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/spring-boot-cli/testscripts/spock.groovy b/spring-boot-cli/testscripts/spock.groovy
new file mode 100644
index 0000000000..360649bf61
--- /dev/null
+++ b/spring-boot-cli/testscripts/spock.groovy
@@ -0,0 +1,12 @@
+class HelloSpock extends Specification {
+ def "length of Spock's and his friends' names"() {
+ expect:
+ name.size() == length
+
+ where:
+ name | length
+ "Spock" | 5
+ "Kirk" | 4
+ "Scotty" | 6
+ }
+}
\ No newline at end of file
diff --git a/spring-boot-cli/testscripts/test.groovy b/spring-boot-cli/testscripts/test.groovy
new file mode 100644
index 0000000000..cdd00a5999
--- /dev/null
+++ b/spring-boot-cli/testscripts/test.groovy
@@ -0,0 +1,7 @@
+class BookTests {
+ @Test
+ void testBooks() {
+ Book book = new Book(author: "Tom Clancy", title: "Threat Vector")
+ assertEquals("Tom Clancy", book.author)
+ }
+}
\ No newline at end of file
diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml
index 9c85441306..1de90eb2fe 100644
--- a/spring-boot-dependencies/pom.xml
+++ b/spring-boot-dependencies/pom.xml
@@ -32,6 +32,7 @@
3.0.1
1.7.5
1.12
+ 0.7-groovy-2.0
4.0.0.M3
3.2.0.RC1
2.2.4.RELEASE
@@ -39,7 +40,7 @@
2.2.0.RELEASE
1.4.1.RELEASE
1.3.1.RELEASE
- 1.2.0.RELEASE
+ 1.2.0.RELEASE
2.0.16
2.0.0
1.1.1
@@ -248,6 +249,18 @@
slf4j-jdk14
${slf4j.version}
+
+ org.spockframework
+ spock-core
+ ${spock.version}
+
+
+ org.codehaus.groovy
+ groovy-all
+
+
+ provided
+
org.springframework
spring-aop