/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* 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.jetbrains.kotlin.android.tests.run;
import com.google.common.base.Charsets;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.OSProcessHandler;
import com.intellij.execution.process.ProcessAdapter;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.execution.process.ProcessOutputTypes;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.android.tests.OutputUtils;
import java.io.Closeable;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
public class RunUtils {
private RunUtils() {
}
public static class RunSettings {
public final GeneralCommandLine commandLine;
public final String input;
public final boolean waitForEnd;
public final String outputPrefix;
public final boolean printOutputAtAppearance;
public RunSettings(
GeneralCommandLine commandLine,
@Nullable String input,
boolean waitForEnd,
@Nullable String outputPrefix,
boolean printOutputAtAppearance
) {
this.commandLine = commandLine;
this.input = input;
this.waitForEnd = waitForEnd;
this.outputPrefix = outputPrefix;
this.printOutputAtAppearance = printOutputAtAppearance;
}
public RunSettings(GeneralCommandLine commandLine) {
this.commandLine = commandLine;
this.input = null;
this.waitForEnd = true;
this.outputPrefix = null;
this.printOutputAtAppearance = false;
}
@Override
public String toString() {
return "commandLine=" + commandLine.getCommandLineString() + " " +
"input=" + input + " " +
"waitForEnd=" + waitForEnd + " " +
"outputPrefix=" + outputPrefix + " " +
"printOutputAtAppearance=" + printOutputAtAppearance + " ";
}
}
public static RunResult execute(@NotNull GeneralCommandLine commandLine) {
return run(new RunSettings(commandLine));
}
public static RunResult execute(@NotNull RunSettings settings) {
assert settings.waitForEnd : "Use executeOnSeparateThread() instead";
return run(settings);
}
public static void executeOnSeparateThread(@NotNull RunSettings settings) {
assert !settings.waitForEnd : "Use execute() instead";
new Thread(() -> run(settings)).start();
}
private static RunResult run(RunSettings settings) {
System.out.println("RUN COMMAND: " + settings);
StringBuilder stdOut = new StringBuilder();
StringBuilder stdErr = new StringBuilder();
OSProcessHandler handler;
try {
handler = new OSProcessHandler(settings.commandLine.createProcess(), settings.commandLine.getCommandLineString(), Charsets.UTF_8);
if (settings.input != null) {
handler.getProcessInput().write(settings.input.getBytes());
}
close(handler.getProcessInput());
}
catch (ExecutionException | IOException e) {
return new RunResult(false, getStackTrace(e));
}
handler.addProcessListener(new ProcessAdapter() {
@Override
public void processTerminated(ProcessEvent event) {
System.out.println("TERMINATED: " + settings.commandLine);
super.processTerminated(event);
}
@Override
public void onTextAvailable(ProcessEvent event, Key outputType) {
String str = event.getText();
if (outputType == ProcessOutputTypes.STDOUT || outputType == ProcessOutputTypes.SYSTEM) {
appendToContent(stdOut, str);
}
else if (outputType == ProcessOutputTypes.STDERR) {
appendToContent(stdErr, str);
}
}
private synchronized void appendToContent(StringBuilder content, String line) {
if (settings.printOutputAtAppearance) {
System.out.println(getPrefixString() + StringUtil.trimTrailing(line));
System.out.flush();
}
else {
content.append(getPrefixString());
content.append(StringUtil.trimTrailing(line));
content.append("\n");
}
}
private String getPrefixString() {
return (settings.outputPrefix != null) ? settings.outputPrefix + " " : "";
}
});
handler.startNotify();
if (settings.waitForEnd) {
String timeoutAsString = System.getenv("kotlin.tests.android.timeout");
if (timeoutAsString == null) {
timeoutAsString = "30";
System.err.println("Default value for timeout used: timeout = 30 min. You can change it using 'kotlin.tests.android.timeout' environment variable");
}
int timeout;
try {
timeout = Integer.parseInt(timeoutAsString);
}
catch (NumberFormatException e) {
timeout = 30;
System.err.println("Timeout system property should be a number");
}
handler.waitFor(timeout * 60 * 1000);
if (!handler.isProcessTerminated()) {
System.out.println("Output before handler.isProcessTerminated() " + settings.commandLine);
System.out.println(stdOut);
System.err.println(stdErr);
return new RunResult(false, "Timeout exception: execution was terminated after ~20 min.");
}
}
else {
handler.waitFor();
}
int exitCode = handler.getProcess().exitValue();
if (exitCode != 0) {
return new RunResult(false, builderToString(stdOut) + builderToString(stdErr));
}
else {
String output = builderToString(stdOut) + builderToString(stdErr);
if (OutputUtils.isBuildFailed(output)) {
return new RunResult(false, output);
}
if (!settings.commandLine.getCommandLineString().contains("install")) {
System.out.print(output);
}
return new RunResult(true, output);
}
}
private static String builderToString(StringBuilder builder) {
return builder.length() > 0 ? builder.toString() : "";
}
public static void close(Closeable closeable) {
try {
if (closeable != null) {
closeable.close();
}
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
public static String getStackTrace(Throwable t) {
StringWriter writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
try {
printWriter.write(t.getMessage());
printWriter.write("\n");
t.printStackTrace(printWriter);
}
finally {
close(printWriter);
}
return writer.toString();
}
}