| /* |
| * 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.gwt.junit; |
| |
| import com.google.gwt.core.ext.TreeLogger; |
| import com.google.gwt.core.ext.UnableToCompleteException; |
| import com.google.gwt.dev.cfg.ConfigurationProperty; |
| import com.google.gwt.dev.cfg.ModuleDef; |
| import com.google.gwt.dev.cfg.ModuleDefLoader; |
| import com.google.gwt.dev.util.collect.HashSet; |
| import com.google.gwt.junit.JUnitShell.Strategy; |
| import com.google.gwt.junit.client.GWTTestCase; |
| import com.google.gwt.junit.client.GWTTestCase.TestModuleInfo; |
| import com.google.gwt.junit.client.impl.GWTRunner; |
| import com.google.gwt.junit.client.impl.JUnitHost.TestInfo; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * An interface that specifies how modules should be compiled. |
| */ |
| public abstract class CompileStrategy { |
| |
| /** |
| * The list of modules that have already been compiled. We use this to avoid |
| * adding test batches that have already been added. |
| */ |
| private Set<String> compiledModuleNames = new HashSet<String>(); |
| |
| private final JUnitShell junitShell; |
| |
| /** |
| * Construct a CompileStrategy. |
| * |
| * @param junitShell |
| */ |
| public CompileStrategy(JUnitShell junitShell) { |
| this.junitShell = junitShell; |
| } |
| |
| /** |
| * Maybe add a test block for the currently executed test case. |
| * |
| * @param testCase the test case being run |
| * @param batchingStrategy the batching strategy |
| */ |
| public void maybeAddTestBlockForCurrentTest(GWTTestCase testCase, |
| BatchingStrategy batchingStrategy) { |
| if (batchingStrategy.isSingleTestOnly()) { |
| TestInfo testInfo = new TestInfo(testCase.getSyntheticModuleName(), |
| testCase.getClass().getName(), testCase.getName()); |
| List<TestInfo[]> testBlocks = new ArrayList<TestInfo[]>(1); |
| testBlocks.add(new TestInfo[] {testInfo}); |
| getMessageQueue().addTestBlocks(testBlocks, false); |
| } |
| } |
| |
| /** |
| * Let the compile strategy compile another module. This is called while |
| * {@link JUnitShell} is waiting for the current test to complete. |
| * |
| * @throws UnableToCompleteException if the compilation fails |
| */ |
| @SuppressWarnings("unused") |
| public void maybeCompileAhead() throws UnableToCompleteException { |
| } |
| |
| /** |
| * Compile a single module using a synthetic module that adds JUnit support. |
| * |
| * @param moduleName the module name |
| * @param syntheticModuleName the synthetic module name |
| * @param strategy the strategy |
| * @param batchingStrategy the batching strategy |
| * @param treeLogger the logger |
| * @return the {@link ModuleDef} describing the synthetic module |
| * @throws UnableToCompleteException |
| */ |
| public abstract ModuleDef maybeCompileModule(String moduleName, |
| String syntheticModuleName, Strategy strategy, |
| BatchingStrategy batchingStrategy, TreeLogger treeLogger) |
| throws UnableToCompleteException; |
| |
| /** |
| * Compile a single module using a synthetic module that adds JUnit support. |
| * |
| * @param moduleName the module name |
| * @param syntheticModuleName the synthetic module name |
| * @param strategy the strategy |
| * @param batchingStrategy the batching strategy |
| * @param treeLogger the logger |
| * @return the {@link ModuleDef} describing the synthetic module |
| */ |
| protected ModuleDef maybeCompileModuleImpl(String moduleName, |
| String syntheticModuleName, Strategy strategy, |
| BatchingStrategy batchingStrategy, TreeLogger treeLogger) |
| throws UnableToCompleteException { |
| |
| ModuleDef moduleDef = maybeCompileModuleImpl2(moduleName, |
| syntheticModuleName, strategy, treeLogger); |
| |
| // Add all test blocks for the module if we haven't seen this module before. |
| if (!compiledModuleNames.contains(syntheticModuleName)) { |
| compiledModuleNames.add(syntheticModuleName); |
| if (!batchingStrategy.isSingleTestOnly()) { |
| // Use >= so we can mock getModuleCount and force isFinalModule to true |
| boolean isFinalModule = compiledModuleNames.size() >= getModuleCount(); |
| List<TestInfo[]> testBlocks = batchingStrategy.getTestBlocks(syntheticModuleName); |
| getMessageQueue().addTestBlocks(testBlocks, isFinalModule); |
| } |
| } |
| |
| return moduleDef; |
| } |
| |
| /** |
| * Visible for testing and mocking. |
| * |
| * @return the {@link JUnitMessageQueue} |
| */ |
| JUnitMessageQueue getMessageQueue() { |
| return JUnitShell.getMessageQueue(); |
| } |
| |
| /** |
| * Visible for testing and mocking. |
| * |
| * @return the number of modules to test |
| */ |
| int getModuleCount() { |
| return GWTTestCase.getModuleCount(); |
| } |
| |
| /** |
| * Compile the module if needed. |
| * |
| * Visible for testing and mocking. |
| * |
| * @param moduleName the module name |
| * @param syntheticModuleName the synthetic module name |
| * @param strategy the strategy |
| * @param treeLogger the logger |
| * @return the {@link ModuleDef} describing the synthetic module |
| */ |
| ModuleDef maybeCompileModuleImpl2(String moduleName, |
| String syntheticModuleName, Strategy strategy, TreeLogger treeLogger) |
| throws UnableToCompleteException { |
| /* |
| * Synthesize a synthetic module that derives from the user-specified module |
| * but also includes JUnit support. |
| */ |
| ModuleDef moduleDef = ModuleDefLoader.createSyntheticModule(treeLogger, |
| syntheticModuleName, new String[] { |
| moduleName, strategy.getModuleInherit()}, false); |
| |
| // Replace any user entry points with our test runner. |
| moduleDef.clearEntryPoints(); |
| moduleDef.addEntryPointTypeName(GWTRunner.class.getName()); |
| |
| // Squirrel away the name of the active module for GWTRunnerGenerator |
| ConfigurationProperty moduleNameProp = moduleDef.getProperties().createConfiguration( |
| "junit.moduleName", false); |
| moduleNameProp.setValue(syntheticModuleName); |
| |
| strategy.processModule(moduleDef); |
| |
| junitShell.maybeCompileForWebMode(syntheticModuleName, |
| JUnitShell.getRemoteUserAgents()); |
| |
| return moduleDef; |
| } |
| } |
| |
| /** |
| * Strategy that compiles modules as tests run. Optimizes total test time. |
| */ |
| class ParallelCompileStrategy extends PreCompileStrategy { |
| |
| /** |
| * The {@link BatchingStrategy} used to compile, which is set on the first |
| * compilation and is the same across all compilations. |
| */ |
| private BatchingStrategy batchingStrategy; |
| |
| /** |
| * The list of all synthetic module names to be compiled. |
| */ |
| private List<String> modulesToCompile = new ArrayList<String>(); |
| |
| /** |
| * The {@link TreeLogger} used to compile, which is set on the first |
| * compilation and is the same across all compilations. |
| */ |
| private TreeLogger treeLogger; |
| |
| public ParallelCompileStrategy(JUnitShell junitShell) { |
| super(junitShell); |
| } |
| |
| @Override |
| public void maybeCompileAhead() throws UnableToCompleteException { |
| if (modulesToCompile.size() > 0) { |
| String nextModule = modulesToCompile.remove(0); |
| TestModuleInfo moduleInfo = GWTTestCase.getTestsForModule(nextModule); |
| String syntheticModuleName = moduleInfo.getSyntheticModuleName(); |
| maybeCompileModuleImpl(moduleInfo.getModuleName(), syntheticModuleName, |
| moduleInfo.getStrategy(), batchingStrategy, treeLogger); |
| } |
| } |
| |
| @Override |
| public ModuleDef maybeCompileModule(String moduleName, |
| String syntheticModuleName, Strategy strategy, |
| BatchingStrategy batchingStrategy, TreeLogger treeLogger) |
| throws UnableToCompleteException { |
| |
| // Initialize the map of modules. |
| if (preCompiledModuleDefs == null) { |
| this.batchingStrategy = batchingStrategy; |
| this.treeLogger = treeLogger; |
| preCompiledModuleDefs = new HashMap<String, ModuleDef>(); |
| String[] allModuleNames = GWTTestCase.getAllTestModuleNames(); |
| for (String curModuleName : allModuleNames) { |
| modulesToCompile.add(curModuleName); |
| } |
| } |
| |
| // Compile the requested module if needed. |
| ModuleDef moduleDef = preCompiledModuleDefs.get(syntheticModuleName); |
| if (moduleDef == null) { |
| moduleDef = maybeCompileModuleImpl(moduleName, syntheticModuleName, |
| strategy, batchingStrategy, treeLogger); |
| } |
| return moduleDef; |
| } |
| |
| @Override |
| protected ModuleDef maybeCompileModuleImpl(String moduleName, |
| String syntheticModuleName, Strategy strategy, |
| BatchingStrategy batchingStrategy, TreeLogger treeLogger) |
| throws UnableToCompleteException { |
| modulesToCompile.remove(syntheticModuleName); |
| ModuleDef moduleDef = super.maybeCompileModuleImpl(moduleName, |
| syntheticModuleName, strategy, batchingStrategy, treeLogger); |
| preCompiledModuleDefs.put(syntheticModuleName, moduleDef); |
| return moduleDef; |
| } |
| } |
| |
| /** |
| * Strategy that compiles all modules before returning results. Optimizes test |
| * system usage. |
| */ |
| class PreCompileStrategy extends CompileStrategy { |
| /** |
| * A mapping of synthetic module names to their precompiled synthetic module |
| * defs. |
| */ |
| Map<String, ModuleDef> preCompiledModuleDefs; |
| |
| public PreCompileStrategy(JUnitShell junitShell) { |
| super(junitShell); |
| } |
| |
| @Override |
| public ModuleDef maybeCompileModule(String moduleName, |
| String syntheticModuleName, Strategy strategy, |
| BatchingStrategy batchingStrategy, TreeLogger treeLogger) |
| throws UnableToCompleteException { |
| maybePrecompileModules(batchingStrategy, treeLogger); |
| |
| // Since all test blocks from a module are added to the queue at the |
| // same time, we can safely take the module out of the hash map at |
| // this point. |
| return preCompiledModuleDefs.get(syntheticModuleName); |
| } |
| |
| /** |
| * Precompile all modules if needed. |
| */ |
| private void maybePrecompileModules(BatchingStrategy batchingStrategy, |
| TreeLogger treeLogger) throws UnableToCompleteException { |
| if (preCompiledModuleDefs == null) { |
| preCompiledModuleDefs = new HashMap<String, ModuleDef>(); |
| for (String moduleName : GWTTestCase.getAllTestModuleNames()) { |
| TestModuleInfo moduleInfo = GWTTestCase.getTestsForModule(moduleName); |
| String syntheticModuleName = moduleInfo.getSyntheticModuleName(); |
| ModuleDef moduleDef = maybeCompileModuleImpl( |
| moduleInfo.getModuleName(), syntheticModuleName, |
| moduleInfo.getStrategy(), batchingStrategy, treeLogger); |
| preCompiledModuleDefs.put(syntheticModuleName, moduleDef); |
| } |
| } |
| } |
| } |
| |
| /** |
| * |
| * Strategy that compiles only one module at a time. Optimizes memory usage. |
| */ |
| class SimpleCompileStrategy extends CompileStrategy { |
| public SimpleCompileStrategy(JUnitShell junitShell) { |
| super(junitShell); |
| } |
| |
| @Override |
| public ModuleDef maybeCompileModule(String moduleName, |
| String syntheticModuleName, Strategy strategy, |
| BatchingStrategy batchingStrategy, TreeLogger treeLogger) |
| throws UnableToCompleteException { |
| return maybeCompileModuleImpl(moduleName, syntheticModuleName, strategy, |
| batchingStrategy, treeLogger); |
| } |
| } |