From 353cbc1ab74a4bc3175e02bb3465a30b8cb09e78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Basl=C3=A9?= Date: Thu, 23 Jan 2020 18:13:55 +0100 Subject: [PATCH] Allow live output + output capture thanks to zt-exec (#184) --- pom.xml | 1 + releaser-core/pom.xml | 5 + .../project/ProjectCommandExecutor.java | 73 +++++++------ .../project/ProjectCommandExecutorTests.java | 103 ++++++++---------- 4 files changed, 89 insertions(+), 93 deletions(-) diff --git a/pom.xml b/pom.xml index b2a21fe4..64092168 100644 --- a/pom.xml +++ b/pom.xml @@ -45,6 +45,7 @@ 1.0.4 5.0.3 1.0.2 + 1.11 diff --git a/releaser-core/pom.xml b/releaser-core/pom.xml index cdbb2bdd..bdfd71aa 100644 --- a/releaser-core/pom.xml +++ b/releaser-core/pom.xml @@ -135,6 +135,11 @@ ${javax.json.version} compile + + org.zeroturnaround + zt-exec + ${zt.exec.version} + diff --git a/releaser-core/src/main/java/releaser/internal/project/ProjectCommandExecutor.java b/releaser-core/src/main/java/releaser/internal/project/ProjectCommandExecutor.java index 4eb40a20..5278d900 100644 --- a/releaser-core/src/main/java/releaser/internal/project/ProjectCommandExecutor.java +++ b/releaser-core/src/main/java/releaser/internal/project/ProjectCommandExecutor.java @@ -18,7 +18,6 @@ package releaser.internal.project; import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; @@ -28,8 +27,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Scanner; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -37,6 +36,9 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.zeroturnaround.exec.ProcessExecutor; +import org.zeroturnaround.exec.ProcessResult; +import org.zeroturnaround.exec.stream.slf4j.Slf4jStream; import releaser.internal.ReleaserProperties; import releaser.internal.ReleaserPropertiesAware; @@ -179,8 +181,8 @@ public class ProjectCommandExecutor implements ReleaserPropertiesAware { return executor(projectRoot).runCommand(substitutedCommands, waitTimeInMinutes); } - ProcessExecutor executor(String workDir) { - return new ProcessExecutor(workDir); + ReleaserProcessExecutor executor(String workDir) { + return new ReleaserProcessExecutor(workDir); } public void publishDocs(ProjectVersion originalVersion, @@ -264,63 +266,64 @@ public class ProjectCommandExecutor implements ReleaserPropertiesAware { } -class ProcessExecutor implements ReleaserPropertiesAware { +class ReleaserProcessExecutor implements ReleaserPropertiesAware { - private static final Logger log = LoggerFactory.getLogger(ProcessExecutor.class); + private static final Logger log = LoggerFactory + .getLogger(ReleaserProcessExecutor.class); private static String[] OS_OPERATORS = { "|", "<", ">", "||", "&&" }; private String workingDir; - ProcessExecutor(String workingDir) { + ReleaserProcessExecutor(String workingDir) { this.workingDir = workingDir; } String runCommand(String[] commands, long waitTimeInMinutes) { + String workingDir = this.workingDir; + log.info("Will run the command from [{}] and wait for result for [{}] minutes", + workingDir, waitTimeInMinutes); + try { - String workingDir = this.workingDir; - log.info( - "Will run the command from [{}] and wait for result for [{}] minutes", - workingDir, waitTimeInMinutes); - ProcessBuilder builder = builder(commands, workingDir); - Process process = startProcess(builder); - boolean finished = process.waitFor(waitTimeInMinutes, TimeUnit.MINUTES); - if (!finished) { - log.error("The command hasn't managed to finish in [{}] minutes", - waitTimeInMinutes); - process.destroyForcibly(); - throw new IllegalStateException("Process waiting time of [" - + waitTimeInMinutes + "] minutes exceeded"); - } - if (process.exitValue() != 0) { + ProcessExecutor processExecutor = processExecutor(commands, workingDir) + .timeout(waitTimeInMinutes, TimeUnit.MINUTES); + final ProcessResult processResult = doExecute(processExecutor); + + int processExitValue = processResult.getExitValue(); + if (processExitValue != 0) { throw new IllegalStateException("The process has exited with exit code [" - + process.exitValue() + "]"); + + processExitValue + "]"); } - return convertStreamToString(process.getInputStream()); + return processResult.outputUTF8(); } catch (InterruptedException | IOException e) { - throw new IllegalStateException(e); + throw new IllegalStateException("Process execution failed", e); + } + catch (TimeoutException e) { + log.error("The command hasn't managed to finish in [{}] minutes", + waitTimeInMinutes); + throw new IllegalStateException("Process waiting time of [" + + waitTimeInMinutes + "] minutes exceeded", e); } } - private String convertStreamToString(InputStream is) { - Scanner scanner = new Scanner(is).useDelimiter("\\A"); - return scanner.hasNext() ? scanner.next() : ""; + ProcessResult doExecute(ProcessExecutor processExecutor) + throws IOException, InterruptedException, TimeoutException { + return processExecutor.execute(); } - Process startProcess(ProcessBuilder builder) throws IOException { - return builder.start(); - } - - ProcessBuilder builder(String[] commands, String workingDir) { + ProcessExecutor processExecutor(String[] commands, String workingDir) { String[] commandsToRun = commands; String lastArg = String.join(" ", commands); if (Arrays.stream(OS_OPERATORS).anyMatch(lastArg::contains)) { commandsToRun = commandToExecute(lastArg); } log.info("Will run the command [{}]", Arrays.toString(commandsToRun)); - return new ProcessBuilder(commandsToRun).directory(new File(workingDir)) - .inheritIO(); + return new ProcessExecutor().command(commandsToRun).destroyOnExit() + .readOutput(true) + .redirectOutputAlsoTo( + Slf4jStream.of(ReleaserProcessExecutor.class).asInfo()) + .directory(new File(workingDir)); } String[] commandToExecute(String lastArg) { diff --git a/releaser-core/src/test/java/releaser/internal/project/ProjectCommandExecutorTests.java b/releaser-core/src/test/java/releaser/internal/project/ProjectCommandExecutorTests.java index 5ce671c2..1de56957 100644 --- a/releaser-core/src/test/java/releaser/internal/project/ProjectCommandExecutorTests.java +++ b/releaser-core/src/test/java/releaser/internal/project/ProjectCommandExecutorTests.java @@ -16,18 +16,22 @@ package releaser.internal.project; +import java.io.BufferedOutputStream; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.net.URISyntaxException; import java.nio.file.Files; +import java.util.concurrent.TimeoutException; import org.junit.Assume; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import org.zeroturnaround.exec.ProcessExecutor; +import org.zeroturnaround.exec.ProcessResult; import releaser.internal.PomUpdateAcceptanceTests; import releaser.internal.ReleaserProperties; import releaser.internal.buildsystem.TestUtils; @@ -62,7 +66,7 @@ public class ProjectCommandExecutorTests { ProjectCommandExecutor projectBuilder(ReleaserProperties properties) { return new ProjectCommandExecutor(properties) { @Override - ProcessExecutor executor(String workingDir) { + ReleaserProcessExecutor executor(String workingDir) { return testExecutor(workingDir); } }; @@ -281,7 +285,7 @@ public class ProjectCommandExecutorTests { ReleaserProperties properties = new ReleaserProperties(); properties.getMaven().setDeployCommand("echo foo"); properties.setWorkingDir(tmpFile("/builder/resolved").getPath()); - new ProcessExecutor(properties.getWorkingDir()) + new ReleaserProcessExecutor(properties.getWorkingDir()) .runCommand(new String[] { "touch", "pom.xml" }, 1); ProjectCommandExecutor builder = projectBuilder(properties); @@ -311,7 +315,7 @@ public class ProjectCommandExecutorTests { ReleaserProperties properties = new ReleaserProperties(); properties.getMaven().setDeployCommand("echo foo"); properties.setWorkingDir(tmpFile("/builder/resolved").getPath()); - new ProcessExecutor(properties.getWorkingDir()) + new ReleaserProcessExecutor(properties.getWorkingDir()) .runCommand(new String[] { "touch", "pom.xml" }, 1); ProjectCommandExecutor builder = projectBuilder(properties); @@ -340,7 +344,7 @@ public class ProjectCommandExecutorTests { ReleaserProperties properties = new ReleaserProperties(); properties.getMaven().setDeployCommand("echo foo"); properties.setWorkingDir(tmpFile("/builder/resolved").getPath()); - new ProcessExecutor(properties.getWorkingDir()) + new ReleaserProcessExecutor(properties.getWorkingDir()) .runCommand(new String[] { "touch", "pom.xml" }, 1); ProjectCommandExecutor builder = projectBuilder(properties); @@ -412,10 +416,10 @@ public class ProjectCommandExecutorTests { properties.getBash().setPublishDocsCommands(new String[] { "ls -al", "echo {{version}} {{oldVersion}} {{nextVersion}}" }); properties.setWorkingDir(tmpFile("/builder/resolved").getPath()); - TestProcessExecutor executor = testExecutor(properties.getWorkingDir()); + TestReleaserProcessExecutor executor = testExecutor(properties.getWorkingDir()); ProjectCommandExecutor builder = new ProjectCommandExecutor(properties) { @Override - ProcessExecutor executor(String workingDir) { + ReleaserProcessExecutor executor(String workingDir) { return executor; } }; @@ -435,10 +439,10 @@ public class ProjectCommandExecutorTests { new String[] { "echo {{systemProps}} 1", "echo {{systemProps}} 2" }); properties.getBash().setSystemProperties("-Dhello=world -Dfoo=bar"); properties.setWorkingDir(tmpFile("/builder/resolved").getPath()); - TestProcessExecutor executor = testExecutor(properties.getWorkingDir()); + TestReleaserProcessExecutor executor = testExecutor(properties.getWorkingDir()); ProjectCommandExecutor builder = new ProjectCommandExecutor(properties) { @Override - ProcessExecutor executor(String workingDir) { + ReleaserProcessExecutor executor(String workingDir) { return executor; } }; @@ -457,10 +461,10 @@ public class ProjectCommandExecutorTests { properties.getBash() .setPublishDocsCommands(new String[] { "echo '{{version}}'" }); properties.setWorkingDir(tmpFile("/builder/resolved").getPath()); - TestProcessExecutor executor = testExecutor(properties.getWorkingDir()); + TestReleaserProcessExecutor executor = testExecutor(properties.getWorkingDir()); ProjectCommandExecutor builder = new ProjectCommandExecutor(properties) { @Override - ProcessExecutor executor(String workingDir) { + ReleaserProcessExecutor executor(String workingDir) { return executor; } }; @@ -478,10 +482,10 @@ public class ProjectCommandExecutorTests { properties.getBash().setGenerateReleaseTrainDocsCommand("echo '{{version}}'"); File resolved = tmpFile("/builder/resolved"); properties.setWorkingDir(resolved.getPath()); - TestProcessExecutor executor = testExecutor(properties.getWorkingDir()); + TestReleaserProcessExecutor executor = testExecutor(properties.getWorkingDir()); ProjectCommandExecutor builder = new ProjectCommandExecutor(properties) { @Override - ProcessExecutor executor(String workingDir) { + ReleaserProcessExecutor executor(String workingDir) { return executor; } }; @@ -513,11 +517,12 @@ public class ProjectCommandExecutorTests { properties.setWorkingDir(tmpFile("/builder/unresolved").getPath()); ProjectCommandExecutor builder = new ProjectCommandExecutor(properties) { @Override - ProcessExecutor executor(String workingDir) { - return new ProcessExecutor(properties.getWorkingDir()) { + ReleaserProcessExecutor executor(String workingDir) { + return new ReleaserProcessExecutor(properties.getWorkingDir()) { @Override - Process startProcess(ProcessBuilder builder) { - return processWithInvalidExitCode(); + ProcessResult doExecute(ProcessExecutor processExecutor) + throws IOException, InterruptedException, TimeoutException { + return new ProcessResult(1, null); } }; } @@ -528,42 +533,8 @@ public class ProjectCommandExecutorTests { "The process has exited with exit code [1]"); } - private Process processWithInvalidExitCode() { - return new Process() { - @Override - public OutputStream getOutputStream() { - return null; - } - - @Override - public InputStream getInputStream() { - return null; - } - - @Override - public InputStream getErrorStream() { - return null; - } - - @Override - public int waitFor() { - return 0; - } - - @Override - public int exitValue() { - return 1; - } - - @Override - public void destroy() { - - } - }; - } - - private TestProcessExecutor testExecutor(String workingDir) { - return new TestProcessExecutor(workingDir); + private TestReleaserProcessExecutor testExecutor(String workingDir) { + return new TestReleaserProcessExecutor(workingDir); } private File tmpFile(String relativePath) { @@ -582,19 +553,35 @@ public class ProjectCommandExecutorTests { return new ProjectVersion("foo", "0.100.0.BUILD-SNAPSHOT"); } - class TestProcessExecutor extends ProcessExecutor { + class TestReleaserProcessExecutor extends ReleaserProcessExecutor { int counter = 0; - TestProcessExecutor(String workingDir) { + TestReleaserProcessExecutor(String workingDir) { super(workingDir); } @Override - ProcessBuilder builder(String[] commands, String workingDir) { + ProcessExecutor processExecutor(String[] commands, String workingDir) { this.counter++; - return super.builder(commands, workingDir) - .redirectOutput(tmpFile("/builder/resolved/resolved.log")); + final ProcessExecutor processExecutor = super.processExecutor(commands, + workingDir); + + File tempFile = tmpFile("/builder/resolved/resolved.log"); + try { + tempFile.createNewFile(); + OutputStream fos = new BufferedOutputStream( + new FileOutputStream(tempFile)); + + return processExecutor + // use redirectOutputAlsoTo to avoid overriding all output + // destinations + .redirectOutput(fos); + } + catch (IOException e) { + e.printStackTrace(); + return processExecutor; + } } }