@Override public StepExecutionResult execute(ExecutionContext context) throws InterruptedException { // Build the process, redirecting output to the provided output file. In general, // it's undesirable that both stdout and stderr are being redirected to the same // input stream. However, due to the nature of OS pipe buffering, we can't really // maintain the natural interleaving of multiple output streams in a way that we // can correctly associate both stdout/stderr streams to the one correct test out // of the many that ran. So, our best bet is to just combine them all into stdout, // so they get properly interleaved with the test start and end messages that we // use when we parse the test output. ProcessBuilder builder = new ProcessBuilder(); builder.command(command); builder.redirectOutput(filesystem.resolve(output).toFile()); builder.redirectErrorStream(true); Process process; try { process = BgProcessKiller.startProcess(builder); } catch (IOException e) { context.logError(e, "Error starting command %s", command); return StepExecutionResult.ERROR; } // Run the test process, saving the exit code. ProcessExecutor executor = context.getProcessExecutor(); ImmutableSet<ProcessExecutor.Option> options = ImmutableSet.of(ProcessExecutor.Option.EXPECTING_STD_OUT); ProcessExecutor.Result result = executor.execute( process, options, /* stdin */ Optional.<String>absent(), /* timeOutMs */ testRuleTimeoutMs, /* timeOutHandler */ Optional.<Function<Process, Void>>absent()); if (result.isTimedOut()) { throw new HumanReadableException( "Timed out after %d ms running test command %s", testRuleTimeoutMs.or(-1L), command); } // Since test binaries return a non-zero exit code when unittests fail, save the exit code // to a file rather than signalling a step failure. try (FileOutputStream stream = new FileOutputStream(filesystem.resolve(exitCode).toFile())) { stream.write((Integer.toString(result.getExitCode())).getBytes()); } catch (IOException e) { context.logError(e, "Error saving exit code to %s", exitCode); return StepExecutionResult.ERROR; } return StepExecutionResult.SUCCESS; }