ChildProcess2Impl implements ChildProcess2 { @Override public void waitForTermination() { Duration timeoutDuration = commandLine.getTimeout(); Instant timeout = timer.currentTime().plus(timeoutDuration); long maxOutputBytes = commandLine.getMaxOutputBytes(); final Duration pollInterval = Duration.ofSeconds(10); boolean hasTerminated = false; while (!hasTerminated) { Instant now = timer.currentTime(); long sleepPeriodMillis = pollInterval.toMillis(); if (now.plusMillis(sleepPeriodMillis).isAfter(timeout)) { sleepPeriodMillis = Duration.between(now, timeout).toMillis(); if (sleepPeriodMillis <= 0) { gracefullyKill(); throw new TimeoutChildProcessException( timeoutDuration, commandLine.toString(), getOutput()); } } try { hasTerminated = process.waitFor(sleepPeriodMillis, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { continue; } long sizeInBytes = uncheck(() -> Files.size(outputPath)); if (sizeInBytes > maxOutputBytes) { gracefullyKill(); throw new LargeOutputChildProcessException( sizeInBytes, commandLine.toString(), getOutput()); } } } ChildProcess2Impl(CommandLine commandLine,
                             ProcessApi2 process,
                             Path outputPath,
                             Timer timer); @Override void waitForTermination(); @Override int exitCode(); @Override String getOutput(); @Override void close();  }
@Test public void testSuccess() throws Exception { when(commandLine.getTimeout()).thenReturn(Duration.ofHours(1)); when(commandLine.getMaxOutputBytes()).thenReturn(10L); when(commandLine.getOutputEncoding()).thenReturn(StandardCharsets.UTF_8); when(commandLine.getSigTermGracePeriod()).thenReturn(Duration.ofMinutes(2)); when(commandLine.getSigKillGracePeriod()).thenReturn(Duration.ofMinutes(3)); when(commandLine.toString()).thenReturn("program arg"); when(timer.currentTime()).thenReturn( Instant.ofEpochMilli(1), Instant.ofEpochMilli(2)); when(processApi.waitFor(anyLong(), any())).thenReturn(true); try (ChildProcess2Impl child = new ChildProcess2Impl(commandLine, processApi, temporaryFile, timer)) { child.waitForTermination(); } }
@Test public void testTimeout() throws Exception { when(commandLine.getTimeout()).thenReturn(Duration.ofSeconds(1)); when(commandLine.getMaxOutputBytes()).thenReturn(10L); when(commandLine.getOutputEncoding()).thenReturn(StandardCharsets.UTF_8); when(commandLine.getSigTermGracePeriod()).thenReturn(Duration.ofMinutes(2)); when(commandLine.getSigKillGracePeriod()).thenReturn(Duration.ofMinutes(3)); when(commandLine.toString()).thenReturn("program arg"); when(timer.currentTime()).thenReturn( Instant.ofEpochSecond(0), Instant.ofEpochSecond(2)); when(processApi.waitFor(anyLong(), any())).thenReturn(true); try (ChildProcess2Impl child = new ChildProcess2Impl(commandLine, processApi, temporaryFile, timer)) { try { child.waitForTermination(); fail(); } catch (TimeoutChildProcessException e) { assertEquals( "Command 'program arg' timed out after PT1S: stdout/stderr: ''", e.getMessage()); } } }
@Test public void testMaxOutputBytes() throws Exception { when(commandLine.getTimeout()).thenReturn(Duration.ofSeconds(1)); when(commandLine.getMaxOutputBytes()).thenReturn(10L); when(commandLine.getOutputEncoding()).thenReturn(StandardCharsets.UTF_8); when(commandLine.getSigTermGracePeriod()).thenReturn(Duration.ofMinutes(2)); when(commandLine.getSigKillGracePeriod()).thenReturn(Duration.ofMinutes(3)); when(commandLine.toString()).thenReturn("program arg"); when(timer.currentTime()).thenReturn( Instant.ofEpochMilli(0), Instant.ofEpochMilli(1)); when(processApi.waitFor(anyLong(), any())).thenReturn(true); Files.write(temporaryFile, "1234567890123".getBytes(StandardCharsets.UTF_8)); try (ChildProcess2Impl child = new ChildProcess2Impl(commandLine, processApi, temporaryFile, timer)) { try { child.waitForTermination(); fail(); } catch (LargeOutputChildProcessException e) { assertEquals( "Command 'program arg' output more than 13 bytes: stdout/stderr: '1234567890123'", e.getMessage()); } } }
@Test public void testUnkillable() throws Exception { when(commandLine.getTimeout()).thenReturn(Duration.ofSeconds(1)); when(commandLine.getMaxOutputBytes()).thenReturn(10L); when(commandLine.getOutputEncoding()).thenReturn(StandardCharsets.UTF_8); when(commandLine.getSigTermGracePeriod()).thenReturn(Duration.ofMinutes(2)); when(commandLine.getSigKillGracePeriod()).thenReturn(Duration.ofMinutes(3)); when(commandLine.toString()).thenReturn("program arg"); when(timer.currentTime()).thenReturn( Instant.ofEpochMilli(0), Instant.ofEpochMilli(1)); when(processApi.waitFor(anyLong(), any())).thenReturn(false); Files.write(temporaryFile, "1234567890123".getBytes(StandardCharsets.UTF_8)); try (ChildProcess2Impl child = new ChildProcess2Impl(commandLine, processApi, temporaryFile, timer)) { try { child.waitForTermination(); fail(); } catch (UnkillableChildProcessException e) { assertEquals( "Command 'program arg' did not terminate even after SIGTERM, +PT2M, SIGKILL, and +PT3M: stdout/stderr: '1234567890123'", e.getMessage()); } } }