diff --git a/pom.xml b/pom.xml
index a5fc6365fc..c4279e4a38 100644
--- a/pom.xml
+++ b/pom.xml
@@ -46,6 +46,7 @@
spring-boot-integration-tests
+ spring-boot-cli-integration-tests
diff --git a/spring-boot-cli-integration-tests/pom.xml b/spring-boot-cli-integration-tests/pom.xml
new file mode 100644
index 0000000000..bc520793ff
--- /dev/null
+++ b/spring-boot-cli-integration-tests/pom.xml
@@ -0,0 +1,51 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-parent
+ 0.5.0.BUILD-SNAPSHOT
+ ../spring-boot-parent
+
+ spring-boot-cli-integration-tests
+
+ ${basedir}/..
+
+
+
+ junit
+ junit
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ unpack
+ generate-resources
+
+ unpack
+
+
+
+
+ ${project.groupId}
+ spring-boot-cli
+ ${project.version}
+ bin
+ zip
+ true
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-boot-cli-integration-tests/src/test/java/org/springframework/boot/cli/it/UsabilityTests.java b/spring-boot-cli-integration-tests/src/test/java/org/springframework/boot/cli/it/UsabilityTests.java
new file mode 100644
index 0000000000..4387707acb
--- /dev/null
+++ b/spring-boot-cli-integration-tests/src/test/java/org/springframework/boot/cli/it/UsabilityTests.java
@@ -0,0 +1,70 @@
+package org.springframework.boot.cli.it;
+
+import java.io.IOException;
+
+import org.junit.Test;
+import org.springframework.boot.cli.it.infrastructure.Cli;
+import org.springframework.boot.cli.it.infrastructure.CliInvocation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Andy Wilkinson
+ */
+public class UsabilityTests {
+
+ private final Cli cli = new Cli();
+
+ @Test
+ public void hintProducesListOfValidCommands() throws IOException,
+ InterruptedException {
+ CliInvocation cli = this.cli.invoke("hint");
+
+ assertEquals(0, cli.await());
+ assertEquals(0, cli.getErrorOutput().length());
+ assertEquals(6, cli.getStandardOutputLines().size());
+ }
+
+ @Test
+ public void invokingWithNoArgumentsDisplaysHelp() throws IOException,
+ InterruptedException {
+ CliInvocation cli = this.cli.invoke();
+
+ assertEquals(1, cli.await()); // TODO Should this be 0? Probably not.
+ assertEquals(0, cli.getErrorOutput().length());
+ assertTrue(cli.getStandardOutput().startsWith("usage:"));
+ }
+
+ @Test
+ public void unrecognizedCommandsAreHandledGracefully() throws IOException,
+ InterruptedException {
+ CliInvocation cli = this.cli.invoke("not-a-real-command");
+
+ assertEquals(1, cli.await());
+ assertTrue(
+ cli.getErrorOutput(),
+ cli.getErrorOutput().contains(
+ "'not-a-real-command' is not a valid command"));
+ assertEquals(0, cli.getStandardOutput().length());
+ }
+
+ @Test
+ public void version() throws IOException, InterruptedException {
+ CliInvocation cli = this.cli.invoke("version");
+
+ assertEquals(0, cli.await());
+ assertEquals(0, cli.getErrorOutput().length());
+ assertTrue(cli.getStandardOutput(),
+ cli.getStandardOutput().startsWith("Spring CLI v"));
+ }
+
+ @Test
+ public void help() throws IOException, InterruptedException {
+ CliInvocation cli = this.cli.invoke("help");
+
+ assertEquals(1, cli.await()); // TODO Should this be 0? Perhaps.
+ assertEquals(0, cli.getErrorOutput().length());
+ assertTrue(cli.getStandardOutput().startsWith("usage:"));
+ }
+}
diff --git a/spring-boot-cli-integration-tests/src/test/java/org/springframework/boot/cli/it/infrastructure/Cli.java b/spring-boot-cli-integration-tests/src/test/java/org/springframework/boot/cli/it/infrastructure/Cli.java
new file mode 100644
index 0000000000..b9bc3b9a3a
--- /dev/null
+++ b/spring-boot-cli-integration-tests/src/test/java/org/springframework/boot/cli/it/infrastructure/Cli.java
@@ -0,0 +1,48 @@
+package org.springframework.boot.cli.it.infrastructure;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author Andy Wilkinson
+ */
+public final class Cli {
+
+ public CliInvocation invoke(String... args) throws IOException {
+ return new CliInvocation(launchCli(args));
+ }
+
+ private Process launchCli(String... args) throws IOException {
+ List arguments = Arrays.asList(args);
+
+ List command = new ArrayList();
+ command.add(findLaunchScript().getAbsolutePath());
+ command.addAll(arguments);
+
+ return new ProcessBuilder(command).start();
+ }
+
+ private File findLaunchScript() {
+ File dependencyDirectory = new File("target/dependency");
+ if (dependencyDirectory.isDirectory()) {
+ File[] files = dependencyDirectory.listFiles();
+ if (files.length == 1) {
+ File binDirectory = new File(files[0], "bin");
+ if (isWindows()) {
+ return new File(binDirectory, "spring.bat");
+ }
+ else {
+ return new File(binDirectory, "spring");
+ }
+ }
+ }
+ throw new IllegalStateException("Could not find CLI launch script");
+ }
+
+ private boolean isWindows() {
+ return File.separatorChar == '\\';
+ }
+}
diff --git a/spring-boot-cli-integration-tests/src/test/java/org/springframework/boot/cli/it/infrastructure/CliInvocation.java b/spring-boot-cli-integration-tests/src/test/java/org/springframework/boot/cli/it/infrastructure/CliInvocation.java
new file mode 100644
index 0000000000..c4580e0c6e
--- /dev/null
+++ b/spring-boot-cli-integration-tests/src/test/java/org/springframework/boot/cli/it/infrastructure/CliInvocation.java
@@ -0,0 +1,84 @@
+package org.springframework.boot.cli.it.infrastructure;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Andy Wilkinson
+ */
+public final class CliInvocation {
+
+ private final StringBuffer errorOutput = new StringBuffer();
+
+ private final StringBuffer standardOutput = new StringBuffer();
+
+ private final Process process;
+
+ CliInvocation(Process process) {
+ this.process = process;
+
+ new Thread(new StreamReadingRunnable(this.process.getErrorStream(),
+ this.errorOutput)).start();
+ new Thread(new StreamReadingRunnable(this.process.getInputStream(),
+ this.standardOutput)).start();
+ }
+
+ public String getErrorOutput() {
+ return this.errorOutput.toString();
+ }
+
+ public String getStandardOutput() {
+ return this.standardOutput.toString();
+ }
+
+ public List getStandardOutputLines() {
+ BufferedReader reader = new BufferedReader(new StringReader(
+ this.standardOutput.toString()));
+ String line;
+ List lines = new ArrayList();
+ try {
+ while ((line = reader.readLine()) != null) {
+ lines.add(line);
+ }
+ }
+ catch (IOException e) {
+ throw new RuntimeException("Failed to read standard output");
+ }
+ return lines;
+ }
+
+ public int await() throws InterruptedException {
+ return this.process.waitFor();
+ }
+
+ private final class StreamReadingRunnable implements Runnable {
+
+ private final InputStream stream;
+
+ private final StringBuffer output;
+
+ private final byte[] buffer = new byte[4096];
+
+ private StreamReadingRunnable(InputStream stream, StringBuffer buffer) {
+ this.stream = stream;
+ this.output = buffer;
+ }
+
+ public void run() {
+ int read;
+ try {
+ while ((read = this.stream.read(this.buffer)) > 0) {
+ this.output.append(new String(this.buffer, 0, read));
+ }
+ }
+ catch (IOException e) {
+ // Allow thread to die
+ }
+ }
+ }
+
+}