/* * Copyright 2010 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.embedded; import com.google.common.base.Joiner; import com.google.common.collect.Lists; import com.google.inject.Binder; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Module; import com.google.inject.multibindings.Multibinder; import com.google.jstestdriver.ActionRunner; import com.google.jstestdriver.BrowserInfo; import com.google.jstestdriver.FileResult; import com.google.jstestdriver.FlagsParser; import com.google.jstestdriver.HttpServer; import com.google.jstestdriver.JsTestDriver; import com.google.jstestdriver.PluginLoader; import com.google.jstestdriver.TestCase; import com.google.jstestdriver.TestResult; import com.google.jstestdriver.config.CmdLineFlag; import com.google.jstestdriver.config.Configuration; import com.google.jstestdriver.config.ConfigurationException; import com.google.jstestdriver.config.InitializeModule; import com.google.jstestdriver.config.Initializer; import com.google.jstestdriver.config.UserConfigurationSource; import com.google.jstestdriver.config.YamlParser; import com.google.jstestdriver.hooks.JsTestDriverValidator; import com.google.jstestdriver.hooks.TestListener; import com.google.jstestdriver.model.BasePaths; import com.google.jstestdriver.model.ConcretePathPrefix; import com.google.jstestdriver.model.HandlerPathPrefix; import com.google.jstestdriver.model.NullPathPrefix; import com.google.jstestdriver.runner.RunnerMode; import com.google.jstestdriver.util.NullStopWatch; import java.io.File; import java.io.IOException; import java.util.List; import java.util.logging.LogManager; public class JsTestDriverImpl implements JsTestDriver { /** * Used to bind a {@link TestListener} into the JsTestDriver framework. * * @author Cory Smith (corbinrsmith@gmail.com) */ private static final class TestListenerModule implements Module { private final TestListener listener; /** * @param listener An instance of a {@link TestListener} to collect * results during a run. */ private TestListenerModule(TestListener listener) { this.listener = listener; } @Override public void configure(Binder binder) { Multibinder.newSetBinder(binder, TestListener.class).addBinding() .toInstance(listener); } } private static final class TestCaseCollector implements TestListener { private final List<TestCase> testCases = Lists.newLinkedList(); @Override public void onTestRegistered(BrowserInfo browser, TestCase testCase) { testCases.add(testCase); } @Override public void onTestComplete(TestResult testResult) { } @Override public void onFileLoad(BrowserInfo browserInfo, FileResult fileResult) { } @Override public void finish() { } /** * Returns a list of collected testcases from a run. */ public List<TestCase> getTestCases() { return testCases; } } private static final class TestResultCollector implements TestListener { private final List<TestResult> results = Lists.newLinkedList(); @Override public void onTestRegistered(BrowserInfo browser, TestCase testCase) { } @Override public void onTestComplete(TestResult testResult) { results.add(testResult); } @Override public void onFileLoad(BrowserInfo browserInfo, FileResult fileResult) { } @Override public void finish() { } /** * @return the results */ public List<TestResult> getResults() { return results; } } private Configuration defaultConfiguration; private PluginLoader pluginLoader; private List<Module> initializerModules; private RunnerMode runnerMode; private String[] defaultFlags; private int port; private List<Module> pluginModules; private BasePaths basePaths; private String serverAddress; private boolean raiseOnFailure; private boolean preload; private FlagsParser flagsParser; /** * @param configuration * @param pluginLoader * @param runnerMode * @param flags * @param pluginModules * @param basePaths * @param serverAddress * @param raiseOnFailure TODO * @param preload * @param flagsParser */ public JsTestDriverImpl( Configuration configuration, PluginLoader pluginLoader, RunnerMode runnerMode, String[] flags, int port, List<Module> pluginModules, List<Module> initializerModules, BasePaths basePaths, String serverAddress, boolean raiseOnFailure, boolean preload, FlagsParser flagsParser) { this.defaultConfiguration = configuration; this.pluginLoader = pluginLoader; this.initializerModules = initializerModules; this.runnerMode = runnerMode; this.defaultFlags = flags; this.port = port; this.pluginModules = pluginModules; this.basePaths = basePaths; this.serverAddress = serverAddress; this.raiseOnFailure = raiseOnFailure; this.preload = preload; this.flagsParser = flagsParser; } /* (non-Javadoc) * @see com.google.jstestdriver.IJsTestDriver#startServer() */ @Override public void startServer() { // TODO(corysmith): refactor these to do less hacking. if (port == -1) { throw new ConfigurationException("Port not defined, cannot start local server."); } if (preload) { runConfigurationWithFlags(defaultConfiguration, createFlagsArray("--port", String.valueOf(port), "--preloadFiles")); } else { runConfigurationWithFlags(defaultConfiguration, createFlagsArray("--port", String.valueOf(port))); } } /* (non-Javadoc) * @see com.google.jstestdriver.IJsTestDriver#stopServer() */ @Override public void stopServer() { // TODO(corysmith): refactor these to do less hacking. CmdLineFlag handlerPrefixFlag = findServerHandlerPrefixFlag(); HandlerPathPrefix prefix = new NullPathPrefix(); if (handlerPrefixFlag != null) { prefix = new ConcretePathPrefix(handlerPrefixFlag.value); } StringBuilder urlBuilder = new StringBuilder(defaultConfiguration.getServer(serverAddress, port, prefix)); urlBuilder.append("/"); urlBuilder.append("quit"); new HttpServer(new NullStopWatch()).ping(urlBuilder.toString()); } /* (non-Javadoc) * @see com.google.jstestdriver.IJsTestDriver#runConfiguration() */ @Override public List<TestResult> runConfiguration() { final List<TestResult> results = Lists.newArrayList(); runConfigurationWithFlags(defaultConfiguration, defaultFlags); return results; } /* (non-Javadoc) * @see com.google.jstestdriver.IJsTestDriver#runAllTests(java.lang.String) */ @Override public List<TestResult> runAllTests(String path) { return runAllTests(parseConfiguration(path)); } /* (non-Javadoc) * @see com.google.jstestdriver.IJsTestDriver#runAllTests(com.google.jstestdriver.config.Configuration) */ @Override public List<TestResult> runAllTests(Configuration config) { // TODO(corysmith): resolve how this should interact. It seems odd to resolve the server address twice, but we want to respect the configuration values. TestResultCollector testResultCollector = new TestResultCollector(); String server = config.getServer(serverAddress, port, new NullPathPrefix()); runConfigurationWithFlags(config, createFlagsArray("--tests", "all", "--server", server), new TestListenerModule(testResultCollector)); return testResultCollector.getResults(); } /* (non-Javadoc) * @see com.google.jstestdriver.IJsTestDriver#runTests(java.lang.String, java.util.List) */ @Override public List<TestResult> runTests(String path, List<String> tests) { return runTests(parseConfiguration(path), tests); } /* (non-Javadoc) * @see com.google.jstestdriver.IJsTestDriver#runTests(com.google.jstestdriver.config.Configuration, java.util.List) */ @Override public List<TestResult> runTests(Configuration config, List<String> tests) { // TODO(corysmith): Refactor to avoid passing string flags. TestResultCollector testResultCollector = new TestResultCollector(); String server = config.getServer(serverAddress, port, new NullPathPrefix()); runConfigurationWithFlags(config, createFlagsArray("--server", server, "--tests", Joiner.on(",").join(tests)), new TestListenerModule(testResultCollector)); return testResultCollector.getResults(); } /* (non-Javadoc) * @see com.google.jstestdriver.IJsTestDriver#reset() */ @Override public void reset() { String server = defaultConfiguration.getServer(serverAddress, port, new NullPathPrefix()); // TODO(corysmith): Refactor to avoid passing string flags. runConfigurationWithFlags(defaultConfiguration, createFlagsArray("--reset", "--server", server)); } /* (non-Javadoc) * @see com.google.jstestdriver.IJsTestDriver#getTestCasesFor(java.lang.String) */ @Override public List<TestCase> getTestCasesFor(String path) { return getTestCasesFor(parseConfiguration(path)); } /* (non-Javadoc) * @see com.google.jstestdriver.IJsTestDriver#getTestCasesFor(com.google.jstestdriver.config.Configuration) */ @Override public List<TestCase> getTestCasesFor(Configuration config) { TestCaseCollector testCaseCollector = new TestCaseCollector(); String server = config.getServer(serverAddress, port, new NullPathPrefix()); // TODO(corysmith): Refactor to avoid passing string flags. runConfigurationWithFlags(config, createFlagsArray("--dryRunFor", "all", "--server", server), new TestListenerModule(testCaseCollector)); return testCaseCollector.getTestCases(); } /* (non-Javadoc) * @see com.google.jstestdriver.IJsTestDriver#captureBrowser(java.lang.String) */ @Override public void captureBrowser(String browserPath) { throw new RuntimeException("not implemented"); } private String[] createFlagsArray(String...coreflags) { List<String> flags = Lists.newArrayList(coreflags); CmdLineFlag handlerPrefixFlag = findServerHandlerPrefixFlag(); if (handlerPrefixFlag != null) { handlerPrefixFlag.addToArgs(flags); } flags.add("--raiseOnFailure"); flags.add(Boolean.toString(raiseOnFailure)); String[] flagsArray = flags.toArray(new String[flags.size()]); return flagsArray; } private Configuration parseConfiguration(String path) { File configFile = new File(path); if (!configFile.exists()) { throw new ConfigurationException("Could not find " + configFile); } UserConfigurationSource userConfigurationSource = new UserConfigurationSource(configFile); return userConfigurationSource.parse(basePaths, new YamlParser()); } private void runConfigurationWithFlags(Configuration config, String[] flags, Module... additionalRunTimeModules) { createRunnerInjector( config, flags, additionalRunTimeModules).getInstance(ActionRunner.class).runActions(); } private Injector createRunnerInjector( Configuration config, String[] flags, Module... additionalRunTimeModules) { if (config == null) { throw new ConfigurationException("Configuration cannot be null."); } List<Module> initializeModules = Lists.newArrayList(initializerModules); BasePaths basePaths; try { // configure logging before we start seriously processing. LogManager.getLogManager().readConfiguration(runnerMode.getLogConfig()); System.out.println("setting runnermode " + runnerMode); basePaths = getPathResolver(config); initializeModules.add(new InitializeModule(pluginLoader, basePaths, flagsParser, runnerMode)); } catch (IOException e) { throw new ConfigurationException("Could not find " + config.getBasePaths(), e); } Injector initializeInjector = Guice.createInjector(initializeModules); List<Module> actionRunnerModules; actionRunnerModules = initializeInjector.getInstance(Initializer.class).initialize(pluginModules, config, runnerMode, flags); actionRunnerModules.addAll(Lists.newArrayList(additionalRunTimeModules)); Injector injector = Guice.createInjector(actionRunnerModules); return injector; } /** * Validates the current configuration. * @param validators A list of validations with direct access to the runtime * injector. */ void validate(JsTestDriverValidator... validatiors) { Injector injector = createRunnerInjector(this.defaultConfiguration, createFlagsArray()); for (JsTestDriverValidator validator : validatiors) { validator.validate(injector); } } private CmdLineFlag findServerHandlerPrefixFlag() { // TODO(corysmith): refactor these to do less hacking. for (int i = 0; i < defaultFlags.length; i++) { if (defaultFlags[i].startsWith("--serverHandlerPrefix")) { String flag = defaultFlags[i]; String value = ""; if (i + 1 < defaultFlags.length) { value = defaultFlags[i + 1]; } return new CmdLineFlag(flag, value); } } return null; } // TODO(corysmith): make this go away by resolving the multiple basePath issue. private BasePaths getPathResolver(Configuration config) { BasePaths mergedPaths = new BasePaths(); for (int i = 0; i < defaultFlags.length; i++) { if ("--basePath".equals(defaultFlags[i])) { if (i + 1 < defaultFlags.length) { for (String path : defaultFlags[i + 1].split(",")) { mergedPaths.add(new File(path)); } } break; } } mergedPaths = mergedPaths.merge(basePaths); return mergedPaths.merge(config.getBasePaths()); } }