package org.testng;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.testng.collections.ListMultiMap;
import org.testng.collections.Lists;
import org.testng.collections.Maps;
import org.testng.internal.Attributes;
import org.testng.internal.ClassHelper;
import org.testng.internal.ClassInfoMap;
import org.testng.internal.ConfigurationGroupMethods;
import org.testng.internal.Constants;
import org.testng.internal.DynamicGraph;
import org.testng.internal.DynamicGraph.Status;
import org.testng.internal.IConfiguration;
import org.testng.internal.IInvoker;
import org.testng.internal.ITestResultNotifier;
import org.testng.internal.InvokedMethod;
import org.testng.internal.Invoker;
import org.testng.internal.MethodGroupsHelper;
import org.testng.internal.MethodHelper;
import org.testng.internal.MethodInstance;
import org.testng.internal.ResultMap;
import org.testng.internal.RunInfo;
import org.testng.internal.TestMethodWorker;
import org.testng.internal.TestNGClassFinder;
import org.testng.internal.TestNGMethodFinder;
import org.testng.internal.Utils;
import org.testng.internal.XmlMethodSelector;
import org.testng.internal.annotations.IAnnotationFinder;
import org.testng.internal.annotations.IListeners;
import org.testng.internal.annotations.Sets;
import org.testng.internal.thread.ThreadUtil;
import org.testng.internal.thread.graph.GraphThreadPoolExecutor;
import org.testng.internal.thread.graph.IThreadWorkerFactory;
import org.testng.internal.thread.graph.IWorker;
import org.testng.junit.IJUnitTestRunner;
import org.testng.xml.XmlClass;
import org.testng.xml.XmlInclude;
import org.testng.xml.XmlPackage;
import org.testng.xml.XmlSuite;
import org.testng.xml.XmlTest;
import com.google.inject.Injector;
import com.google.inject.Module;
/**
* This class takes care of running one Test.
*
* @author Cedric Beust, Apr 26, 2004
*/
public class TestRunner
implements ITestContext, ITestResultNotifier, IThreadWorkerFactory<ITestNGMethod>
{
/* generated */
private static final long serialVersionUID = 4247820024988306670L;
private ISuite m_suite;
private XmlTest m_xmlTest;
private String m_testName;
transient private List<XmlClass> m_testClassesFromXml= null;
transient private List<XmlPackage> m_packageNamesFromXml= null;
transient private IInvoker m_invoker= null;
transient private IAnnotationFinder m_annotationFinder= null;
/** ITestListeners support. */
transient private List<ITestListener> m_testListeners = Lists.newArrayList();
transient private Set<IConfigurationListener> m_configurationListeners = Sets.newHashSet();
transient private IConfigurationListener m_confListener= new ConfigurationListener();
transient private boolean m_skipFailedInvocationCounts;
transient private List<IInvokedMethodListener> m_invokedMethodListeners = Lists.newArrayList();
/**
* All the test methods we found, associated with their respective classes.
* Note that these test methods might belong to different classes.
* We pick which ones to run at runtime.
*/
private ITestNGMethod[] m_allTestMethods = new ITestNGMethod[0];
// Information about this test run
private Date m_startDate = null;
private Date m_endDate = null;
/** A map to keep track of Class <-> IClass. */
transient private Map<Class<?>, ITestClass> m_classMap = Maps.newHashMap();
/** Where the reports will be created. */
private String m_outputDirectory= Constants.getDefaultValueFor(Constants.PROP_OUTPUT_DIR);
// The XML method selector (groups/methods included/excluded in XML)
private XmlMethodSelector m_xmlMethodSelector = new XmlMethodSelector();
private static int m_verbose = 1;
//
// These next fields contain all the configuration methods found on this class.
// At initialization time, they just contain all the various @Configuration methods
// found in all the classes we are going to run. When comes the time to run them,
// only a subset of them are run: those that are enabled and belong on the same class as
// (or a parent of) the test class.
//
/** */
private ITestNGMethod[] m_beforeSuiteMethods = {};
private ITestNGMethod[] m_afterSuiteMethods = {};
private ITestNGMethod[] m_beforeXmlTestMethods = {};
private ITestNGMethod[] m_afterXmlTestMethods = {};
private List<ITestNGMethod> m_excludedMethods = Lists.newArrayList();
private ConfigurationGroupMethods m_groupMethods = null;
// Meta groups
private Map<String, List<String>> m_metaGroups = Maps.newHashMap();
// All the tests that were run along with their result
private IResultMap m_passedTests = new ResultMap();
private IResultMap m_failedTests = new ResultMap();
private IResultMap m_failedButWithinSuccessPercentageTests = new ResultMap();
private IResultMap m_skippedTests = new ResultMap();
private RunInfo m_runInfo= new RunInfo();
// The host where this test was run, or null if run locally
private String m_host;
// Defined dynamically depending on <test preserve-order="true/false">
private transient IMethodInterceptor m_methodInterceptor;
private transient ClassMethodMap m_classMethodMap;
private transient TestNGClassFinder m_testClassFinder;
private transient IConfiguration m_configuration;
protected TestRunner(IConfiguration configuration,
ISuite suite,
XmlTest test,
String outputDirectory,
IAnnotationFinder finder,
boolean skipFailedInvocationCounts,
List<IInvokedMethodListener> invokedMethodListeners)
{
init(configuration, suite, test, outputDirectory, finder, skipFailedInvocationCounts,
invokedMethodListeners);
}
public TestRunner(IConfiguration configuration, ISuite suite, XmlTest test,
boolean skipFailedInvocationCounts,
List<IInvokedMethodListener> listeners) {
init(configuration, suite, test, suite.getOutputDirectory(),
suite.getAnnotationFinder(),
skipFailedInvocationCounts, listeners);
}
private void init(IConfiguration configuration,
ISuite suite,
XmlTest test,
String outputDirectory,
IAnnotationFinder annotationFinder,
boolean skipFailedInvocationCounts,
List<IInvokedMethodListener> invokedMethodListeners)
{
m_configuration = configuration;
m_xmlTest= test;
m_suite = suite;
m_testName = test.getName();
m_host = suite.getHost();
m_testClassesFromXml= test.getXmlClasses();
m_skipFailedInvocationCounts = skipFailedInvocationCounts;
setVerbose(test.getVerbose());
boolean preserveOrder = "true".equalsIgnoreCase(test.getPreserveOrder());
m_methodInterceptor = preserveOrder ? new PreserveOrderMethodInterceptor()
: new InstanceOrderingMethodInterceptor();
m_packageNamesFromXml= test.getXmlPackages();
if(null != m_packageNamesFromXml) {
for(XmlPackage xp: m_packageNamesFromXml) {
m_testClassesFromXml.addAll(xp.getXmlClasses());
}
}
m_annotationFinder= annotationFinder;
m_invokedMethodListeners = invokedMethodListeners;
m_invoker = new Invoker(m_configuration, this, this, m_suite.getSuiteState(),
m_skipFailedInvocationCounts, invokedMethodListeners);
if (suite.getParallel() != null) {
log(3, "Running the tests in '" + test.getName() + "' with parallel mode:" + suite.getParallel());
}
setOutputDirectory(outputDirectory);
// Finish our initialization
init();
}
public IInvoker getInvoker() {
return m_invoker;
}
public ITestNGMethod[] getBeforeSuiteMethods() {
return m_beforeSuiteMethods;
}
public ITestNGMethod[] getAfterSuiteMethods() {
return m_afterSuiteMethods;
}
public ITestNGMethod[] getBeforeTestConfigurationMethods() {
return m_beforeXmlTestMethods;
}
public ITestNGMethod[] getAfterTestConfigurationMethods() {
return m_afterXmlTestMethods;
}
private void init() {
initMetaGroups(m_xmlTest);
initRunInfo(m_xmlTest);
// Init methods and class map
// JUnit behavior is different and doesn't need this initialization step
if(!m_xmlTest.isJUnit()) {
initMethods();
}
initListeners();
addConfigurationListener(m_confListener);
}
private class ListenerHolder {
private List<Class<? extends ITestNGListener>> listenerClasses;
private Class<? extends ITestNGListenerFactory> listenerFactoryClass;
}
/**
* @return all the @Listeners annotations found in the current class and its
* superclasses.
*/
private ListenerHolder findAllListeners(Class<?> cls) {
ListenerHolder result = new ListenerHolder();
result.listenerClasses = Lists.newArrayList();
do {
IListeners l = m_annotationFinder.findAnnotation(cls, IListeners.class);
if (l != null) {
Class<? extends ITestNGListener>[] classes = l.getValue();
for (Class<? extends ITestNGListener> c : classes) {
result.listenerClasses.add(c);
if (ITestNGListenerFactory.class.isAssignableFrom(c)) {
if (result.listenerFactoryClass == null) {
result.listenerFactoryClass = (Class<? extends ITestNGListenerFactory>) c;
}
else {
throw new TestNGException("Found more than one class implementing" +
"ITestNGListenerFactory:" + c + " and " + result.listenerFactoryClass);
}
}
}
}
cls = cls.getSuperclass();
} while (cls != Object.class);
return result;
}
private void initListeners() {
//
// Find all the listener factories and collect all the listeners requested in a
// @Listeners annotation.
//
Set<Class<? extends ITestNGListener>> listenerClasses = Sets.newHashSet();
Class<? extends ITestNGListenerFactory> listenerFactoryClass = null;
for (IClass cls : getTestClasses()) {
Class<? extends ITestNGListenerFactory> realClass = cls.getRealClass();
ListenerHolder listenerHolder = findAllListeners(realClass);
if (listenerFactoryClass == null) {
listenerFactoryClass = listenerHolder.listenerFactoryClass;
}
listenerClasses.addAll(listenerHolder.listenerClasses);
}
//
// Now we have all the listeners collected from @Listeners and at most one
// listener factory collected from a class implementing ITestNGListenerFactory.
// Instantiate all the requested listeners.
//
ITestNGListenerFactory listenerFactory = null;
// If we found a test listener factory, instantiate it.
try {
if (m_testClassFinder != null) {
IClass ic = m_testClassFinder.getIClass(listenerFactoryClass);
if (ic != null) {
listenerFactory = (ITestNGListenerFactory) ic.getInstances(false)[0];
}
}
if (listenerFactory == null) {
listenerFactory = listenerFactoryClass != null ? listenerFactoryClass.newInstance() : null;
}
}
catch(Exception ex) {
throw new TestNGException("Couldn't instantiate the ITestNGListenerFactory: "
+ ex);
}
// Instantiate all the listeners
for (Class<? extends ITestNGListener> c : listenerClasses) {
Object listener = listenerFactory != null ? listenerFactory.createListener(c) : null;
if (listener == null) {
listener = ClassHelper.newInstance(c);
}
if (listener instanceof IMethodInterceptor) {
setMethodInterceptor((IMethodInterceptor) listener);
}
if (listener instanceof ISuiteListener) {
m_suite.addListener((ISuiteListener) listener);
}
if (listener instanceof IInvokedMethodListener) {
m_suite.addListener((ITestNGListener) listener);
}
if (listener instanceof ITestListener) {
// At this point, the field m_testListeners has already been used in the creation
addTestListener((ITestListener) listener);
}
if (listener instanceof IConfigurationListener) {
addConfigurationListener((IConfigurationListener) listener);
}
if (listener instanceof IReporter) {
m_suite.addListener((ITestNGListener) listener);
}
if (listener instanceof IConfigurable) {
m_configuration.setConfigurable((IConfigurable) listener);
}
if (listener instanceof IHookable) {
m_configuration.setHookable((IHookable) listener);
}
if (listener instanceof IExecutionListener) {
IExecutionListener iel = (IExecutionListener) listener;
iel.onExecutionStart();
m_configuration.addExecutionListener(iel);
}
}
}
/**
* Initialize meta groups
*/
private void initMetaGroups(XmlTest xmlTest) {
Map<String, List<String>> metaGroups = xmlTest.getMetaGroups();
for (Map.Entry<String, List<String>> entry : metaGroups.entrySet()) {
addMetaGroup(entry.getKey(), entry.getValue());
}
}
private void initRunInfo(final XmlTest xmlTest) {
// Groups
m_xmlMethodSelector.setIncludedGroups(createGroups(m_xmlTest.getIncludedGroups()));
m_xmlMethodSelector.setExcludedGroups(createGroups(m_xmlTest.getExcludedGroups()));
m_xmlMethodSelector.setExpression(m_xmlTest.getExpression());
// Methods
m_xmlMethodSelector.setXmlClasses(m_xmlTest.getXmlClasses());
m_runInfo.addMethodSelector(m_xmlMethodSelector, 10);
// Add user-specified method selectors (only class selectors, we can ignore
// script selectors here)
if (null != xmlTest.getMethodSelectors()) {
for (org.testng.xml.XmlMethodSelector selector : xmlTest.getMethodSelectors()) {
if (selector.getClassName() != null) {
IMethodSelector s = ClassHelper.createSelector(selector);
m_runInfo.addMethodSelector(s, selector.getPriority());
}
}
}
}
private void initMethods() {
//
// Calculate all the methods we need to invoke
//
List<ITestNGMethod> beforeClassMethods = Lists.newArrayList();
List<ITestNGMethod> testMethods = Lists.newArrayList();
List<ITestNGMethod> afterClassMethods = Lists.newArrayList();
List<ITestNGMethod> beforeSuiteMethods = Lists.newArrayList();
List<ITestNGMethod> afterSuiteMethods = Lists.newArrayList();
List<ITestNGMethod> beforeXmlTestMethods = Lists.newArrayList();
List<ITestNGMethod> afterXmlTestMethods = Lists.newArrayList();
ClassInfoMap classMap = new ClassInfoMap(m_testClassesFromXml);
m_testClassFinder= new TestNGClassFinder(classMap,
null,
m_xmlTest,
m_configuration,
this);
ITestMethodFinder testMethodFinder
= new TestNGMethodFinder(m_runInfo, m_annotationFinder);
m_runInfo.setTestMethods(testMethods);
//
// Initialize TestClasses
//
IClass[] classes = m_testClassFinder.findTestClasses();
for (IClass ic : classes) {
// Create TestClass
ITestClass tc = new TestClass(ic,
testMethodFinder,
m_annotationFinder,
m_runInfo,
m_xmlTest,
classMap.getXmlClass(ic.getRealClass()));
m_classMap.put(ic.getRealClass(), tc);
}
//
// Calculate groups methods
//
Map<String, List<ITestNGMethod>> beforeGroupMethods =
MethodGroupsHelper.findGroupsMethods(m_classMap.values(), true);
Map<String, List<ITestNGMethod>> afterGroupMethods =
MethodGroupsHelper.findGroupsMethods(m_classMap.values(), false);
//
// Walk through all the TestClasses, store their method
// and initialize them with the correct ITestClass
//
for (ITestClass tc : m_classMap.values()) {
fixMethodsWithClass(tc.getTestMethods(), tc, testMethods);
fixMethodsWithClass(tc.getBeforeClassMethods(), tc, beforeClassMethods);
fixMethodsWithClass(tc.getBeforeTestMethods(), tc, null);
fixMethodsWithClass(tc.getAfterTestMethods(), tc, null);
fixMethodsWithClass(tc.getAfterClassMethods(), tc, afterClassMethods);
fixMethodsWithClass(tc.getBeforeSuiteMethods(), tc, beforeSuiteMethods);
fixMethodsWithClass(tc.getAfterSuiteMethods(), tc, afterSuiteMethods);
fixMethodsWithClass(tc.getBeforeTestConfigurationMethods(), tc, beforeXmlTestMethods);
fixMethodsWithClass(tc.getAfterTestConfigurationMethods(), tc, afterXmlTestMethods);
fixMethodsWithClass(tc.getBeforeGroupsMethods(), tc,
MethodHelper.uniqueMethodList(beforeGroupMethods.values()));
fixMethodsWithClass(tc.getAfterGroupsMethods(), tc,
MethodHelper.uniqueMethodList(afterGroupMethods.values()));
}
//
// Sort the methods
//
m_beforeSuiteMethods = MethodHelper.collectAndOrderMethods(beforeSuiteMethods,
false /* forTests */,
m_runInfo,
m_annotationFinder,
true /* unique */,
m_excludedMethods);
m_beforeXmlTestMethods = MethodHelper.collectAndOrderMethods(beforeXmlTestMethods,
false /* forTests */,
m_runInfo,
m_annotationFinder,
true /* unique (CQ added by me)*/,
m_excludedMethods);
m_allTestMethods = MethodHelper.collectAndOrderMethods(testMethods,
true /* forTest? */,
m_runInfo,
m_annotationFinder,
false /* unique */,
m_excludedMethods);
m_classMethodMap = new ClassMethodMap(testMethods, m_xmlMethodSelector);
m_afterXmlTestMethods = MethodHelper.collectAndOrderMethods(afterXmlTestMethods,
false /* forTests */,
m_runInfo,
m_annotationFinder,
true /* unique (CQ added by me)*/,
m_excludedMethods);
m_afterSuiteMethods = MethodHelper.collectAndOrderMethods(afterSuiteMethods,
false /* forTests */,
m_runInfo,
m_annotationFinder,
true /* unique */,
m_excludedMethods);
// shared group methods
m_groupMethods = new ConfigurationGroupMethods(m_allTestMethods, beforeGroupMethods, afterGroupMethods);
}
private void fixMethodsWithClass(ITestNGMethod[] methods,
ITestClass testCls,
List<ITestNGMethod> methodList) {
for (ITestNGMethod itm : methods) {
itm.setTestClass(testCls);
if (methodList != null) {
methodList.add(itm);
}
}
}
public Collection<ITestClass> getTestClasses() {
return m_classMap.values();
}
public void setTestName(String name) {
m_testName = name;
}
public void setOutputDirectory(String od) {
m_outputDirectory= od;
// FIX: empty directories were created
// if (od == null) { m_outputDirectory = null; return; } //for maven2
// File file = new File(od);
// file.mkdirs();
// m_outputDirectory= file.getAbsolutePath();
}
private void addMetaGroup(String name, List<String> groupNames) {
m_metaGroups.put(name, groupNames);
}
/**
* Calculate the transitive closure of all the MetaGroups
*
* @param groups
* @param unfinishedGroups
* @param result The transitive closure containing all the groups found
*/
private void collectGroups(String[] groups,
List<String> unfinishedGroups,
Map<String, String> result) {
for (String gn : groups) {
List<String> subGroups = m_metaGroups.get(gn);
if (null != subGroups) {
for (String sg : subGroups) {
if (null == result.get(sg)) {
result.put(sg, sg);
unfinishedGroups.add(sg);
}
}
}
}
}
private Map<String, String> createGroups(List<String> groups) {
return createGroups(groups.toArray(new String[groups.size()]));
}
private Map<String, String> createGroups(String[] groups) {
Map<String, String> result = Maps.newHashMap();
// Groups that were passed on the command line
for (String group : groups) {
result.put(group, group);
}
// See if we have any MetaGroups and
// expand them if they match one of the groups
// we have just been passed
List<String> unfinishedGroups = Lists.newArrayList();
if (m_metaGroups.size() > 0) {
collectGroups(groups, unfinishedGroups, result);
// Do we need to loop over unfinished groups?
while (unfinishedGroups.size() > 0) {
String[] uGroups = unfinishedGroups.toArray(new String[unfinishedGroups.size()]);
unfinishedGroups = Lists.newArrayList();
collectGroups(uGroups, unfinishedGroups, result);
}
}
// Utils.dumpMap(result);
return result;
}
/**
* The main entry method for TestRunner.
*
* This is where all the hard work is done:
* - Invoke configuration methods
* - Invoke test methods
* - Catch exceptions
* - Collect results
* - Invoke listeners
* - etc...
*/
public void run() {
beforeRun();
try {
XmlTest test= getTest();
if(test.isJUnit()) {
privateRunJUnit(test);
}
else {
privateRun(test);
}
}
finally {
afterRun();
}
}
/** Before run preparements. */
private void beforeRun() {
//
// Log the start date
//
m_startDate = new Date(System.currentTimeMillis());
// Log start
logStart();
// Invoke listeners
fireEvent(true /*start*/);
// invoke @BeforeTest
ITestNGMethod[] testConfigurationMethods= getBeforeTestConfigurationMethods();
if(null != testConfigurationMethods && testConfigurationMethods.length > 0) {
m_invoker.invokeConfigurations(null,
testConfigurationMethods,
m_xmlTest.getSuite(),
m_xmlTest.getAllParameters(),
null, /* no parameter values */
null /* instance */);
}
}
private void privateRunJUnit(XmlTest xmlTest) {
final ClassInfoMap cim = new ClassInfoMap(m_testClassesFromXml, false);
final Set<Class<?>> classes = cim.getClasses();
final List<ITestNGMethod> runMethods = Lists.newArrayList();
List<IWorker<ITestNGMethod>> workers = Lists.newArrayList();
// FIXME: directly referencing JUnitTestRunner which uses JUnit classes
// may result in an class resolution exception under different JVMs
// The resolution process is not specified in the JVM spec with a specific implementation,
// so it can be eager => failure
workers.add(new IWorker<ITestNGMethod>() {
/**
* @see org.testng.internal.IMethodWorker#getMaxTimeOut()
*/
@Override
public long getTimeOut() {
return 0;
}
/**
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
for(Class<?> tc: classes) {
List<XmlInclude> includedMethods = cim.getXmlClass(tc).getIncludedMethods();
List<String> methods = Lists.newArrayList();
for (XmlInclude inc: includedMethods) {
methods.add(inc.getName());
}
IJUnitTestRunner tr= ClassHelper.createTestRunner(TestRunner.this);
tr.setInvokedMethodListeners(m_invokedMethodListeners);
try {
tr.run(tc, methods.toArray(new String[methods.size()]));
}
catch(Exception ex) {
ex.printStackTrace();
}
finally {
runMethods.addAll(tr.getTestMethods());
}
}
}
@Override
public List<ITestNGMethod> getTasks() {
throw new TestNGException("JUnit not supported");
}
@Override
public int getPriority() {
if (m_allTestMethods.length == 1) {
return m_allTestMethods[0].getPriority();
} else {
return 0;
}
}
@Override
public int compareTo(IWorker<ITestNGMethod> other) {
return getPriority() - other.getPriority();
}
});
runWorkers(workers, "" /* JUnit does not support parallel */, null);
m_allTestMethods= runMethods.toArray(new ITestNGMethod[runMethods.size()]);
}
/**
* Main method that create a graph of methods and then pass it to the
* graph executor to run them.
*/
private void privateRun(XmlTest xmlTest) {
String parallelMode = xmlTest.getParallel();
boolean parallel = XmlSuite.PARALLEL_METHODS.equals(parallelMode)
|| "true".equalsIgnoreCase(parallelMode)
|| XmlSuite.PARALLEL_CLASSES.equals(parallelMode)
|| XmlSuite.PARALLEL_INSTANCES.equals(parallelMode);
{
// parallel
int threadCount = parallel ? xmlTest.getThreadCount() : 1;
// Make sure we create a graph based on the intercepted methods, otherwise an interceptor
// removing methods would cause the graph never to terminate (because it would expect
// termination from methods that never get invoked).
DynamicGraph<ITestNGMethod> graph = createDynamicGraph(intercept(m_allTestMethods));
if (parallel) {
if (graph.getNodeCount() > 0) {
GraphThreadPoolExecutor<ITestNGMethod> executor =
new GraphThreadPoolExecutor<ITestNGMethod>(graph, this,
threadCount, threadCount, 0, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
executor.run();
try {
long timeOut = m_xmlTest.getTimeOut(XmlTest.DEFAULT_TIMEOUT_MS);
Utils.log("TestRunner", 2, "Starting executor for test " + m_xmlTest.getName()
+ " with time out:" + timeOut + " milliseconds.");
executor.awaitTermination(timeOut, TimeUnit.MILLISECONDS);
executor.shutdownNow();
} catch (InterruptedException handled) {
handled.printStackTrace();
Thread.currentThread().interrupt();
}
}
} else {
boolean debug = false;
List<ITestNGMethod> freeNodes = graph.getFreeNodes();
if (debug) {
System.out.println("Free nodes:" + freeNodes);
}
if (graph.getNodeCount() > 0 && freeNodes.isEmpty()) {
throw new TestNGException("No free nodes found in:" + graph);
}
while (! freeNodes.isEmpty()) {
List<IWorker<ITestNGMethod>> runnables = createWorkers(freeNodes);
for (IWorker<ITestNGMethod> r : runnables) {
r.run();
}
graph.setStatus(freeNodes, Status.FINISHED);
freeNodes = graph.getFreeNodes();
if (debug) {
System.out.println("Free nodes:" + freeNodes);
}
}
}
}
}
/**
* Apply the method interceptor (if applicable) to the list of methods.
*/
private ITestNGMethod[] intercept(ITestNGMethod[] methods) {
if (m_methodInterceptor == null) return methods;
IMethodInstance[] instances = methodsToMethodInstances(Arrays.asList(methods));
List<IMethodInstance> resultInstances =
m_methodInterceptor.intercept(Arrays.asList(instances), this);
List<ITestNGMethod> result = Lists.newArrayList();
for (IMethodInstance imi : resultInstances) {
result.add(imi.getMethod());
}
return result.toArray(new ITestNGMethod[result.size()]);
}
/**
* Create a list of workers to run the methods passed in parameter.
* Each test method is run in its own worker except in the following cases:
* - The method belongs to a class that has @Test(sequential=true)
* - The parallel attribute is set to "classes"
* In both these cases, all the methods belonging to that class will then
* be put in the same worker in order to run in the same thread.
*/
@Override
public List<IWorker<ITestNGMethod>> createWorkers(List<ITestNGMethod> methods) {
List<IWorker<ITestNGMethod>> result;
if (XmlSuite.PARALLEL_INSTANCES.equals(m_xmlTest.getParallel())) {
result = createInstanceBasedParallelWorkers(methods);
} else {
result = createClassBasedParallelWorkers(methods);
}
return result;
}
/**
* Create workers for parallel="classes" and similar cases.
*/
private List<IWorker<ITestNGMethod>> createClassBasedParallelWorkers(List<ITestNGMethod> methods) {
List<IWorker<ITestNGMethod>> result = Lists.newArrayList();
// Methods that belong to classes with a sequential=true or parallel=classes
// attribute must all be run in the same worker
Set<Class> sequentialClasses = Sets.newHashSet();
for (ITestNGMethod m : methods) {
Class<? extends ITestClass> cls = m.getRealClass();
org.testng.annotations.ITestAnnotation test =
m_annotationFinder.findAnnotation(cls, org.testng.annotations.ITestAnnotation.class);
// If either sequential=true or parallel=classes, mark this class sequential
if (test != null && (test.getSequential() || test.getSingleThreaded()) ||
XmlSuite.PARALLEL_CLASSES.equals(m_xmlTest.getParallel())) {
sequentialClasses.add(cls);
}
}
List<IMethodInstance> methodInstances = Lists.newArrayList();
for (ITestNGMethod tm : methods) {
methodInstances.addAll(methodsToMultipleMethodInstances(tm));
}
//
// Finally, sort the parallel methods by classes
//
methodInstances = m_methodInterceptor.intercept(methodInstances, this);
Map<String, String> params = m_xmlTest.getAllParameters();
Set<Class<?>> processedClasses = Sets.newHashSet();
for (IMethodInstance im : methodInstances) {
Class<?> c = im.getMethod().getTestClass().getRealClass();
if (sequentialClasses.contains(c)) {
if (!processedClasses.contains(c)) {
processedClasses.add(c);
if (System.getProperty("experimental") != null) {
List<List<IMethodInstance>> instances = createInstances(methodInstances);
for (List<IMethodInstance> inst : instances) {
TestMethodWorker worker = createTestMethodWorker(inst, params, c);
result.add(worker);
}
}
else {
// Sequential class: all methods in one worker
TestMethodWorker worker = createTestMethodWorker(methodInstances, params, c);
result.add(worker);
}
}
}
else {
// Parallel class: each method in its own worker
TestMethodWorker worker = createTestMethodWorker(Arrays.asList(im), params, c);
result.add(worker);
}
}
// Sort by priorities
Collections.sort(result);
return result;
}
/**
* Create workers for parallel="instances".
*/
private List<IWorker<ITestNGMethod>>
createInstanceBasedParallelWorkers(List<ITestNGMethod> methods) {
List<IWorker<ITestNGMethod>> result = Lists.newArrayList();
ListMultiMap<Object, ITestNGMethod> lmm = Maps.newListMultiMap();
for (ITestNGMethod m : methods) {
lmm.put(m.getInstance(), m);
}
for (Map.Entry<Object, List<ITestNGMethod>> es : lmm.getEntrySet()) {
List<IMethodInstance> methodInstances = Lists.newArrayList();
for (ITestNGMethod m : es.getValue()) {
methodInstances.add(new MethodInstance(m));
}
TestMethodWorker tmw = new TestMethodWorker(m_invoker,
methodInstances.toArray(new IMethodInstance[methodInstances.size()]),
m_xmlTest.getSuite(),
m_xmlTest.getAllParameters(),
m_allTestMethods,
m_groupMethods,
m_classMethodMap,
this);
result.add(tmw);
}
return result;
}
private List<List<IMethodInstance>> createInstances(List<IMethodInstance> methodInstances) {
Map<Object, List<IMethodInstance>> map = Maps.newHashMap();
// MapList<IMethodInstance[], Object> map = new MapList<IMethodInstance[], Object>();
for (IMethodInstance imi : methodInstances) {
for (Object o : imi.getInstances()) {
System.out.println(o);
List<IMethodInstance> l = map.get(o);
if (l == null) {
l = Lists.newArrayList();
map.put(o, l);
}
l.add(imi);
}
// for (Object instance : imi.getInstances()) {
// map.put(imi, instance);
// }
}
// return map.getKeys();
// System.out.println(map);
return new ArrayList<List<IMethodInstance>>(map.values());
}
private TestMethodWorker createTestMethodWorker(
List<IMethodInstance> methodInstances, Map<String, String> params,
Class<?> c) {
return new TestMethodWorker(m_invoker,
findClasses(methodInstances, c),
m_xmlTest.getSuite(),
params,
m_allTestMethods,
m_groupMethods,
m_classMethodMap,
this);
}
private IMethodInstance[] findClasses(List<IMethodInstance> methodInstances, Class<?> c) {
List<IMethodInstance> result = Lists.newArrayList();
for (IMethodInstance mi : methodInstances) {
if (mi.getMethod().getTestClass().getRealClass() == c) {
result.add(mi);
}
}
return result.toArray(new IMethodInstance[result.size()]);
}
/**
* @@@ remove this
*/
private List<MethodInstance> methodsToMultipleMethodInstances(ITestNGMethod... sl) {
List<MethodInstance> vResult = Lists.newArrayList();
for (ITestNGMethod m : sl) {
vResult.add(new MethodInstance(m));
}
return vResult;
}
private MethodInstance[] methodsToMethodInstances(List<ITestNGMethod> sl) {
MethodInstance[] result = new MethodInstance[sl.size()];
for (int i = 0; i < result.length; i++) {
result[i] = new MethodInstance(sl.get(i));
}
return result;
}
//
// Invoke the workers
//
private void runWorkers(List<? extends IWorker<ITestNGMethod>> workers, String parallelMode,
ListMultiMap<Integer, TestMethodWorker> sequentialWorkers) {
if (XmlSuite.PARALLEL_METHODS.equals(parallelMode)
|| "true".equalsIgnoreCase(parallelMode)
|| XmlSuite.PARALLEL_CLASSES.equals(parallelMode))
{
//
// Parallel run
//
// Default timeout for individual methods: same as the test global-time-out, but
// overridden if a method defines its own.
long maxTimeOut = m_xmlTest.getTimeOut(XmlTest.DEFAULT_TIMEOUT_MS);
for (IWorker<ITestNGMethod> tmw : workers) {
long mt = tmw.getTimeOut();
if (mt > maxTimeOut) {
maxTimeOut= mt;
}
}
ThreadUtil.execute(workers, m_xmlTest.getThreadCount(), maxTimeOut, false);
// ThreadUtil.execute(sequentialWorkers);
}
else {
//
// Sequential run
//
for (IWorker<ITestNGMethod> tmw : workers) {
tmw.run();
}
}
}
private void afterRun() {
// invoke @AfterTest
ITestNGMethod[] testConfigurationMethods= getAfterTestConfigurationMethods();
if(null != testConfigurationMethods && testConfigurationMethods.length > 0) {
m_invoker.invokeConfigurations(null,
testConfigurationMethods,
m_xmlTest.getSuite(),
m_xmlTest.getAllParameters(),
null, /* no parameter values */
null /* instance */);
}
//
// Log the end date
//
m_endDate = new Date(System.currentTimeMillis());
if (getVerbose() >= 3) {
dumpInvokedMethods();
}
// Invoke listeners
fireEvent(false /*stop*/);
// Statistics
// logResults();
}
private DynamicGraph<ITestNGMethod> createDynamicGraph(ITestNGMethod[] methods) {
DynamicGraph<ITestNGMethod> result = new DynamicGraph<ITestNGMethod>();
result.setComparator(new Comparator<ITestNGMethod>() {
@Override
public int compare(ITestNGMethod o1, ITestNGMethod o2) {
return o1.getPriority() - o2.getPriority();
}
});
DependencyMap dependencyMap = new DependencyMap(methods);
// Keep track of whether we have group dependencies. If we do, preserve-order needs
// to be ignored since group dependencies create inter-class dependencies which can
// end up creating cycles when combined with preserve-order.
boolean hasDependencies = false;
for (ITestNGMethod m : methods) {
result.addNode(m);
// Dependent methods
{
String[] dependentMethods = m.getMethodsDependedUpon();
if (dependentMethods != null) {
for (String d : dependentMethods) {
ITestNGMethod dm = dependencyMap.getMethodDependingOn(d, m);
if (m != dm){
result.addEdge(m, dm);
}
}
}
}
// Dependent groups
{
String[] dependentGroups = m.getGroupsDependedUpon();
for (String d : dependentGroups) {
hasDependencies = true;
List<ITestNGMethod> dg = dependencyMap.getMethodsThatBelongTo(d, m);
if (dg == null) {
throw new TestNGException("Method \"" + m
+ "\" depends on nonexistent group \"" + d + "\"");
}
for (ITestNGMethod ddm : dg) {
result.addEdge(m, ddm);
}
}
}
}
// Preserve order
// Don't preserve the ordering if we're running in parallel, otherwise the suite will
// create multiple threads but these threads will be created one after the other,
// giving the impression of parallelism (multiple thread id's) while still running
// sequentially.
if (! hasDependencies
&& ! XmlSuite.isParallel(getCurrentXmlTest().getParallel())
&& "true".equalsIgnoreCase(getCurrentXmlTest().getPreserveOrder())) {
// If preserve-order was specified and the class order is A, B
// create a new set of dependencies where each method of B depends
// on all the methods of A
ListMultiMap<ITestNGMethod, ITestNGMethod> classDependencies
= createClassDependencies(methods, getCurrentXmlTest());
for (Map.Entry<ITestNGMethod, List<ITestNGMethod>> es : classDependencies.getEntrySet()) {
for (ITestNGMethod dm : es.getValue()) {
result.addEdge(dm, es.getKey());
}
}
}
// Group by instances
if (getCurrentXmlTest().getGroupByInstances()) {
ListMultiMap<ITestNGMethod, ITestNGMethod> instanceDependencies
= createInstanceDependencies(methods, getCurrentXmlTest());
for (Map.Entry<ITestNGMethod, List<ITestNGMethod>> es : instanceDependencies.getEntrySet()) {
for (ITestNGMethod dm : es.getValue()) {
result.addEdge(dm, es.getKey());
}
}
}
return result;
}
private ListMultiMap<ITestNGMethod, ITestNGMethod> createInstanceDependencies(
ITestNGMethod[] methods, XmlTest currentXmlTest)
{
ListMultiMap<Object, ITestNGMethod> instanceMap = Maps.newListMultiMap();
for (ITestNGMethod m : methods) {
instanceMap.put(m.getInstance(), m);
}
ListMultiMap<ITestNGMethod, ITestNGMethod> result = Maps.newListMultiMap();
Object previousInstance = null;
for (Map.Entry<Object, List<ITestNGMethod>> es : instanceMap.getEntrySet()) {
if (previousInstance == null) {
previousInstance = es.getKey();
} else {
List<ITestNGMethod> previousMethods = instanceMap.get(previousInstance);
Object currentInstance = es.getKey();
List<ITestNGMethod> currentMethods = instanceMap.get(currentInstance);
// Make all the methods from the current instance depend on the methods of
// the previous instance
for (ITestNGMethod cm : currentMethods) {
for (ITestNGMethod pm : previousMethods) {
result.put(cm, pm);
}
}
previousInstance = currentInstance;
}
}
return result;
}
private ListMultiMap<ITestNGMethod, ITestNGMethod> createClassDependencies(
ITestNGMethod[] methods, XmlTest test)
{
Map<String, List<ITestNGMethod>> classes = Maps.newHashMap();
// Note: use a List here to preserve the ordering but make sure
// we don't add the same class twice
List<XmlClass> sortedClasses = Lists.newArrayList();
for (XmlClass c : test.getXmlClasses()) {
classes.put(c.getName(), new ArrayList<ITestNGMethod>());
if (! sortedClasses.contains(c)) sortedClasses.add(c);
}
// Sort the classes based on their order of appearance in the XML
Collections.sort(sortedClasses, new Comparator<XmlClass>() {
@Override
public int compare(XmlClass arg0, XmlClass arg1) {
return arg0.getIndex() - arg1.getIndex();
}
});
Map<String, Integer> indexedClasses1 = Maps.newHashMap();
Map<Integer, String> indexedClasses2 = Maps.newHashMap();
int i = 0;
for (XmlClass c : sortedClasses) {
indexedClasses1.put(c.getName(), i);
indexedClasses2.put(i, c.getName());
i++;
}
ListMultiMap<String, ITestNGMethod> methodsFromClass = Maps.newListMultiMap();
for (ITestNGMethod m : methods) {
methodsFromClass.put(m.getTestClass().getName(), m);
}
ListMultiMap<ITestNGMethod, ITestNGMethod> result = Maps.newListMultiMap();
for (ITestNGMethod m : methods) {
String name = m.getTestClass().getName();
Integer index = indexedClasses1.get(name);
// The index could be null if the classes listed in the XML are different
// from the methods being run (e.g. the .xml only contains a factory that
// instantiates methods from a different class). In this case, we cannot
// perform any ordering.
if (index != null && index > 0) {
// Make this method depend on all the methods of the class in the previous
// index
String classDependedUpon = indexedClasses2.get(index - 1);
List<ITestNGMethod> methodsDependedUpon = methodsFromClass.get(classDependedUpon);
if (methodsDependedUpon != null) {
for (ITestNGMethod mdu : methodsDependedUpon) {
result.put(mdu, m);
}
}
}
}
return result;
}
/**
* Logs the beginning of the {@link #beforeRun()} .
*/
private void logStart() {
log(3,
"Running test " + m_testName + " on " + m_classMap.size() + " " + " classes, "
+ " included groups:[" + mapToString(m_xmlMethodSelector.getIncludedGroups())
+ "] excluded groups:[" + mapToString(m_xmlMethodSelector.getExcludedGroups()) + "]");
if (getVerbose() >= 3) {
for (ITestClass tc : m_classMap.values()) {
((TestClass) tc).dump();
}
}
}
/**
* Trigger the start/finish event.
*
* @param isStart <tt>true</tt> if the event is for start, <tt>false</tt> if the
* event is for finish
*/
private void fireEvent(boolean isStart) {
for (ITestListener itl : m_testListeners) {
if (isStart) {
itl.onStart(this);
}
else {
itl.onFinish(this);
}
}
}
/////
// ITestContext
//
@Override
public String getName() {
return m_testName;
}
/**
* @return Returns the startDate.
*/
@Override
public Date getStartDate() {
return m_startDate;
}
/**
* @return Returns the endDate.
*/
@Override
public Date getEndDate() {
return m_endDate;
}
@Override
public IResultMap getPassedTests() {
return m_passedTests;
}
@Override
public IResultMap getSkippedTests() {
return m_skippedTests;
}
@Override
public IResultMap getFailedTests() {
return m_failedTests;
}
@Override
public IResultMap getFailedButWithinSuccessPercentageTests() {
return m_failedButWithinSuccessPercentageTests;
}
@Override
public String[] getIncludedGroups() {
Map<String, String> ig= m_xmlMethodSelector.getIncludedGroups();
String[] result= ig.values().toArray((new String[ig.size()]));
return result;
}
@Override
public String[] getExcludedGroups() {
Map<String, String> eg= m_xmlMethodSelector.getExcludedGroups();
String[] result= eg.values().toArray((new String[eg.size()]));
return result;
}
@Override
public String getOutputDirectory() {
return m_outputDirectory;
}
/**
* @return Returns the suite.
*/
@Override
public ISuite getSuite() {
return m_suite;
}
@Override
public ITestNGMethod[] getAllTestMethods() {
return m_allTestMethods;
}
@Override
public String getHost() {
return m_host;
}
@Override
public Collection<ITestNGMethod> getExcludedMethods() {
Map<ITestNGMethod, ITestNGMethod> vResult = Maps.newHashMap();
for (ITestNGMethod m : m_excludedMethods) {
vResult.put(m, m);
}
return vResult.keySet();
}
/**
* @see org.testng.ITestContext#getFailedConfigurations()
*/
@Override
public IResultMap getFailedConfigurations() {
return m_failedConfigurations;
}
/**
* @see org.testng.ITestContext#getPassedConfigurations()
*/
@Override
public IResultMap getPassedConfigurations() {
return m_passedConfigurations;
}
/**
* @see org.testng.ITestContext#getSkippedConfigurations()
*/
@Override
public IResultMap getSkippedConfigurations() {
return m_skippedConfigurations;
}
//
// ITestContext
/////
/////
// ITestResultNotifier
//
@Override
public void addPassedTest(ITestNGMethod tm, ITestResult tr) {
m_passedTests.addResult(tr, tm);
}
@Override
public Set<ITestResult> getPassedTests(ITestNGMethod tm) {
return m_passedTests.getResults(tm);
}
@Override
public Set<ITestResult> getFailedTests(ITestNGMethod tm) {
return m_failedTests.getResults(tm);
}
@Override
public Set<ITestResult> getSkippedTests(ITestNGMethod tm) {
return m_skippedTests.getResults(tm);
}
@Override
public void addSkippedTest(ITestNGMethod tm, ITestResult tr) {
m_skippedTests.addResult(tr, tm);
}
@Override
public void addInvokedMethod(InvokedMethod im) {
synchronized(m_invokedMethods) {
m_invokedMethods.add(im);
}
}
@Override
public void addFailedTest(ITestNGMethod testMethod, ITestResult result) {
logFailedTest(testMethod, result, false /* withinSuccessPercentage */);
}
@Override
public void addFailedButWithinSuccessPercentageTest(ITestNGMethod testMethod,
ITestResult result) {
logFailedTest(testMethod, result, true /* withinSuccessPercentage */);
}
@Override
public XmlTest getTest() {
return m_xmlTest;
}
@Override
public List<ITestListener> getTestListeners() {
return m_testListeners;
}
@Override
public List<IConfigurationListener> getConfigurationListeners() {
return Lists.<IConfigurationListener>newArrayList(m_configurationListeners);
}
//
// ITestResultNotifier
/////
private void logFailedTest(ITestNGMethod method,
ITestResult tr,
boolean withinSuccessPercentage) {
/*
* We should not remove a passed method from m_passedTests so that we can
* account for the passed instances of this test method.
*/
//m_passedTests.removeResult(method);
if (withinSuccessPercentage) {
m_failedButWithinSuccessPercentageTests.addResult(tr, method);
}
else {
m_failedTests.addResult(tr, method);
}
}
private String mapToString(Map<?, ?> m) {
StringBuffer result= new StringBuffer();
for (Object o : m.values()) {
result.append(o.toString()).append(" ");
}
return result.toString();
}
private void log(int level, String s) {
Utils.log("TestRunner", level, s);
}
public static int getVerbose() {
return m_verbose;
}
public void setVerbose(int n) {
m_verbose = n;
}
private void log(String s) {
Utils.log("TestRunner", 2, s);
}
/////
// Listeners
//
public void addListener(Object listener) {
if(listener instanceof ITestListener) {
addTestListener((ITestListener) listener);
}
if(listener instanceof IConfigurationListener) {
addConfigurationListener((IConfigurationListener) listener);
}
}
public void addTestListener(ITestListener il) {
m_testListeners.add(il);
}
void addConfigurationListener(IConfigurationListener icl) {
m_configurationListeners.add(icl);
}
//
// Listeners
/////
private final List<InvokedMethod> m_invokedMethods = Lists.newArrayList();
private void dumpInvokedMethods() {
System.out.println("===== Invoked methods");
for (IInvokedMethod im : m_invokedMethods) {
if (im.isTestMethod()) {
System.out.print(" ");
}
else if (im.isConfigurationMethod()) {
System.out.print(" ");
}
else {
continue;
}
System.out.println("" + im);
}
System.out.println("=====");
}
public List<ITestNGMethod> getInvokedMethods() {
List<ITestNGMethod> result= Lists.newArrayList();
synchronized(m_invokedMethods) {
for (IInvokedMethod im : m_invokedMethods) {
ITestNGMethod tm= im.getTestMethod();
tm.setDate(im.getDate());
result.add(tm);
}
}
return result;
}
private IResultMap m_passedConfigurations= new ResultMap();
private IResultMap m_skippedConfigurations= new ResultMap();
private IResultMap m_failedConfigurations= new ResultMap();
private class ConfigurationListener implements IConfigurationListener2 {
@Override
public void beforeConfiguration(ITestResult tr) {
}
@Override
public void onConfigurationFailure(ITestResult itr) {
m_failedConfigurations.addResult(itr, itr.getMethod());
}
@Override
public void onConfigurationSkip(ITestResult itr) {
m_skippedConfigurations.addResult(itr, itr.getMethod());
}
@Override
public void onConfigurationSuccess(ITestResult itr) {
m_passedConfigurations.addResult(itr, itr.getMethod());
}
}
public void setMethodInterceptor(IMethodInterceptor methodInterceptor) {
m_methodInterceptor = methodInterceptor;
}
@Override
public XmlTest getCurrentXmlTest() {
return m_xmlTest;
}
private IAttributes m_attributes = new Attributes();
@Override
public Object getAttribute(String name) {
return m_attributes.getAttribute(name);
}
@Override
public void setAttribute(String name, Object value) {
m_attributes.setAttribute(name, value);
}
@Override
public Set<String> getAttributeNames() {
return m_attributes.getAttributeNames();
}
@Override
public Object removeAttribute(String name) {
return m_attributes.removeAttribute(name);
}
private ListMultiMap<Class<? extends Module>, Module> m_guiceModules = Maps.newListMultiMap();
@Override
public List<Module> getGuiceModules(Class<? extends Module> cls) {
List<Module> result = m_guiceModules.get(cls);
return result;
}
@Override
public void addGuiceModule(Class<? extends Module> cls, Module module) {
m_guiceModules.put(cls, module);
}
private Map<List<Module>, Injector> m_injectors = Maps.newHashMap();
@Override
public Injector getInjector(List<Module> moduleInstances) {
return m_injectors .get(moduleInstances);
}
@Override
public void addInjector(List<Module> moduleInstances, Injector injector) {
m_injectors.put(moduleInstances, injector);
}
} // TestRunner