package net.jsunit; import com.opensymphony.xwork.config.ConfigurationProvider; import net.jsunit.configuration.CompositeConfigurationSource; import net.jsunit.configuration.ConfigurationSource; import net.jsunit.configuration.ServerConfiguration; import net.jsunit.logging.BrowserResultRepository; import net.jsunit.logging.FileBrowserResultRepository; import net.jsunit.model.Browser; import net.jsunit.model.BrowserLaunchSpecification; import net.jsunit.model.BrowserResult; import net.jsunit.model.ResultType; import net.jsunit.utility.StringUtility; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class JsUnitServer extends AbstractJsUnitServer implements BrowserTestRunner, WebServer { private static JsUnitServer instance; private List<TestRunListener> browserTestRunListeners = new ArrayList<TestRunListener>(); private ProcessStarter processStarter = new DefaultProcessStarter(); private TimeoutChecker timeoutChecker; private BrowserResultRepository browserResultRepository; private Map<Browser, Process> launchedBrowsersToProcesses = new HashMap<Browser, Process>(); private BrowserResult lastResult; public JsUnitServer(ServerConfiguration configuration) { this(configuration, new FileBrowserResultRepository(configuration.getLogsDirectory())); } public JsUnitServer(ServerConfiguration configuration, BrowserResultRepository repository) { super(configuration); setResultRepository(repository); registerInstance(this); } public static void main(String args[]) { try { ConfigurationSource source = CompositeConfigurationSource.forArguments(args); JsUnitServer server = new JsUnitServer(new ServerConfiguration(source)); server.start(); } catch (Throwable t) { t.printStackTrace(); } } public void accept(BrowserResult result) { Browser submittingBrowser = result.getBrowser(); endBrowser(submittingBrowser); for (TestRunListener listener : browserTestRunListeners) listener.browserTestRunFinished(submittingBrowser, result); launchedBrowsersToProcesses.remove(submittingBrowser); lastResult = result; } private void killTimeoutChecker() { if (timeoutChecker != null) { timeoutChecker.die(); timeoutChecker = null; } } public BrowserResult findResultWithId(String id, int browserId) throws InvalidBrowserIdException { Browser browser = configuration.getBrowserById(browserId); if (browser == null) throw new InvalidBrowserIdException(browserId); return findResultWithId(id, browser); } private BrowserResult findResultWithId(String id, Browser browser) { return browserResultRepository.retrieve(id, browser); } public String toString() { return "JsUnit Server"; } public List<Browser> getBrowsers() { return configuration.getBrowsers(); } public boolean isWaitingForBrowser(Browser browser) { return launchedBrowsersToProcesses.containsKey(browser); } public void addTestRunListener(TestRunListener listener) { browserTestRunListeners.add(listener); } public void removeTestRunListener(TestRunListener listener) { browserTestRunListeners.remove(listener); } public List<TestRunListener> getBrowserTestRunListeners() { return browserTestRunListeners; } private void endBrowser(Browser browser) { Process process = launchedBrowsersToProcesses.get(browser); if (process != null && configuration.shouldCloseBrowsersAfterTestRuns()) { if (!StringUtility.isEmpty(browser.getKillCommand())) { try { processStarter.execute(new String[]{browser.getKillCommand()}); } catch (IOException e) { e.printStackTrace(); } } else { process.destroy(); try { process.waitFor(); } catch (InterruptedException e) { e.printStackTrace(); } waitUntilProcessHasExitValue(process); } } killTimeoutChecker(); } private void waitUntilProcessHasExitValue(Process browserProcess) { while (true) { //noinspection EmptyCatchBlock try { if (browserProcess != null) browserProcess.exitValue(); return; } catch (IllegalThreadStateException e) { } } } public void launchBrowserTestRun(BrowserLaunchSpecification launchSpec) { long launchTime = System.currentTimeMillis(); Browser browser = launchSpec.getBrowser(); LaunchTestRunCommand command = new LaunchTestRunCommand(launchSpec, configuration); String displayName = browser.getDisplayName(); try { logger.info("Launching " + displayName + " on " + command.getTestURL()); for (TestRunListener listener : browserTestRunListeners) listener.browserTestRunStarted(browser); Process process = processStarter.execute(command.generateArray()); launchedBrowsersToProcesses.put(browser, process); startTimeoutChecker(launchTime, browser, process); } catch (Throwable throwable) { handleCrashWhileLaunching(throwable, browser); } } private void handleCrashWhileLaunching(Throwable throwable, Browser browser) { logger.info(failedToLaunchStatusMessage(browser, throwable)); BrowserResult failedToLaunchBrowserResult = new BrowserResult(); failedToLaunchBrowserResult._setResultType(ResultType.FAILED_TO_LAUNCH); failedToLaunchBrowserResult.setBrowser(browser); failedToLaunchBrowserResult._setServerSideException(throwable); accept(failedToLaunchBrowserResult); } private String failedToLaunchStatusMessage(Browser browser, Throwable throwable) { String result = "Browser " + browser.getDisplayName() + " failed to launch: " + throwable.getClass().getName(); if (throwable.getMessage() != null) result += (" - " + throwable.getMessage()); return result; } private void startTimeoutChecker(long launchTime, Browser browser, Process process) { timeoutChecker = new TimeoutChecker(process, browser, launchTime, this); timeoutChecker.start(); } void setProcessStarter(ProcessStarter starter) { this.processStarter = starter; } public void startTestRun() { for (TestRunListener listener : browserTestRunListeners) { listener.testRunStarted(); while (!listener.isReady()) //noinspection EmptyCatchBlock try { Thread.sleep(100); } catch (InterruptedException e) { } } } public void finishTestRun() { for (TestRunListener listener : browserTestRunListeners) listener.testRunFinished(); } public void dispose() { super.dispose(); for (Browser browser : new HashMap<Browser, Process>(launchedBrowsersToProcesses).keySet()) endBrowser(browser); } public int timeoutSeconds() { return configuration.getTimeoutSeconds(); } public BrowserResult lastResult() { return lastResult; } protected ConfigurationProvider createConfigurationProvider() { return new JsUnitServerConfigurationProvider(); } protected String resourceBase() { return configuration.getResourceBase().toString(); } public void setResultRepository(BrowserResultRepository repository) { this.browserResultRepository = repository; addTestRunListener(new BrowserResultLogWriter(browserResultRepository)); } protected List<String> servletNames() { List<String> result = new ArrayList<String>(); result.add("acceptor"); result.add("config"); result.add("displayer"); result.add("runner"); return result; } public static void registerInstance(JsUnitServer server) { JsUnitServer.instance = server; } public static JsUnitServer instance() { return instance; } }