package net.serenitybdd.integration.jenkins.process; import org.jdeferred.Promise; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.validation.constraints.NotNull; import java.io.IOException; import java.nio.file.Path; import java.util.*; import java.util.regex.Matcher; import static java.lang.String.format; import static java.util.Arrays.asList; public class JenkinsProcess { private static final Logger Log = LoggerFactory.getLogger(JenkinsProcess.class); private static final int Startup_Timeout = 5 * 60 * 1000; public static final String JENKINS_IS_FULLY_UP_AND_RUNNING = "Jenkins is fully up and running"; private final ProcessBuilder process; private final int port; private Process jenkinsProcess; private final Thread shutdownHook = new Thread() { @Override public void run() { jenkinsProcess.destroy(); } }; private JenkinsLogWatcher jenkinsLogWatcher; private Thread jenkinsLogWatcherThread; public JenkinsProcess(@NotNull Path java, @NotNull Path jenkinsWar, @NotNull int port, @NotNull Path jenkinsHome) { Log.debug("jenkins.war: {}", jenkinsWar.toAbsolutePath()); Log.debug("JENKINS_HOME: {}", jenkinsHome.toAbsolutePath()); this.port = port; Map<String, String> env = new HashMap<>(); env.put("JENKINS_HOME", jenkinsHome.toAbsolutePath().toString()); env.put("JAVA_HOME", java.getParent().getParent().toAbsolutePath().toString()); process = process(java, "-Duser.language=en", "-Dhudson.Main.development=true", "-jar", jenkinsWar.toString(), "--ajp13Port=-1", "--httpPort=" + port ).directory(jenkinsHome.toFile()); process.environment().putAll(Collections.unmodifiableMap(env)); process.redirectErrorStream(true); } public void start() throws IOException { jenkinsProcess = start(process); jenkinsLogWatcher = new JenkinsLogWatcher(jenkinsProcess.getInputStream()); jenkinsLogWatcherThread = new Thread(jenkinsLogWatcher, "jenkins"); jenkinsLogWatcherThread.start(); Runtime.getRuntime().addShutdownHook(shutdownHook); Promise<Matcher, ?, ?> portConflictDetected = jenkinsLogWatcher.watchFor("java.net.BindException: Address already in use"); Promise<Matcher, ?, ?> jenkinsStarted = jenkinsLogWatcher.watchFor(JENKINS_IS_FULLY_UP_AND_RUNNING); try { jenkinsStarted.waitSafely(Startup_Timeout); if (! jenkinsStarted.isResolved()) { throw new RuntimeException(format("Jenkins failed to start within %s seconds, aborting the test.", Startup_Timeout)); } Log.info("Jenkins is now available at http://localhost:{}", port); } catch (InterruptedException e) { throw portConflictDetected.isResolved() ? new RuntimeException(format("Couldn't start Jenkins on port '%s', the port is already in use", port), e) : new RuntimeException("Couldn't start Jenkins", e); } } public Promise<Matcher, ?, ?> promiseWhen(String logLine) { return jenkinsLogWatcher.watchFor(logLine); } public void waitUntil(String logLine) { try { jenkinsLogWatcher.watchFor(logLine).waitSafely(Startup_Timeout); } catch (InterruptedException e) { throw new RuntimeException(format("Did not see '%s' in the Jenkins log within %s ms", logLine, Startup_Timeout), e); } } public void stop() { Log.info("Stopping Jenkins..."); jenkinsProcess.destroy(); Runtime.getRuntime().removeShutdownHook(shutdownHook); Log.info("Jenkins stopped"); } private ProcessBuilder process(Path executable, String... arguments) { List<String> args = new ArrayList<>(windowsOrUnix(executable)); args.addAll(asList(arguments)); return new ProcessBuilder(args); } private Process start(ProcessBuilder jenkinsProcessBuilder) throws IOException { Log.info("Starting Jenkins on port {}...", port); Process startedProcess = jenkinsProcessBuilder.start(); startedProcess.getOutputStream().close(); return startedProcess; } private static String OS = System.getProperty("os.name").toLowerCase(); private List<String> windowsOrUnix(Path command) { return OS.contains("win") ? asList("cmd.exe", "/C", command.toString()) : asList(command.toString()); } }