diff --git a/src/main/java/net/hostsharing/hsadminng/system/SystemProcess.java b/src/main/java/net/hostsharing/hsadminng/system/SystemProcess.java new file mode 100644 index 00000000..149c6019 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/system/SystemProcess.java @@ -0,0 +1,57 @@ +package net.hostsharing.hsadminng.system; + +import lombok.Getter; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; + +public class SystemProcess { + private final ProcessBuilder processBuilder; + + @Getter + private String stdOut; + @Getter + private String stdErr; + + public SystemProcess(final String... command) { + this.processBuilder = new ProcessBuilder(command); + } + + public int execute() throws IOException, InterruptedException { + final var process = processBuilder.start(); + stdOut = fetchOutput(process.getInputStream()); // yeah, twisted ProcessBuilder API + stdErr = fetchOutput(process.getErrorStream()); + return process.waitFor(); + } + + public int execute(final String input) throws IOException, InterruptedException { + final var process = processBuilder.start(); + feedInput(input, process); + stdOut = fetchOutput(process.getInputStream()); // yeah, twisted ProcessBuilder API + stdErr = fetchOutput(process.getErrorStream()); + return process.waitFor(); + } + + private static void feedInput(final String input, final Process process) throws IOException { + try ( + final OutputStreamWriter stdIn = new OutputStreamWriter(process.getOutputStream()); // yeah, twisted ProcessBuilder API + final BufferedWriter writer = new BufferedWriter(stdIn)) { + writer.write(input); + writer.flush(); + } + } + + private static String fetchOutput(final InputStream inputStream) throws IOException { + final var output = new StringBuilder(); + try (final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + for (String line; (line = reader.readLine()) != null; ) { + output.append(line).append(System.lineSeparator()); + } + } + return output.toString(); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/system/SystemProcessTest.java b/src/test/java/net/hostsharing/hsadminng/system/SystemProcessTest.java new file mode 100644 index 00000000..e1924adf --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/system/SystemProcessTest.java @@ -0,0 +1,81 @@ +package net.hostsharing.hsadminng.system; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; + +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; +import static org.junit.jupiter.api.condition.OS.LINUX; + +class SystemProcessTest { + + @Test + @EnabledOnOs(LINUX) + void shouldExecuteAndFetchOutput() throws IOException, InterruptedException { + // given + final var process = new SystemProcess("bash", "-c", "echo 'Hello, World!'; echo 'Error!' >&2"); + + // when + final var returnCode = process.execute(); + + // then + assertThat(returnCode).isEqualTo(0); + assertThat(process.getStdOut()).isEqualTo("Hello, World!\n"); + assertThat(process.getStdErr()).isEqualTo("Error!\n"); + } + + @Test + @EnabledOnOs(LINUX) + void shouldReturnErrorCode() throws IOException, InterruptedException { + // given + final var process = new SystemProcess("false"); + + // when + final int returnCode = process.execute(); + + // then + assertThat(returnCode).isEqualTo(1); + } + + @Test + @EnabledOnOs(LINUX) + void shouldExecuteAndFeedInput() throws IOException, InterruptedException { + // given + final var process = new SystemProcess("tr", "[:lower:]", "[:upper:]"); + + // when + final int returnCode = process.execute("Hallo"); + + // then + assertThat(returnCode).isEqualTo(0); + assertThat(process.getStdOut()).isEqualTo("HALLO\n"); + } + + @Test + void shouldThrowExceptionIfProgramNotFound() { + // given + final var process = new SystemProcess("non-existing program"); + + // when + final var exception = catchThrowable(process::execute); + + // then + assertThat(exception).isInstanceOf(IOException.class) + .hasMessage("Cannot run program \"non-existing program\": error=2, No such file or directory"); + } + + @Test + void shouldBeAbleToRunMultipleTimes() throws IOException, InterruptedException { + // given + final var process = new SystemProcess("true"); + + // when + process.execute(); + final int returnCode = process.execute(); + + // then + assertThat(returnCode).isEqualTo(0); + } +}