/* * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.google.jstestdriver.browser; import com.google.common.collect.Lists; import com.google.inject.Inject; import com.google.inject.name.Named; import com.google.jstestdriver.Action; import com.google.jstestdriver.BrowserAction; import com.google.jstestdriver.BrowserInfo; import com.google.jstestdriver.JsTestDriverClient; import com.google.jstestdriver.ResponseStream; import com.google.jstestdriver.RunTestsAction; import com.google.jstestdriver.TestErrors; import com.google.jstestdriver.browser.BrowserControl.BrowserControlFactory; import com.google.jstestdriver.model.RunData; import com.google.jstestdriver.util.RetryingCallable; import com.google.jstestdriver.util.StopWatch; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; /** * Executes each {@link BrowserAction} on each browser. * * @author jeremiele@google.com (Jeremie Lenfant-Engelmann) */ public class BrowserActionExecutorAction implements Action { private static final Logger logger = LoggerFactory.getLogger(BrowserActionExecutorAction.class); private final JsTestDriverClient client; private final List<BrowserAction> actions; private final ExecutorService executor; private final Set<BrowserRunner> browserRunners; private final String captureAddress; private final long testSuiteTimeout; private final StopWatch stopWatch; private final BrowserSessionManager sessionManager; private final BrowserControlFactory browserControlFactory; @Inject public BrowserActionExecutorAction(JsTestDriverClient client, List<BrowserAction> actions, ExecutorService executor, Set<BrowserRunner> browserRunners, @Named("captureAddress") String captureAddress, @Named("testSuiteTimeout") long testTimeout, StopWatch stopWatch, BrowserSessionManager sessionManager, BrowserControlFactory browserControlFactory) { this.client = client; this.actions = actions; this.executor = executor; this.browserRunners = browserRunners; this.captureAddress = captureAddress; this.testSuiteTimeout = testTimeout; this.stopWatch = stopWatch; this.sessionManager = sessionManager; this.browserControlFactory = browserControlFactory; } @Override public RunData run(RunData runData) { stopWatch.start("run %s", actions); logger.trace("Starting BrowserActions {}.", actions); Collection<BrowserInfo> browsers = client.listBrowsers(); if (browsers.size() == 0 && browserRunners.size() == 0 && actions.size() > 0) { throw new RuntimeException("No browsers available, yet actions " + actions + " requested. " + "If running against a persistent server please capture browsers. "+ "Otherwise, ensure that browsers are defined."); } // TODO(corysmith): Change the threaded action runner to // return useful information about a run. List<Callable<Collection<ResponseStream>>> runners = Lists.newLinkedList(); for (BrowserInfo browserInfo : browsers) { runners.add(new BrowserActionRunner(browserInfo.getId().toString(), client, actions, stopWatch, runData.getTestCases(), sessionManager)); logger.debug("Queueing BrowserActionRunner {} for {}.", actions, browserInfo); } for (BrowserRunner runner : browserRunners) { String browserId = client.getNextBrowserId(); final BrowserActionRunner actionRunner = new BrowserActionRunner( browserId, client, actions, stopWatch, runData.getTestCases(), sessionManager); runners.add(createBrowserManagedRunner(runData, runner, browserId, actionRunner)); logger.debug("Queueing BrowserActionRunner {} for {}.", actions, runner); } List<Throwable> exceptions = Lists.newLinkedList(); long currentTimeout = testSuiteTimeout; try { final List<Future<Collection<ResponseStream>>> results = executor.invokeAll(runners, currentTimeout, TimeUnit.SECONDS); for (Future<Collection<ResponseStream>> result : results) { try { for (ResponseStream response : result.get()) { runData = runData.recordResponse(response); } } catch (CancellationException e) { exceptions.add(new RuntimeException( "Test run cancelled, exceeded " + currentTimeout + "s", e)); } catch (ExecutionException e) { exceptions.add(e.getCause()); } catch (Exception e) { exceptions.add(e); } } } catch (InterruptedException e) { throw new RuntimeException(e); } finally { // something isn't working.... executor.shutdownNow(); } logger.debug("Finished BrowserActions {}.", actions); // TODO(corysmith): Move this to the ActionRunner? runData.finish(); // finalizes the rundata collection. stopWatch.stop("run %s", actions); if (!exceptions.isEmpty()) { throw new TestErrors("Failures during test run.", exceptions); } return runData; } // TODO(corysmith): Pull this into a factory. private Callable<Collection<ResponseStream>> createBrowserManagedRunner(RunData runData, BrowserRunner runner, String browserId, BrowserActionRunner actionRunner) { return new RetryingCallable<Collection<ResponseStream>>(runner.getNumStartupTries(), new BrowserCallable<Collection<ResponseStream>>(actionRunner, browserId, browserControlFactory.create(runner, captureAddress, runData.getTestCases()))); } public List<BrowserAction> getActions() { return actions; } public RunTestsAction getRunTestsAction() { for (BrowserAction action : actions) { if (action instanceof RunTestsAction) { return (RunTestsAction) action; } } return null; } public JsTestDriverClient getClient() { return client; } }