### Question:
PlainAsyncCmdExecutor implements AsyncCmdExecutor { @Override public SingleProcessExecution<String> launchCommand(final String command, final Map<String, String> environmentVariables, final File workDir) { final Process process = cmdExecutor.launchCommand(command, environmentVariables, workDir); final CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { final StringBuilder output = new StringBuilder(); final StringBuilder errors = new StringBuilder(); try { final Thread stdReader = new Thread(() -> readOutputStream(command, output, new InputStreamReader(process.getInputStream()))); final Thread errReader = new Thread(() -> readOutputStream(command, errors, new InputStreamReader(process.getErrorStream()))); stdReader.start(); errReader.start(); final int exitCode = process.waitFor(); stdReader.join(); errReader.join(); if (exitCode == 0) { return output.toString(); } else { final String errorMessage = String.format("Command '%s' failed with the following stderr: %s", command, errors.toString()); log.error(errorMessage); throw new CmdExecutionException(errorMessage); } } catch (InterruptedException e) { throw new CmdExecutionException(String.format("Command '%s' execution was interrupted", command)); } }, executor); return new SingleProcessExecution<>(process, future); }  @Override SingleProcessExecution<String> launchCommand(final String command,
                                                        final Map<String, String> environmentVariables,
                                                        final File workDir);  }### Answer:
@Test public void executorShouldDelegateCallToInnerExecutor() { asyncCmdExecutor.launchCommand(COMMAND).get(); verify(innerCmdExecutor).launchCommand(eq(COMMAND), eq(Collections.emptyMap()), eq(null)); }
@Test public void executorShouldCollectProcessStdOutAsResult() { final String actualOut = asyncCmdExecutor.launchCommand(COMMAND).get(); assertThat(actualOut, is(STD_OUT + LINE_BREAK)); }
@Test public void executorShouldCollectProcessStdErrAsExceptionContentOnFailure() throws InterruptedException { when(process.waitFor()).thenReturn(BAD_EXIT_CODE); final Throwable throwable = asyncCmdExecutor.launchCommand(COMMAND).with(exceptionallyResult()).get(); assertThat(throwable.getMessage(), containsString(STD_ERR + LINE_BREAK)); }
@Test public void cancelShouldKillUnderlyingProcess() throws InterruptedException, IOException { final Process process = Runtime.getRuntime().exec(SLEEP_5); when(innerCmdExecutor.launchCommand(eq(COMMAND), any(), any())) .thenReturn(process); final Execution<String> execution = asyncCmdExecutor.launchCommand(COMMAND); Thread.sleep(SECOND); assertTrue(process.isAlive()); assertFalse(execution.isDone()); execution.cancel(); Thread.sleep(SECOND); assertFalse(process.isAlive()); assertTrue(execution.isDone()); }