package org.keycloak.testsuite.cli.exec;
import org.keycloak.testsuite.cli.OsArch;
import org.keycloak.testsuite.cli.OsUtils;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public abstract class AbstractExec {
public static final String WORK_DIR = System.getProperty("user.dir");
public static final OsArch OS_ARCH = OsUtils.determineOSAndArch();
private long waitTimeout = 30000;
private Process process;
private int exitCode = -1;
private boolean logStreams = Boolean.valueOf(System.getProperty("cli.log.output", "true"));
protected boolean dumpStreams;
protected String workDir = WORK_DIR;
private String env;
private String argsLine;
private ByteArrayOutputStream stdout = new ByteArrayOutputStream();
private ByteArrayOutputStream stderr = new ByteArrayOutputStream();
private InputStream stdin = new InteractiveInputStream();
private Throwable err;
private Thread stdoutRunner;
private Thread stderrRunner;
public AbstractExec(String workDir, String argsLine, InputStream stdin) {
this(workDir, argsLine, null, stdin);
}
public AbstractExec(String workDir, String argsLine, String env, InputStream stdin) {
if (workDir != null) {
this.workDir = workDir;
}
this.argsLine = argsLine;
this.env = env;
if (stdin != null) {
this.stdin = stdin;
}
}
public abstract String getCmd();
public void execute() {
executeAsync();
if (err == null) {
waitCompletion();
}
}
public void executeAsync() {
try {
if (OS_ARCH.isWindows()) {
String cmd = (env != null ? "set " + env + " & " : "") + fixPath(getCmd()) + " " + fixQuotes(argsLine);
System.out.println("Executing: cmd.exe /c " + cmd);
process = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", cmd}, null, new File(workDir));
} else {
String cmd = (env != null ? env + " " : "") + getCmd() + " " + argsLine;
System.out.println("Executing: sh -c " + cmd);
process = Runtime.getRuntime().exec(new String[]{"sh", "-c", cmd}, null, new File(workDir));
}
stdoutRunner = new StreamReaderThread(process.getInputStream(), logStreams ? new LoggingOutputStream("STDOUT", stdout) : stdout);
stdoutRunner.start();
stderrRunner = new StreamReaderThread(process.getErrorStream(), logStreams ? new LoggingOutputStream("STDERR", stderr) : stderr);
stderrRunner.start();
new StreamReaderThread(stdin, process.getOutputStream())
.start();
} catch (Throwable t) {
err = t;
}
}
private String fixPath(String cmd) {
return cmd.replaceAll("/", "\\\\");
}
private String fixQuotes(String argsLine) {
argsLine = argsLine + " ";
argsLine = argsLine.replaceAll("\"", "\\\\\"");
argsLine = argsLine.replaceAll(" '", " \"");
argsLine = argsLine.replaceAll("' ", "\" ");
return argsLine;
}
public void waitCompletion() {
// This is necessary to make sure the process isn't stuck reading from stdin
if (stdin instanceof InteractiveInputStream) {
((InteractiveInputStream) stdin).close();
}
try {
if (process.waitFor(waitTimeout, TimeUnit.MILLISECONDS)) {
exitCode = process.exitValue();
if (exitCode != 0) {
dumpStreams = true;
}
// make sure reading output is really done (just in case)
stdoutRunner.join(5000);
stderrRunner.join(5000);
} else {
if (process.isAlive()) {
process.destroyForcibly();
}
throw new RuntimeException("Timeout after " + (waitTimeout / 1000) + " seconds.");
}
} catch (InterruptedException e) {
dumpStreams = true;
throw new RuntimeException("Interrupted ...", e);
} catch (Throwable t) {
dumpStreams = true;
err = t;
} finally {
if (!logStreams && dumpStreams) try {
System.out.println("STDOUT: ");
copyStream(new ByteArrayInputStream(stdout.toByteArray()), System.out);
System.out.println("STDERR: ");
copyStream(new ByteArrayInputStream(stderr.toByteArray()), System.out);
} catch (Exception ignored) {
}
}
}
public int exitCode() {
return exitCode;
}
public Throwable error() {
return err;
}
public InputStream stdout() {
return new ByteArrayInputStream(stdout.toByteArray());
}
public List<String> stdoutLines() {
return parseStreamAsLines(new ByteArrayInputStream(stdout.toByteArray()));
}
public String stdoutString() {
return new String(stdout.toByteArray());
}
public InputStream stderr() {
return new ByteArrayInputStream(stderr.toByteArray());
}
public List<String> stderrLines() {
return filterAgentsOutput(parseStreamAsLines(new ByteArrayInputStream(stderr.toByteArray())));
}
public static List<String> filterAgentsOutput(List<String> lines) {
return lines.stream().filter(line -> !line.contains("JAVA_TOOL_OPTIONS")).collect(Collectors.toList());
}
public String stderrString() {
return new String(stderr.toByteArray());
}
static List<String> parseStreamAsLines(InputStream stream) {
List<String> lines = new ArrayList<>();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
String line;
while ((line = reader.readLine()) != null) {
lines.add(line);
}
return lines;
} catch (IOException e) {
throw new RuntimeException("Unexpected I/O error", e);
}
}
public void waitForStdout(String content) {
long start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < waitTimeout) {
if (stdoutString().indexOf(content) != -1) {
return;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted ...", e);
}
}
throw new RuntimeException("Timed while waiting for content to appear in stdout");
}
public void sendToStdin(String s) {
if (stdin instanceof InteractiveInputStream) {
((InteractiveInputStream) stdin).pushBytes(s.getBytes());
} else {
throw new RuntimeException("Can't push to stdin - not interactive");
}
}
static void copyStream(InputStream is, OutputStream os) throws IOException {
byte [] buf = new byte[8192];
try (InputStream iss = is) {
int c;
while ((c = iss.read(buf)) != -1) {
os.write(buf, 0, c);
os.flush();
}
}
}
}