diff --git a/spring-boot-cli/src/main/groovy/testers/junit.groovy b/spring-boot-cli/src/main/groovy/testers/junit.groovy index b21fd4662d..4f86f6ba68 100644 --- a/spring-boot-cli/src/main/groovy/testers/junit.groovy +++ b/spring-boot-cli/src/main/groovy/testers/junit.groovy @@ -22,6 +22,7 @@ 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. diff --git a/spring-boot-cli/src/main/groovy/testers/tester.groovy b/spring-boot-cli/src/main/groovy/testers/tester.groovy index 1ee3a1da6f..ce286fe90a 100644 --- a/spring-boot-cli/src/main/groovy/testers/tester.groovy +++ b/spring-boot-cli/src/main/groovy/testers/tester.groovy @@ -16,6 +16,7 @@ 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. @@ -28,14 +29,14 @@ public abstract class AbstractTester { Set> testable = findTestableClasses(compiled) if (testable.size() == 0) { - return TestResults.none + return TestResults.NONE } return test(testable.toArray(new Class[0])) } - abstract protected Set> findTestableClasses(List> compiled) + protected abstract Set> findTestableClasses(List> compiled) - abstract protected TestResults test(Class[] testable) + protected abstract TestResults test(Class[] testable) } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/FileOptions.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/FileOptions.java new file mode 100644 index 0000000000..e6264883d9 --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/FileOptions.java @@ -0,0 +1,110 @@ +/* + * 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 java.io.File; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import joptsimple.OptionSet; + +/** + * Extract file options (anything following '--' in an {@link OptionSet}). + * + * @author Phillip Webb + * @author Dave Syer + * @author Greg Turnquist + */ +public class FileOptions { + + private List files; + + private List args; + + /** + * Create a new {@link FileOptions} instance. + * @param options the source option set + */ + public FileOptions(OptionSet options) { + this(options, null); + } + + /** + * Create a new {@link FileOptions} instance. + * @param optionSet the source option set + * @param classLoader an optional classloader used to try and load files that are not + * found directly. + */ + public FileOptions(OptionSet optionSet, ClassLoader classLoader) { + List nonOptionArguments = optionSet.nonOptionArguments(); + 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 = getFile(filename, classLoader); + if (file == null) { + throw new RuntimeException("Can't find " + filename); + } + files.add(file); + } + } + } + if (files.size() == 0) { + throw new RuntimeException("Please specify a file to run"); + } + this.files = Collections.unmodifiableList(files); + this.args = Collections.unmodifiableList(nonOptionArguments.subList(files.size(), + nonOptionArguments.size())); + } + + private File getFile(String filename, ClassLoader classLoader) { + File file = new File(filename); + if (file.isFile() && file.canRead()) { + return file; + } + if (classLoader != null) { + URL url = classLoader.getResource(filename); + if (url != null && url.toString().startsWith("file:")) { + return new File(url.toString().substring("file:".length())); + } + } + return null; + } + + public List getArgs() { + return this.args; + } + + public String[] getArgsArray() { + return this.args.toArray(new String[this.args.size()]); + } + + public List getFiles() { + return this.files; + } + + public File[] getFilesArray() { + return this.files.toArray(new File[this.files.size()]); + } + +} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/RunCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/RunCommand.java index e167f6b346..1f5f9b1376 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/RunCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/RunCommand.java @@ -17,9 +17,6 @@ package org.springframework.boot.cli.command; import java.awt.Desktop; -import java.io.File; -import java.util.ArrayList; -import java.util.List; import java.util.logging.Level; import joptsimple.OptionSet; @@ -94,13 +91,10 @@ public class RunCommand extends OptionParsingCommand { @Override protected void run(OptionSet options) throws Exception { - List nonOptionArguments = options.nonOptionArguments(); - File[] files = getFileArguments(nonOptionArguments); - List args = nonOptionArguments.subList(files.length, - nonOptionArguments.size()); + FileOptions fileOptions = new FileOptions(options); if (options.has(this.editOption)) { - Desktop.getDesktop().edit(files[0]); + Desktop.getDesktop().edit(fileOptions.getFiles().get(0)); } SpringApplicationRunnerConfiguration configuration = new SpringApplicationRunnerConfigurationAdapter( @@ -108,33 +102,11 @@ public class RunCommand extends OptionParsingCommand { if (configuration.isLocal() && System.getProperty("grape.root") == null) { System.setProperty("grape.root", "."); } - this.runner = new SpringApplicationRunner(configuration, files, - args.toArray(new String[args.size()])); + this.runner = new SpringApplicationRunner(configuration, + fileOptions.getFilesArray(), fileOptions.getArgsArray()); this.runner.compileAndRun(); } - private File[] getFileArguments(List nonOptionArguments) { - 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); - } - } - } - } - if (files.size() == 0) { - throw new RuntimeException("Please specify a file to run"); - } - return files.toArray(new File[files.size()]); - } - /** * Simple adapter class to present the {@link OptionSet} as a * {@link SpringApplicationRunnerConfiguration}. 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 index 042e74b0d9..322ec83ae7 100644 --- 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 @@ -21,10 +21,10 @@ import groovy.lang.GroovyObject; 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.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.logging.Level; @@ -32,6 +32,7 @@ import java.util.logging.Level; import joptsimple.OptionSet; import org.apache.ivy.util.FileUtil; +import org.codehaus.groovy.control.CompilationFailedException; import org.springframework.boot.cli.Log; import org.springframework.boot.cli.command.tester.Failure; import org.springframework.boot.cli.command.tester.TestResults; @@ -39,7 +40,7 @@ import org.springframework.boot.cli.compiler.GroovyCompiler; import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration; /** - * Invokes testing for autocompiled scripts + * Invokes testing for auto-compiled scripts * * @author Greg Turnquist */ @@ -63,6 +64,7 @@ public class TestCommand extends OptionParsingCommand { private static class TestGroovyCompilerConfiguration implements GroovyCompilerConfiguration { + @Override public boolean isGuessImports() { return true; @@ -99,10 +101,8 @@ public class TestCommand extends OptionParsingCommand { @Override protected void run(OptionSet options) throws Exception { - List nonOptionArguments = options.nonOptionArguments(); - - Set testerFiles = new HashSet(); - File[] files = getFileArguments(nonOptionArguments, testerFiles); + FileOptions fileOptions = new FileOptions(options, getClass() + .getClassLoader()); /* * Need to compile the code twice: The first time automatically pulls in @@ -112,44 +112,24 @@ public class TestCommand extends OptionParsingCommand { * 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 1 - collect testers + Object[] sources = this.compiler.sources(fileOptions.getFilesArray()); + Set testerFiles = compileAndCollectTesterFiles(sources); // Compile - Pass 2 - with appropriate testers added in - files = getFileArguments(nonOptionArguments, testerFiles); - sources = this.compiler.sources(files); - + List files = new ArrayList(fileOptions.getFiles()); + files.addAll(testerFiles); + sources = this.compiler.sources(files.toArray(new File[files.size()])); 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>(); + List> testers = new ArrayList>(); for (Object source : sources) { - if (source.getClass() == Class.class) { + if (source instanceof Class) { Class sourceClass = (Class) source; if (sourceClass.getSuperclass().getName().equals("AbstractTester")) { testers.add(sourceClass); @@ -161,21 +141,47 @@ public class TestCommand extends OptionParsingCommand { } 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) { + private Set compileAndCollectTesterFiles(Object[] sources) + throws CompilationFailedException, IOException { + Set testerFiles = new LinkedHashSet(); + addTesterOnClass(sources, "org.junit.Test", "junit", testerFiles); + addTesterOnClass(sources, "spock.lang.Specification", "spock", testerFiles); + if (!testerFiles.isEmpty()) { + testerFiles.add(createTempTesterFile("tester")); + } + + return testerFiles; + } + + private void addTesterOnClass(Object[] sources, String className, + String testerName, Set testerFiles) { + for (Object source : sources) { + if (source instanceof Class) { + try { + ((Class) source).getClassLoader().loadClass(className); + testerFiles.add(createTempTesterFile(testerName)); + return; + } + catch (ClassNotFoundException ex) { + } + } + } + } + + private File createTempTesterFile(String name) { try { File file = File.createTempFile(name, ".groovy"); file.deleteOnExit(); - FileUtil.copy(getClass().getClassLoader().getResourceAsStream(path), - file, null); + InputStream resource = getClass().getClassLoader().getResourceAsStream( + "testers/" + name + ".groovy"); + FileUtil.copy(resource, file, null); return file; } catch (IOException ex) { @@ -184,64 +190,6 @@ public class TestCommand extends OptionParsingCommand { } } - 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"); 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 index bef7dfbd86..ec7f19c2a8 100644 --- 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 @@ -20,20 +20,23 @@ import java.io.FileNotFoundException; import java.util.List; import java.util.Set; +/** + * Abstract base class for tester implementations. + * + * @author Greg Turnquist + */ public abstract class AbstractTester { - public TestResults findAndTest(List> compiled) throws FileNotFoundException { - Set> testable = findTestableClasses(compiled); + public TestResults findAndTest(List> compiled) throws FileNotFoundException { + Set> testable = findTestableClasses(compiled); + if (testable.size() == 0) { + return TestResults.NONE; + } + return test(testable.toArray(new Class[] {})); + } - if (testable.size() == 0) { - return TestResults.none; - } + protected abstract Set> findTestableClasses(List> compiled); - return test(testable.toArray(new Class[]{})); - } - - abstract protected Set> findTestableClasses(List> compiled); - - abstract protected TestResults test(Class[] testable); + protected abstract 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 index 1dead7929d..b827c2fc7f 100644 --- 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 @@ -18,31 +18,35 @@ 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 + * + * @author Greg Turnquist */ public class Failure { - private String description; - private String trace; - public Failure(String description, String trace) { - this.description = description; - this.trace = trace; - } + private String description; - public String getDescription() { - return description; - } + private String trace; - public void setDescription(String description) { - this.description = description; - } + public Failure(String description, String trace) { + this.description = description; + this.trace = trace; + } - public String getTrace() { - return trace; - } + public String getDescription() { + return this.description; + } - public void setTrace(String trace) { - this.trace = trace; - } + public void setDescription(String description) { + this.description = description; + } + + public String getTrace() { + return this.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 index d5f6344e4d..360f8c21c2 100644 --- 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 @@ -21,74 +21,78 @@ 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 + * + * NOTE: This is needed to avoid having to add JUnit's jar file to the deployable + * artifacts + * + * @author Greg Turnquist */ public class TestResults { - public static final NoTestResults none = new NoTestResults(); + public static final TestResults NONE = new TestResults() { - private int runCount; - private int failureCount; - private Failure[] failures = new Failure[0]; + @Override + public int getRunCount() { + return 0; + } - public void add(TestResults results) { - this.runCount += results.getRunCount(); - this.failureCount += results.getFailureCount(); + @Override + public int getFailureCount() { + return 0; + } - List failures = Arrays.asList(this.failures); - failures.addAll(Arrays.asList(results.getFailures())); - this.failures = failures.toArray(new Failure[]{}); - } + @Override + public Failure[] getFailures() { + return new Failure[0]; + } + @Override + public boolean wasSuccessful() { + return true; + } - public boolean wasSuccessful() { - return this.failureCount == 0; - } + }; - public int getRunCount() { - return runCount; - } + private int runCount; - public void setRunCount(int runCount) { - this.runCount = runCount; - } + private int failureCount; - public int getFailureCount() { - return failureCount; - } + private Failure[] failures = new Failure[0]; - public void setFailureCount(int failureCount) { - this.failureCount = failureCount; - } + 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 Failure[] getFailures() { - return failures; - } + public boolean wasSuccessful() { + return this.failureCount == 0; + } - public void setFailures(Failure[] failures) { - this.failures = failures; - } + public int getRunCount() { + return this.runCount; + } - private static class NoTestResults extends TestResults { - @Override - public int getRunCount() { - return 0; - } + public void setRunCount(int runCount) { + this.runCount = runCount; + } - @Override - public int getFailureCount() { - return 0; - } + public int getFailureCount() { + return this.failureCount; + } - @Override - public Failure[] getFailures() { - return new Failure[0]; - } + public void setFailureCount(int failureCount) { + this.failureCount = failureCount; + } - @Override - public boolean wasSuccessful() { - return true; - } - } -} \ No newline at end of file + public Failure[] getFailures() { + return this.failures; + } + + public void setFailures(Failure[] failures) { + this.failures = failures; + } + +}