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 { private static class ReadResult { String output; IOException exception; } public static ThreadReader start(Reader reader) { var lock = new Object(); var readResult = new ReadResult(); Thread.startVirtualThread(() -> { synchronized (lock) { try (reader) { readResult.output = reader.readAllAsString(); } catch (IOException exception) { readResult.exception = exception; } } }); return new ThreadReader(lock, readResult); } private final Object lock; private final ReadResult readResult; public ThreadReader(Object lock, ReadResult readResult) { this.lock = lock; this.readResult = readResult; } public String waitForString() throws IOException { synchronized (this.lock) { if (this.readResult.exception != null) { throw this.readResult.exception; } else { return this.readResult.output; } } } } }