From 165ba2eb4548a7949e6bb425897ada67a103c214 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Mon, 13 Jan 2014 12:17:23 +0000 Subject: [PATCH] Add some performance tests for InitCommand Fixed gh-212 again --- .../springframework/boot/cli/SpringCli.java | 20 ++- .../boot/cli/command/InitCommand.java | 17 ++- .../boot/cli/util/ResourceUtils.java | 132 ++++++++---------- .../command/InitCommandPerformanceTests.java | 116 +++++++++++++++ .../boot/cli/command/InitCommandTests.java | 22 ++- 5 files changed, 228 insertions(+), 79 deletions(-) create mode 100644 spring-boot-cli/src/test/java/org/springframework/boot/cli/command/InitCommandPerformanceTests.java diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/SpringCli.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/SpringCli.java index 498b8c55b3f..f1a5a4fc8a3 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/SpringCli.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/SpringCli.java @@ -57,10 +57,10 @@ public class SpringCli { /** * Create a new {@link SpringCli} implementation with the default set of commands. */ - public SpringCli() { + public SpringCli(String... args) { try { this.init = new InitCommand(this); - this.init.run(); + this.init.run(args); } catch (Exception e) { throw new IllegalStateException("Cannot init with those args", e); @@ -410,7 +410,21 @@ public class SpringCli { * @param args CLI arguments */ public static void main(String... args) { - int exitCode = new SpringCli().runAndHandleErrors(args); + String[] init = new String[1]; + for (String arg : args) { + if (arg.startsWith("--init")) { + init[0] = arg; + } + } + if (init[0] != null) { + String[] newargs = new String[args.length - 1]; + System.arraycopy(args, 1, newargs, 0, newargs.length); + args = newargs; + } + else { + init = new String[0]; + } + int exitCode = new SpringCli(init).runAndHandleErrors(args); if (exitCode != 0) { System.exit(exitCode); } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/InitCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/InitCommand.java index 1922894e901..f323f8e47eb 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/InitCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/InitCommand.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.ServiceLoader; import joptsimple.OptionSet; +import joptsimple.OptionSpec; import org.springframework.boot.cli.Command; import org.springframework.boot.cli.CommandFactory; @@ -35,6 +36,7 @@ import org.springframework.boot.cli.compiler.GroovyCompilerConfigurationAdapter; import org.springframework.boot.cli.compiler.GroovyCompilerScope; import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory; import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration; +import org.springframework.util.StringUtils; /** *

@@ -62,21 +64,32 @@ public class InitCommand extends OptionParsingCommand { private static class InitOptionHandler extends CompilerOptionHandler { + private static final String DEFAULT_PATH = "file:init.groovy,file:spring.groovy"; private SpringCli cli; private GroovyCompiler compiler; + private OptionSpec initOption; public InitOptionHandler(SpringCli cli) { this.cli = cli; } + @Override + protected void doOptions() { + this.initOption = option("init", + "Path to init file as comma-separated list (default file:init.groovy,file:spring.groovy)") + .withOptionalArg().defaultsTo( + System.getProperty("spring.cli.init", DEFAULT_PATH)); + } + @Override protected void run(OptionSet options) throws Exception { ClassLoader loader = Thread.currentThread().getContextClassLoader(); boolean enhanced = false; - SourceOptions sourceOptions = new SourceOptions(options, loader, - "init.groovy", "spring.groovy"); + String[] paths = StringUtils.commaDelimitedListToStringArray(this.initOption + .value(options)); + SourceOptions sourceOptions = new SourceOptions(options, loader, paths); String[] sources = sourceOptions.getSourcesArray(); if (!(loader instanceof GroovyClassLoader) && sources.length > 0) { diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/util/ResourceUtils.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/util/ResourceUtils.java index 7b0b9652703..8c1fe04bf58 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/util/ResourceUtils.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/util/ResourceUtils.java @@ -17,16 +17,20 @@ package org.springframework.boot.cli.util; import java.io.IOException; +import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; -import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.FileSystemResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -53,11 +57,6 @@ public abstract class ResourceUtils { */ public static final String FILE_URL_PREFIX = "file:"; - /** - * Wildcard character in source path - */ - private static final CharSequence WILDCARD = "*"; - /** * Return URLs from a given source path. Source paths can be simple file locations * (/some/file.java) or wildcard patterns (/some/**). Additionally the prefixes @@ -75,22 +74,7 @@ public abstract class ResourceUtils { path = StringUtils.cleanPath(path); try { - if (path.contains(WILDCARD)) { - return getUrlsFromWildcardPath(path, classLoader); - } - - if (path.contains(":")) { - return getUrlsFromPrefixedPath(path, classLoader); - } - - try { - return getUrlsFromFile(path); - } - catch (IOException ex) { - // ignore - } - - return getUrlsFromResources(path, classLoader); + return getUrlsFromWildcardPath(path, classLoader); } catch (Exception ex) { throw new IllegalArgumentException("Cannot create URL from path [" + path @@ -105,51 +89,36 @@ public abstract class ResourceUtils { return getUrlsFromPrefixedWildcardPath(path, classLoader); } + Set result = new LinkedHashSet(); try { - return getUrls(FILE_URL_PREFIX + path, classLoader); + result.addAll(getUrls(FILE_URL_PREFIX + path, classLoader)); } catch (IllegalArgumentException ex) { // ignore } - return getUrls(ALL_CLASSPATH_URL_PREFIX + path, classLoader); + path = stripLeadingSlashes(path); + result.addAll(getUrls(ALL_CLASSPATH_URL_PREFIX + path, classLoader)); + return new ArrayList(result); } private static List getUrlsFromPrefixedWildcardPath(String path, ClassLoader classLoader) throws IOException { - Resource[] resources = new PathMatchingResourcePatternResolver(classLoader) - .getResources(path); + Resource[] resources = new PathMatchingResourcePatternResolver( + new FileSearchResourceLoader(classLoader)).getResources(path); List result = new ArrayList(); for (Resource resource : resources) { - result.add(resource.getURL().toExternalForm()); - } - return result; - } - - private static List getUrlsFromPrefixedPath(String path, - ClassLoader classLoader) throws IOException { - if (path.startsWith(CLASSPATH_URL_PREFIX)) { - return getUrlsFromResources(path.substring(CLASSPATH_URL_PREFIX.length()), - classLoader); - } - return getUrlsFromFile(path); - } - - private static List getUrlsFromFile(String path) throws IOException { - Resource resource = new FileSystemResource(path); - if (resource.exists()) { - if (resource.getFile().isDirectory()) { - return getChildFiles(resource); + if (resource.exists()) { + if (resource.getURI().getScheme().equals("file")) { + if (resource.getFile().isDirectory()) { + result.addAll(getChildFiles(resource)); + continue; + } + } + result.add(resource.getURL().toExternalForm()); } - return Collections.singletonList(resource.getURL().toExternalForm()); } - - resource = new UrlResource(path); - if (resource.exists()) { - return Collections.singletonList(resource.getURL().toExternalForm()); - } - - return Collections.emptyList(); + return result; } private static List getChildFiles(Resource resource) throws IOException { @@ -164,24 +133,6 @@ public abstract class ResourceUtils { return childFiles; } - private static List getUrlsFromResources(String path, ClassLoader classLoader) { - path = stripLeadingSlashes(path); - List result = new ArrayList(); - if (classLoader != null) { - try { - Enumeration urls = classLoader.getResources(path); - while (urls.hasMoreElements()) { - URL url = urls.nextElement(); - result.add(url.toExternalForm()); - } - } - catch (IOException e) { - // Ignore - } - } - return result; - } - private static String stripLeadingSlashes(String path) { while (path.startsWith("/")) { path = path.substring(1); @@ -189,4 +140,39 @@ public abstract class ResourceUtils { return path; } + private static class FileSearchResourceLoader extends DefaultResourceLoader { + + private final FileSystemResourceLoader files; + + public FileSearchResourceLoader(ClassLoader classLoader) { + super(classLoader); + this.files = new FileSystemResourceLoader(); + } + + @Override + public Resource getResource(String location) { + Assert.notNull(location, "Location must not be null"); + if (location.startsWith(CLASSPATH_URL_PREFIX)) { + return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX + .length()), getClassLoader()); + } + else { + if (location.startsWith(FILE_URL_PREFIX)) { + Resource resource = this.files.getResource(location); + return resource; + } + try { + // Try to parse the location as a URL... + URL url = new URL(location); + return new UrlResource(url); + } + catch (MalformedURLException ex) { + // No URL -> resolve as resource path. + return getResourceByPath(location); + } + } + } + + } + } diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/InitCommandPerformanceTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/InitCommandPerformanceTests.java new file mode 100644 index 00000000000..056f5494d9a --- /dev/null +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/InitCommandPerformanceTests.java @@ -0,0 +1,116 @@ +/* + * 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.util.Random; +import java.util.ServiceLoader; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.springframework.boot.OutputCapture; +import org.springframework.boot.cli.Command; +import org.springframework.boot.cli.CommandFactory; +import org.springframework.boot.cli.SpringCli; + +import static org.mockito.Mockito.mock; + +/** + * @author Dave Syer + */ +public class InitCommandPerformanceTests { + + @Rule + public OutputCapture output = new OutputCapture(); + private SpringCli cli = mock(SpringCli.class); + private ClassLoader classLoader; + private Random random = new Random(); + + @Before + public void init() { + this.classLoader = Thread.currentThread().getContextClassLoader(); + } + + @After + public void close() { + Thread.currentThread().setContextClassLoader(this.classLoader); + } + + @Test + public void initDefault() throws Exception { + for (int i = 0; i < 100; i++) { + InitCommand command = new InitCommand(this.cli); + command.run(); + close(); + } + } + + @Test + // Fast... + public void initNonExistent() throws Exception { + for (int i = 0; i < 100; i++) { + InitCommand command = new InitCommand(this.cli); + command.run("--init=" + this.random.nextInt() + ".groovy"); + close(); + } + } + + @Test + // Fast... + public void fakeCommand() throws Exception { + for (int i = 0; i < 100; i++) { + Command command = new AbstractCommand("fake", "") { + @Override + public void run(String... args) throws Exception { + for (CommandFactory factory : ServiceLoader.load( + CommandFactory.class, Thread.currentThread() + .getContextClassLoader())) { + for (Command command : factory + .getCommands(InitCommandPerformanceTests.this.cli)) { + InitCommandPerformanceTests.this.cli.register(command); + } + } + } + }; + command.run("--init=" + this.random.nextInt() + ".groovy"); + close(); + } + } + + @Test + // Fast... + public void initNonExistentWithPrefix() throws Exception { + for (int i = 0; i < 100; i++) { + InitCommand command = new InitCommand(this.cli); + command.run("--init=file:" + this.random.nextInt() + ".groovy"); + close(); + } + } + + @Test + // There is an init.groovy on the test classpath so this succeeds + // Slow... + public void initFromClasspath() throws Exception { + for (int i = 0; i < 5; i++) { + InitCommand command = new InitCommand(this.cli); + command.run("--init=init.groovy"); + close(); + } + } + +} diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/InitCommandTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/InitCommandTests.java index f6020ef3b95..e211e4a39a4 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/InitCommandTests.java +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/InitCommandTests.java @@ -18,6 +18,8 @@ package org.springframework.boot.cli.command; import groovy.lang.GroovyClassLoader; +import java.util.Random; + import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -26,6 +28,7 @@ import org.springframework.boot.OutputCapture; import org.springframework.boot.cli.Command; import org.springframework.boot.cli.SpringCli; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; @@ -107,11 +110,28 @@ public class InitCommandTests { this.command.run("nonexistent.groovy"); } - // There is an init.groovy on the test classpath so this succeeds @Test public void initDefault() throws Exception { this.command.run(); + assertFalse(this.output.toString().contains("Hello Init")); + } + + @Test + public void initWithCommandline() throws Exception { + this.command.run("--init=init.groovy"); assertTrue(this.output.toString().contains("Hello Init")); } + public static void main(String[] args) throws Exception { + Random random = new Random(); + InitCommandTests test = new InitCommandTests(); + test.init(); + SpringCli cli = new SpringCli(); + while (true) { + InitCommand command = new InitCommand(cli); + command.run("--init=file:" + random.nextInt() + ".groovy"); + test.close(); + } + } + }