Allow live output + output capture thanks to zt-exec (#184)

This commit is contained in:
Simon Baslé
2020-01-23 18:13:55 +01:00
committed by Marcin Grzejszczak
parent 0d5460e051
commit 353cbc1ab7
4 changed files with 89 additions and 93 deletions

View File

@@ -45,6 +45,7 @@
<javax.json.version>1.0.4</javax.json.version>
<jopt-simple.version>5.0.3</jopt-simple.version>
<fliptables.version>1.0.2</fliptables.version>
<zt.exec.version>1.11</zt.exec.version>
</properties>
<dependencyManagement>

View File

@@ -135,6 +135,11 @@
<version>${javax.json.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.zeroturnaround</groupId>
<artifactId>zt-exec</artifactId>
<version>${zt.exec.version}</version>
</dependency>
</dependencies>
<profiles>

View File

@@ -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) {

View File

@@ -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;
}
}
}