Skip to content

Commit

Permalink
CommandLine module upgrades (#560)
Browse files Browse the repository at this point in the history
* command lines module upgrades

* new tests

* missing dep

* missing dep

* release notes

* [Gradle Release Plugin] - new version commit:  '3.27.0-snapshot'.
  • Loading branch information
mageddo authored Sep 5, 2024
1 parent 3992930 commit 5a5dfb7
Show file tree
Hide file tree
Showing 16 changed files with 589 additions and 56 deletions.
3 changes: 3 additions & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 3.27.0
* Commandline module upgrade to be able to execute new usecases when creating new native int tests. #533

## 3.26.0
* Option to set config file path from the ENV, see the docs.

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version=3.26.0-snapshot
version=3.27.0-snapshot
106 changes: 54 additions & 52 deletions src/main/java/com/mageddo/commons/exec/CommandLines.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
package com.mageddo.commons.exec;

import lombok.Builder;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DaemonExecutor;
import org.apache.commons.exec.ExecuteException;
import org.apache.commons.exec.ExecuteResultHandler;
import org.apache.commons.exec.ExecuteWatchdog;
import org.apache.commons.exec.Executor;
import org.apache.commons.exec.PumpStreamHandler;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.time.Duration;

@Slf4j
public class CommandLines {

public static Result exec(String commandLine, Object... args) {
return exec(CommandLine.parse(String.format(commandLine, args)),
ExecuteWatchdog.INFINITE_TIMEOUT
ExecuteWatchdog.INFINITE_TIMEOUT
);
}

Expand All @@ -32,56 +28,62 @@ public static Result exec(CommandLine commandLine) {
}

public static Result exec(CommandLine commandLine, long timeout) {
final var out = new ByteArrayOutputStream();
final var executor = new DaemonExecutor();
final var streamHandler = new PumpStreamHandler(out);
executor.setStreamHandler(streamHandler);
int exitCode;
return exec(
Request.builder()
.commandLine(commandLine)
.timeout(Duration.ofMillis(timeout))
.build()
);
}

private static void registerProcessWatch(ProcessAccessibleDaemonExecutor executor) {
ProcessesWatchDog.instance()
.watch(executor::getProcess)
;
}

public static Result exec(CommandLine commandLine, ExecuteResultHandler handler) {
return exec(Request
.builder()
.commandLine(commandLine)
.handler(handler)
.build()
);
}

public static Result exec(Request request) {
final var executor = createExecutor();
executor.setStreamHandler(request.getStreamHandler());
Integer exitCode = null;
try {
executor.setWatchdog(new ExecuteWatchdog(timeout));
exitCode = executor.execute(commandLine);
executor.setWatchdog(new ExecuteWatchdog(request.getTimeoutInMillis()));
if (request.getHandler() != null) {
executor.execute(request.getCommandLine(), request.getEnv(), request.getHandler());
registerProcessWatch(executor);
} else {
exitCode = executor.execute(request.getCommandLine(), request.getEnv());
}
} catch (ExecuteException e) {
exitCode = e.getExitValue();
if (request.getHandler() != null) {
request.getHandler().onProcessFailed(e);
} else {
exitCode = e.getExitValue();
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
return Result
.builder()
.executor(executor)
.out(out)
.exitCode(exitCode)
.build();
.builder()
.executor(executor)
.processSupplier(executor::getProcess)
.out(request.getBestOut())
.exitCode(exitCode)
.request(request)
.build();
}

@Getter
@Builder
@ToString(of = {"exitCode"})
public static class Result {

@NonNull
private Executor executor;

@NonNull
private ByteArrayOutputStream out;

private int exitCode;

public String getOutAsString() {
return this.out.toString();
}

public Result checkExecution() {
if (this.executor.isFailure(this.getExitCode())) {
throw new ExecutionValidationFailedException(this);
}
return this;
}

public String toString(boolean printOut) {
return String.format(
"code=%d, out=%s",
this.exitCode, printOut ? this.getOutAsString() : null
);
}
private static ProcessAccessibleDaemonExecutor createExecutor() {
return new ProcessAccessibleDaemonExecutor();
}

}
36 changes: 36 additions & 0 deletions src/main/java/com/mageddo/commons/exec/DelegateOutputStream.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.mageddo.commons.exec;

import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.stream.Stream;

public class DelegateOutputStream extends OutputStream {

private final List<OutputStream> delegateOuts;

public DelegateOutputStream(OutputStream... delegateOuts) {
this.delegateOuts = Stream.of(delegateOuts).toList();
}

public DelegateOutputStream(List<OutputStream> delegateOuts) {
this.delegateOuts = delegateOuts;
}

@Override
public void write(int b) throws IOException {
for (final var delegateOut : this.delegateOuts) {
delegateOut.write(b);
}
}

@Override
public void close() throws IOException {
for (final var out : this.delegateOuts) {
try {
out.close();
} catch (IOException e) {
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package com.mageddo.commons.exec;

public class ExecutionValidationFailedException extends RuntimeException {
private final CommandLines.Result result;
private final Result result;

public ExecutionValidationFailedException(CommandLines.Result result) {
public ExecutionValidationFailedException(Result result) {
super(String.format("error, code=%d, error=%s", result.getExitCode(), result.getOutAsString()));
this.result = result;
}

public CommandLines.Result result() {
public Result result() {
return this.result;
}

public int getExitCode() {
return this.result.getExitCode();
}
}
16 changes: 16 additions & 0 deletions src/main/java/com/mageddo/commons/exec/NopResultHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.mageddo.commons.exec;

import org.apache.commons.exec.ExecuteException;
import org.apache.commons.exec.ExecuteResultHandler;

public class NopResultHandler implements ExecuteResultHandler {
@Override
public void onProcessComplete(int exitValue) {

}

@Override
public void onProcessFailed(ExecuteException e) {

}
}
42 changes: 42 additions & 0 deletions src/main/java/com/mageddo/commons/exec/PipedStream.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.mageddo.commons.exec;

import lombok.Getter;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.UncheckedIOException;

public class PipedStream extends OutputStream {

@Getter
private final PipedInputStream pipedIn;

private final DelegateOutputStream delegateOut;
private final OutputStream originalOut;

public PipedStream(final OutputStream out) {
try {
this.pipedIn = new PipedInputStream();
this.originalOut = out;
final var pout = new PipedOutputStream(this.pipedIn);
this.delegateOut = new DelegateOutputStream(out, pout);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

@Override
public void write(int b) throws IOException {
this.delegateOut.write(b);
}

public void close() throws IOException {
this.delegateOut.close();
}

OutputStream getOriginalOut() {
return originalOut;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.mageddo.commons.exec;

import lombok.Getter;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DaemonExecutor;

import java.io.File;
import java.io.IOException;
import java.util.Map;

@Getter
class ProcessAccessibleDaemonExecutor extends DaemonExecutor {

private Process process = null;

@Override
protected Process launch(CommandLine command, Map<String, String> env, File dir) throws IOException {
return this.process = super.launch(command, env, dir);
}
}
49 changes: 49 additions & 0 deletions src/main/java/com/mageddo/commons/exec/ProcessesWatchDog.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.mageddo.commons.exec;

import com.mageddo.commons.lang.Singletons;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;

@Slf4j
public class ProcessesWatchDog {

private List<Supplier<Process>> processes = new ArrayList<>();

public static ProcessesWatchDog instance() {
return Singletons.createOrGet(ProcessesWatchDog.class, ProcessesWatchDog::new);
}

public void watch(Supplier<Process> sup) {
this.processes.add(sup);
}

public void watch(Process process) {
this.processes.add(() -> process);
}

public void killAllProcesses() {
final var validProcesses = this.findValidProcesses();

log.debug("status=killing all processes, processes={}, valid={}", this.processes.size(), validProcesses.size());

validProcesses.forEach(process -> {
try {
process.destroy();
log.trace("status=killed, pid={}", process.pid());
} catch (Exception e) {
log.warn("status=unable to destroy, processId={}, msg={}", process.pid(), e.getMessage(), e);
}
});
}

private List<Process> findValidProcesses() {
return this.processes.stream()
.map(Supplier::get)
.filter(Objects::nonNull)
.toList();
}
}
Loading

0 comments on commit 5a5dfb7

Please sign in to comment.