From 1ce13cc2c277c27af0d29f62dbc4a8011723df5a Mon Sep 17 00:00:00 2001 From: Greg Turnquist Date: Thu, 3 Oct 2013 08:16:48 -0500 Subject: [PATCH] Add 'spring test [files]' command to compile and test code automatically - Look for JUnit test symbols, and add JUnit automatically - Look for Spock test symbols, and add Spock automatically - Based on what test libraries were used, invoke relevant embedded testers and accumulate results - Make it so that multiple testers can be invoked through a single 'test' command - Print out total results and write out detailed trace errors in results.txt - Update based on the new artifact resolution mechanism --- spring-boot-cli-properties/pom.xml | 5 + spring-boot-cli/.gitignore | 1 + spring-boot-cli/pom.xml | 24 ++ .../cli/command/DefaultCommandFactory.java | 2 +- .../boot/cli/command/TestCommand.java | 264 ++++++++++++++++++ .../cli/command/tester/AbstractTester.java | 39 +++ .../boot/cli/command/tester/Failure.java | 48 ++++ .../boot/cli/command/tester/TestResults.java | 94 +++++++ .../boot/cli/compiler/AstUtils.java | 46 ++- .../JUnitCompilerAutoConfiguration.java | 52 ++++ .../SpockCompilerAutoConfiguration.java | 49 ++++ ...oot.cli.compiler.CompilerAutoConfiguration | 2 + .../resources/META-INF/springcli.properties | 14 + .../boot/cli/SpringCliTests.java | 11 +- .../boot/cli/TestCommandIntegrationTests.java | 125 +++++++++ .../boot/cli/command/ScriptCommandTests.java | 2 - .../test/resources/commands/handler.groovy | 3 + spring-boot-cli/testers/junit.groovy | 68 +++++ spring-boot-cli/testers/spock.groovy | 62 ++++ spring-boot-cli/testers/tester.groovy | 41 +++ spring-boot-cli/testscripts/book.groovy | 4 + .../testscripts/book_and_tests.groovy | 12 + spring-boot-cli/testscripts/empty.groovy | 0 spring-boot-cli/testscripts/spock.groovy | 12 + spring-boot-cli/testscripts/test.groovy | 7 + spring-boot-dependencies/pom.xml | 15 +- 26 files changed, 990 insertions(+), 12 deletions(-) create mode 100644 spring-boot-cli/.gitignore create mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/cli/command/TestCommand.java create mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/cli/command/tester/AbstractTester.java create mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/cli/command/tester/Failure.java create mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/cli/command/tester/TestResults.java create mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/JUnitCompilerAutoConfiguration.java create mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpockCompilerAutoConfiguration.java create mode 100644 spring-boot-cli/src/main/resources/META-INF/springcli.properties create mode 100644 spring-boot-cli/src/test/java/org/springframework/boot/cli/TestCommandIntegrationTests.java create mode 100644 spring-boot-cli/testers/junit.groovy create mode 100644 spring-boot-cli/testers/spock.groovy create mode 100644 spring-boot-cli/testers/tester.groovy create mode 100644 spring-boot-cli/testscripts/book.groovy create mode 100644 spring-boot-cli/testscripts/book_and_tests.groovy create mode 100644 spring-boot-cli/testscripts/empty.groovy create mode 100644 spring-boot-cli/testscripts/spock.groovy create mode 100644 spring-boot-cli/testscripts/test.groovy 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