package io.bootique.test.junit; import io.bootique.BQRuntime; import io.bootique.command.CommandOutcome; import io.bootique.log.BootLogger; import io.bootique.log.DefaultBootLogger; import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Function; /** * A wrapper around {@link BQRuntime} that runs it on the background and handles startup and shutdown sequence. */ public class BQRuntimeDaemon { private BootLogger logger; private BQRuntime runtime; private long startupTimeout; private TimeUnit startupTimeoutTimeUnit; private ExecutorService executor; private Function<BQRuntime, Boolean> startupCheck; private Optional<CommandOutcome> outcome; public BQRuntimeDaemon(BQRuntime runtime, Function<BQRuntime, Boolean> startupCheck, long startupTimeout, TimeUnit startupTimeoutTimeUnit) { // use a separate logger from the tested process to avoid mixing STDERR output this.logger = new DefaultBootLogger(false); this.runtime = runtime; this.startupCheck = startupCheck; this.executor = Executors.newCachedThreadPool(); this.outcome = Optional.empty(); this.startupTimeout = startupTimeout; this.startupTimeoutTimeUnit = startupTimeoutTimeUnit; } /** * @return an optional outcome, available if the test runtime has finished. * @since 0.16 */ public Optional<CommandOutcome> getOutcome() { return outcome; } public BQRuntime getRuntime() { return runtime; } public void start() { this.executor.submit(() -> outcome = Optional.of(runtime.run())); checkStartupSucceeded(startupTimeout, startupTimeoutTimeUnit); } protected void checkStartupSucceeded(long timeout, TimeUnit unit) { Future<Boolean> startupFuture = executor.submit(() -> { try { while (!startupCheck.apply(runtime)) { logger.stderr("Daemon runtime hasn't started yet..."); Thread.sleep(500); } return true; } catch (InterruptedException e) { logger.stderr("Timed out waiting for server to start"); return false; } catch (Throwable th) { logger.stderr("Server error", th); return false; } }); boolean success; try { success = startupFuture.get(timeout, unit); } catch (InterruptedException | ExecutionException | TimeoutException e) { throw new RuntimeException(String.format("Daemon failed to start in %s ms", unit.toMillis(timeout))); } if (success) { logger.stderr("Daemon runtime started..."); } else { throw new RuntimeException("Daemon failed to start"); } } public void stop() { runtime.shutdown(); // must interrupt execution (using "shutdown()" is not enough to stop // Jetty for instance executor.shutdownNow(); try { executor.awaitTermination(3, TimeUnit.SECONDS); logger.stderr("Daemon runtime stopped..."); } catch (InterruptedException e) { logger.stderr("Interrupted while waiting for shutdown", e); } } }