Add ExitStatus to Command.run()

The main difference for now is the removal of the --nohup
(slightly hacky) option in TestCommand. Now a TestCommand
can signal to its caller that it wants to be hung up.

Fixes gh-975
This commit is contained in:
Dave Syer 2014-05-28 16:39:05 +01:00
parent 5e3cc95ccf
commit 3c6dfb72c5
21 changed files with 155 additions and 46 deletions

View File

@ -62,6 +62,85 @@ public interface Command {
* @param args command arguments (this will not include the command itself)
* @throws Exception
*/
void run(String... args) throws Exception;
ExitStatus run(String... args) throws Exception;
/**
* Encapsulation of the outcome of a command.
*
* @author Dave Syer
*
* @see ExitStatus#OK
* @see ExitStatus#ERROR
*
*/
public static class ExitStatus {
/**
* Generic "OK" exit status with zero exit code and hangup=fa;se
*/
public static ExitStatus OK = new ExitStatus(0, "OK");
/**
* Generic "not OK" exit status with non-zero exit code and hangup=true
*/
public static ExitStatus ERROR = new ExitStatus(-1, "ERROR", true);
private int code;
private String name;
private boolean hangup;
/**
* Create a new ExitStatus with an exit code and name as specified.
*
* @param code the exit code
* @param name the name
*/
public ExitStatus(int code, String name) {
this(code, name, false);
}
/**
* @param code the exit code
* @param name the name
* @param hangup true if it is OK for the caller to hangup
*/
public ExitStatus(int code, String name, boolean hangup) {
this.code = code;
this.name = name;
this.hangup = hangup;
}
/**
* An exit code appropriate for use in System.exit()
* @return an exit code
*/
public int getCode() {
return code;
}
/**
* A name describing the outcome
* @return a name
*/
public String getName() {
return name;
}
/**
* Flag to signal that the caller can (or should) hangup. A server process with
* non-daemon threads should set this to false.
* @return the flag
*/
public boolean isHangup() {
return hangup;
}
/**
* Convert the existing code to a hangup.
*
* @return a new ExitStatus with hangup=true
*/
public ExitStatus hangup() {
return new ExitStatus(this.code, this.name, true);
}
}
}

View File

@ -24,6 +24,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.springframework.boot.cli.command.Command.ExitStatus;
import org.springframework.boot.cli.util.Log;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@ -166,8 +167,9 @@ public class CommandRunner implements Iterable<Command> {
System.setProperty("debug", "true");
}
try {
run(argsWithoutDebugFlags);
return 0;
ExitStatus result = run(argsWithoutDebugFlags);
// The caller will hang up if it gets a non-zero status
return result==null ? 0 : result.isHangup() ? (result.getCode()>0 ? result.getCode() : 1) : 0;
}
catch (NoArgumentsException ex) {
showUsage();
@ -197,7 +199,7 @@ public class CommandRunner implements Iterable<Command> {
* @param args the arguments
* @throws Exception
*/
protected void run(String... args) throws Exception {
protected ExitStatus run(String... args) throws Exception {
if (args.length == 0) {
throw new NoArgumentsException();
}
@ -209,7 +211,7 @@ public class CommandRunner implements Iterable<Command> {
}
beforeRun(command);
try {
command.run(commandArguments);
return command.run(commandArguments);
}
finally {
afterRun(command);

View File

@ -48,8 +48,8 @@ public abstract class OptionParsingCommand extends AbstractCommand {
}
@Override
public final void run(String... args) throws Exception {
this.handler.run(args);
public final ExitStatus run(String... args) throws Exception {
return this.handler.run(args);
}
protected OptionHandler getHandler() {

View File

@ -85,7 +85,7 @@ public class HelpCommand extends AbstractCommand {
}
@Override
public void run(String... args) throws Exception {
public ExitStatus run(String... args) throws Exception {
if (args.length == 0) {
throw new NoHelpCommandArgumentsException();
}
@ -103,7 +103,7 @@ public class HelpCommand extends AbstractCommand {
if (command.getHelp() != null) {
Log.info(command.getHelp());
}
return;
return ExitStatus.OK;
}
}
throw new NoSuchCommandException(commandName);

View File

@ -42,7 +42,7 @@ public class HintCommand extends AbstractCommand {
}
@Override
public void run(String... args) throws Exception {
public ExitStatus run(String... args) throws Exception {
try {
int index = (args.length == 0 ? 0 : Integer.valueOf(args[0]) - 1);
List<String> arguments = new ArrayList<String>(args.length);
@ -64,7 +64,9 @@ public class HintCommand extends AbstractCommand {
}
catch (Exception ex) {
// Swallow and provide no hints
return ExitStatus.ERROR;
}
return ExitStatus.OK;
}
private void showCommandHints(String starting) {

View File

@ -32,8 +32,9 @@ public class VersionCommand extends AbstractCommand {
}
@Override
public void run(String... args) {
public ExitStatus run(String... args) {
Log.info("Spring CLI v" + getClass().getPackage().getImplementationVersion());
return ExitStatus.OK;
}
}

View File

@ -45,7 +45,7 @@ public class GrabCommand extends OptionParsingCommand {
private static final class GrabOptionHandler extends CompilerOptionHandler {
@Override
protected void run(OptionSet options) throws Exception {
protected ExitStatus run(OptionSet options) throws Exception {
SourceOptions sourceOptions = new SourceOptions(options);
List<RepositoryConfiguration> repositoryConfiguration = RepositoryConfigurationFactory
@ -60,6 +60,7 @@ public class GrabCommand extends OptionParsingCommand {
GroovyCompiler groovyCompiler = new GroovyCompiler(configuration);
groovyCompiler.compile(sourceOptions.getSourcesArray());
return ExitStatus.OK;
}
}

View File

@ -103,7 +103,7 @@ public class JarCommand extends OptionParsingCommand {
}
@Override
protected void run(OptionSet options) throws Exception {
protected ExitStatus run(OptionSet options) throws Exception {
List<?> nonOptionArguments = new ArrayList<Object>(
options.nonOptionArguments());
Assert.isTrue(nonOptionArguments.size() >= 2,
@ -127,6 +127,7 @@ public class JarCommand extends OptionParsingCommand {
dependencies.removeAll(classpath);
writeJar(output, compiledClasses, classpathEntries, dependencies);
return ExitStatus.OK;
}
private void deleteIfExists(File file) {

View File

@ -38,6 +38,7 @@ import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpecBuilder;
import org.springframework.boot.cli.command.Command.ExitStatus;
import org.springframework.boot.cli.command.OptionParsingCommand;
/**
@ -80,7 +81,7 @@ public class OptionHandler {
this.closure = closure;
}
public final void run(String... args) throws Exception {
public final ExitStatus run(String... args) throws Exception {
String[] argsToUse = args.clone();
for (int i = 0; i < argsToUse.length; i++) {
if ("-cp".equals(argsToUse[i])) {
@ -88,18 +89,29 @@ public class OptionHandler {
}
}
OptionSet options = getParser().parse(args);
run(options);
return run(options);
}
/**
* Run the command using the specified parsed {@link OptionSet}.
* @param options the parsed option set
* @return an ExitStatus
* @throws Exception
*/
protected void run(OptionSet options) throws Exception {
protected ExitStatus run(OptionSet options) throws Exception {
if (this.closure != null) {
this.closure.call(options);
Object result = this.closure.call(options);
if (result instanceof ExitStatus) {
return (ExitStatus) result;
}
if (result instanceof Boolean) {
return (Boolean) result ? ExitStatus.OK : ExitStatus.ERROR;
}
if (result instanceof Integer) {
return new ExitStatus((Integer) result, "Finished");
}
}
return ExitStatus.OK;
}
public String getHelp() {

View File

@ -85,7 +85,7 @@ public class RunCommand extends OptionParsingCommand {
}
@Override
protected synchronized void run(OptionSet options) throws Exception {
protected synchronized ExitStatus run(OptionSet options) throws Exception {
if (this.runner != null) {
throw new RuntimeException(
@ -105,6 +105,8 @@ public class RunCommand extends OptionParsingCommand {
this.runner = new SpringApplicationRunner(configuration,
sourceOptions.getSourcesArray(), sourceOptions.getArgsArray());
this.runner.compileAndRun();
return ExitStatus.OK;
}
/**

View File

@ -36,9 +36,10 @@ class ClearCommand extends AbstractCommand {
}
@Override
public void run(String... args) throws Exception {
public ExitStatus run(String... args) throws Exception {
this.consoleReader.setPrompt("");
this.consoleReader.clearScreen();
return ExitStatus.OK;
}
}

View File

@ -31,7 +31,7 @@ class ExitCommand extends AbstractCommand {
}
@Override
public void run(String... args) throws Exception {
public ExitStatus run(String... args) throws Exception {
throw new ShellExitException();
}

View File

@ -67,7 +67,7 @@ class ForkProcessCommand extends RunProcessCommand {
}
@Override
public void run(String... args) throws Exception {
public ExitStatus run(String... args) throws Exception {
List<String> fullArgs = new ArrayList<String>();
fullArgs.add("-cp");
fullArgs.add(System.getProperty("java.class.path"));
@ -75,6 +75,7 @@ class ForkProcessCommand extends RunProcessCommand {
fullArgs.add(this.command.getName());
fullArgs.addAll(Arrays.asList(args));
run(fullArgs);
return ExitStatus.OK;
}
}

View File

@ -35,7 +35,7 @@ public class PromptCommand extends AbstractCommand {
}
@Override
public void run(String... strings) throws Exception {
public ExitStatus run(String... strings) throws Exception {
if (strings.length > 0) {
for (String string : strings) {
this.prompts.pushPrompt(string + " ");
@ -44,6 +44,7 @@ public class PromptCommand extends AbstractCommand {
else {
this.prompts.popPrompt();
}
return ExitStatus.OK;
}
}

View File

@ -41,13 +41,19 @@ class RunProcessCommand extends AbstractCommand {
}
@Override
public void run(String... args) throws Exception {
run(Arrays.asList(args));
public ExitStatus run(String... args) throws Exception {
return run(Arrays.asList(args));
}
protected void run(Collection<String> args) throws IOException {
protected ExitStatus run(Collection<String> args) throws IOException {
this.process = new RunProcess(this.command);
this.process.run(args.toArray(new String[args.size()]));
int code = this.process.run(args.toArray(new String[args.size()]));
if (code == 0) {
return ExitStatus.OK;
}
else {
return new ExitStatus(code, "EXTERNAL_ERROR");
}
}
public boolean handleSigInt() {

View File

@ -32,8 +32,9 @@ public class ShellCommand extends AbstractCommand {
}
@Override
public void run(String... args) throws Exception {
public ExitStatus run(String... args) throws Exception {
new Shell().run();
return ExitStatus.OK;
}
}

View File

@ -17,7 +17,6 @@
package org.springframework.boot.cli.command.test;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.springframework.boot.cli.command.Command;
import org.springframework.boot.cli.command.OptionParsingCommand;
@ -47,22 +46,14 @@ public class TestCommand extends OptionParsingCommand {
private TestRunner runner;
@Override
protected void doOptions() {
option("nohup",
"Flag to indicate that the JVM should not exit when tests are finished");
}
@Override
protected void run(OptionSet options) throws Exception {
protected ExitStatus run(OptionSet options) throws Exception {
SourceOptions sourceOptions = new SourceOptions(options);
TestRunnerConfiguration configuration = new TestRunnerConfigurationAdapter(
options, this);
this.runner = new TestRunner(configuration, sourceOptions.getSourcesArray(),
sourceOptions.getArgsArray());
this.runner.compileAndRunTests();
if (!options.has("nohup")) {
System.exit(0); // TODO: non-zero if test fails?
}
return ExitStatus.OK.hangup();
}
/**
@ -77,5 +68,7 @@ public class TestCommand extends OptionParsingCommand {
super(options, optionHandler);
}
}
}
}

View File

@ -67,6 +67,8 @@ public class SpringBootCompilerAutoConfiguration extends CompilerAutoConfigurati
"org.springframework.boot.context.properties.ConfigurationProperties",
"org.springframework.boot.context.properties.EnableConfigurationProperties",
"org.springframework.boot.autoconfigure.EnableAutoConfiguration",
"org.springframework.boot.context.properties.ConfigurationProperties",
"org.springframework.boot.context.properties.EnableConfigurationProperties",
"org.springframework.boot.groovy.GrabMetadata");
imports.addStarImports("org.springframework.stereotype",
"org.springframework.scheduling.annotation");

View File

@ -28,8 +28,9 @@ public class CustomCommand extends AbstractCommand {
}
@Override
public void run(String... args) throws Exception {
public ExitStatus run(String... args) throws Exception {
System.err.println("Custom Command Hello");
return ExitStatus.OK;
}
}

View File

@ -75,10 +75,7 @@ public class CliTester implements TestRule {
}
public String test(String... args) throws Exception {
String[] argsToUse = new String[args.length + 1];
System.arraycopy(args, 0, argsToUse, 1, args.length);
argsToUse[0] = "--nohup";
Future<TestCommand> future = submitCommand(new TestCommand(), argsToUse);
Future<TestCommand> future = submitCommand(new TestCommand(), args);
this.commands.add(future.get(this.timeout, TimeUnit.MILLISECONDS));
return getOutput();
}

View File

@ -48,11 +48,11 @@ public class RunProcess {
this.command = command;
}
public void run(String... args) throws IOException {
run(Arrays.asList(args));
public int run(String... args) throws IOException {
return run(Arrays.asList(args));
}
protected void run(Collection<String> args) throws IOException {
protected int run(Collection<String> args) throws IOException {
ProcessBuilder builder = new ProcessBuilder(this.command);
builder.command().addAll(args);
builder.redirectErrorStream(true);
@ -74,6 +74,12 @@ public class RunProcess {
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
try {
return this.process.exitValue();
}
catch (IllegalThreadStateException e) {
return 1;
}
}
finally {
this.endTime = System.currentTimeMillis();