package org.zwobble.hobgoblin.compiler.testing; import java.io.IOException; import java.io.Reader; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.List; public class TestProcessRunner { public static TestProcessRunner command(List command) { return new TestProcessRunner( new ProcessBuilder().command(command) ); } private final ProcessBuilder processBuilder; private TestProcessRunner(ProcessBuilder processBuilder) { this.processBuilder = processBuilder; } public TestProcessRunner cwd(Path cwd) { this.processBuilder.directory(cwd.toFile()); return this; } public TestProcessRunner addEnvVar(String name, String value) { this.processBuilder.environment().put(name, value); return this; } /** * Run a process until it exits, capturing its stdout and stderr. */ public ExecutionResult runCaptureOutput() throws IOException, InterruptedException { var process = this.processBuilder.start(); var stdout = ThreadReader.start(process.inputReader(StandardCharsets.UTF_8)); var stderr = ThreadReader.start(process.errorReader(StandardCharsets.UTF_8)); var exitCode = process.waitFor(); return new ExecutionResult(exitCode, stdout.waitForString(), stderr.waitForString()); } private static class ThreadReader { public static ThreadReader start(Reader reader) { var lock = new Object(); var output = new String[1]; Thread.startVirtualThread(() -> { synchronized (lock) { try (reader) { output[0] = reader.readAllAsString(); } catch (IOException exception) { // TODO: handle this more elegantly. throw new RuntimeException(exception); } } }); return new ThreadReader(lock, output); } private final Object lock; private final String[] output; public ThreadReader(Object lock, String[] output) { this.lock = lock; this.output = output; } public String waitForString() { synchronized (this.lock) { return this.output[0]; } } } }