args) throws ProcessRunnerExcept
processRunner.run(command.toArray(new String[command.size()]));
// wait for start if configured
- if (runDevAppServerWaitListener != null) {
- runDevAppServerWaitListener.await();
+ if (outputLineListener != null) {
+ outputLineListener.await();
}
}
@@ -231,9 +267,12 @@ public Path getJarPath(String jarName) {
}
/**
- * Checks whether the configured Cloud SDK Path is valid.
+ * Checks whether the Cloud SDK is installed in the supplied path
+ * and the App Engine Java Components have been installed.
*
- * @throws AppEngineException when there is a validation error.
+ * @throws AppEngineComponentsNotInstalledException the App Engine Java
+ * components have not been installed
+ * @throws AppEngineException if a necessary component of the Cloud SDK cannot be found
*/
public void validate() throws AppEngineException {
if (sdkPath == null) {
@@ -253,9 +292,8 @@ public void validate() throws AppEngineException {
+ getDevAppServerPath() + "' is not a file.");
}
if (!Files.isDirectory(getJavaAppEngineSdkPath())) {
- throw new AppEngineException(
- "Validation Error: Java App Engine SDK location '"
- + getJavaAppEngineSdkPath() + "' is not a directory.");
+ throw new AppEngineComponentsNotInstalledException(
+ "Validation Error: App Engine Java component not installed");
}
if (!Files.isRegularFile(JAR_LOCATIONS.get(JAVA_TOOLS_JAR))) {
throw new AppEngineException(
@@ -266,7 +304,7 @@ public void validate() throws AppEngineException {
@VisibleForTesting
WaitingProcessOutputLineListener getRunDevAppServerWaitListener() {
- return runDevAppServerWaitListener;
+ return outputLineListener;
}
public static class Builder {
@@ -528,4 +566,5 @@ public int compare(CloudSdkResolver o1, CloudSdkResolver o2) {
}
}
+
}
diff --git a/src/main/java/com/google/cloud/tools/appengine/cloudsdk/internal/process/AccumulatingLineListener.java b/src/main/java/com/google/cloud/tools/appengine/cloudsdk/internal/process/AccumulatingLineListener.java
new file mode 100644
index 000000000..764babc0a
--- /dev/null
+++ b/src/main/java/com/google/cloud/tools/appengine/cloudsdk/internal/process/AccumulatingLineListener.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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 com.google.cloud.tools.appengine.cloudsdk.internal.process;
+
+import com.google.cloud.tools.appengine.cloudsdk.process.ProcessOutputLineListener;
+
+class AccumulatingLineListener implements ProcessOutputLineListener {
+
+ private StringBuilder output = new StringBuilder();
+
+ @Override
+ public void onOutputLine(String line) {
+ output.append(line + "\n");
+ }
+
+ String getOutput() {
+ return output.toString();
+ }
+
+ public void clear() {
+ output = new StringBuilder();
+ }
+
+}
diff --git a/src/main/java/com/google/cloud/tools/appengine/cloudsdk/internal/process/DefaultProcessRunner.java b/src/main/java/com/google/cloud/tools/appengine/cloudsdk/internal/process/DefaultProcessRunner.java
index 50e0d471c..816803a24 100644
--- a/src/main/java/com/google/cloud/tools/appengine/cloudsdk/internal/process/DefaultProcessRunner.java
+++ b/src/main/java/com/google/cloud/tools/appengine/cloudsdk/internal/process/DefaultProcessRunner.java
@@ -85,8 +85,9 @@ public DefaultProcessRunner(boolean async,
* If any output listeners were configured, output will go to them only. Otherwise, process
* output will be redirected to the caller via inheritIO.
*
- * @param command The shell command to execute
+ * @param command the shell command to execute
*/
+ @Override
public void run(String[] command) throws ProcessRunnerException {
try {
// Configure process builder.
@@ -131,10 +132,61 @@ public void run(String[] command) throws ProcessRunnerException {
throw new ProcessRunnerException(e);
}
}
+
+ /**
+ * Executes a not-too-long-lived shell command synchornously and returns stdout.
+ *
+ *
If any output listeners were configured, output will go to them only. Otherwise, process
+ * output will be redirected to the caller via inheritIO.
+ *
+ * @param command the shell command to execute
+ * @return everything printed on stdout
+ */
+ @Override
+ public String runSynchronously(String[] command) throws ProcessRunnerException {
+ try {
+ // Configure process builder.
+ final ProcessBuilder processBuilder = new ProcessBuilder();
+
+ // If there are no listeners, we might still want to redirect stdout and stderr to the parent
+ // process, or not.
+ if (stdErrLineListeners.isEmpty() && inheritProcessOutput) {
+ processBuilder.redirectError(Redirect.INHERIT);
+ }
+ if (environment != null) {
+ processBuilder.environment().putAll(environment);
+ }
+
+ processBuilder.command(command);
+
+ Process process = processBuilder.start();
+ AccumulatingLineListener stdOut = new AccumulatingLineListener();
+ stdOutLineListeners.add(stdOut);
+ // Only handle stdout or stderr if there are listeners.
+ handleStdOut(process);
+ if (!stdErrLineListeners.isEmpty()) {
+ handleErrOut(process);
+ }
+
+ for (ProcessStartListener startListener : startListeners) {
+ startListener.onStart(process);
+ }
+
+ shutdownProcessHook(process);
+ syncRun(process);
+ stdOutLineListeners.remove(stdOut);
+
+ return stdOut.getOutput();
+
+ } catch (IOException | InterruptedException | IllegalThreadStateException e) {
+ throw new ProcessRunnerException(e);
+ }
+ }
/**
* Environment variables to append to the current system environment variables.
*/
+ @Override
public void setEnvironment(Map environment) {
this.environment = environment;
}
@@ -142,6 +194,7 @@ public void setEnvironment(Map environment) {
private void handleStdOut(final Process process) {
final Scanner stdOut = new Scanner(process.getInputStream(), Charsets.UTF_8.name());
Thread stdOutThread = new Thread("standard-out") {
+ @Override
public void run() {
while (stdOut.hasNextLine() && !Thread.interrupted()) {
String line = stdOut.nextLine();
@@ -159,6 +212,7 @@ public void run() {
private void handleErrOut(final Process process) {
final Scanner stdErr = new Scanner(process.getErrorStream(), Charsets.UTF_8.name());
Thread stdErrThread = new Thread("standard-err") {
+ @Override
public void run() {
while (stdErr.hasNextLine() && !Thread.interrupted()) {
String line = stdErr.nextLine();
diff --git a/src/main/java/com/google/cloud/tools/appengine/cloudsdk/internal/process/ProcessRunner.java b/src/main/java/com/google/cloud/tools/appengine/cloudsdk/internal/process/ProcessRunner.java
index f261a66f2..440398040 100644
--- a/src/main/java/com/google/cloud/tools/appengine/cloudsdk/internal/process/ProcessRunner.java
+++ b/src/main/java/com/google/cloud/tools/appengine/cloudsdk/internal/process/ProcessRunner.java
@@ -22,6 +22,8 @@
public interface ProcessRunner {
void run(String[] command) throws ProcessRunnerException;
+
+ String runSynchronously(String[] command) throws ProcessRunnerException;
void setEnvironment(Map environment);
diff --git a/src/test/java/com/google/cloud/tools/appengine/cloudsdk/CloudSdkEnvironmentTest.java b/src/test/java/com/google/cloud/tools/appengine/cloudsdk/CloudSdkEnvironmentTest.java
new file mode 100644
index 000000000..df78b4394
--- /dev/null
+++ b/src/test/java/com/google/cloud/tools/appengine/cloudsdk/CloudSdkEnvironmentTest.java
@@ -0,0 +1,40 @@
+package com.google.cloud.tools.appengine.cloudsdk;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.nio.file.Files;
+
+import org.junit.Test;
+
+import com.google.cloud.tools.appengine.cloudsdk.internal.process.ProcessRunnerException;
+
+/**
+ * Integration tests for {@link CloudSdk} that require an installed CloudSdk instance.
+ */
+public class CloudSdkEnvironmentTest {
+
+ private CloudSdk sdk = new CloudSdk.Builder().build();
+
+ @Test
+ public void testGetSdkPath() {
+ assertTrue(Files.exists(sdk.getSdkPath()));
+ }
+
+ @Test
+ public void testIsComponentInstalled_true() throws ProcessRunnerException {
+ assertTrue(sdk.isComponentInstalled("app-engine-java"));
+ }
+
+ @Test
+ public void testIsComponentInstalled_False() throws ProcessRunnerException {
+ assertFalse(sdk.isComponentInstalled("no-such-component"));
+ }
+
+ @Test
+ public void testIsComponentInstalled_sequential() throws ProcessRunnerException {
+ assertTrue(sdk.isComponentInstalled("app-engine-java"));
+ assertFalse(sdk.isComponentInstalled("no-such-component"));
+ }
+
+}
diff --git a/src/test/java/com/google/cloud/tools/appengine/cloudsdk/CloudSdkTest.java b/src/test/java/com/google/cloud/tools/appengine/cloudsdk/CloudSdkTest.java
index 4319dac03..cb454d67a 100644
--- a/src/test/java/com/google/cloud/tools/appengine/cloudsdk/CloudSdkTest.java
+++ b/src/test/java/com/google/cloud/tools/appengine/cloudsdk/CloudSdkTest.java
@@ -5,9 +5,8 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;
-import com.google.cloud.tools.appengine.cloudsdk.CloudSdk.Builder;
-
import com.google.cloud.tools.appengine.api.AppEngineException;
+import com.google.cloud.tools.appengine.cloudsdk.CloudSdk.Builder;
import com.google.cloud.tools.appengine.cloudsdk.process.ProcessOutputLineListener;
import org.junit.Test;
import org.junit.runner.RunWith;