blob: d251264fc2800d50d8f99e750163b60434d139b7 [file] [log] [blame]
package org.testng.internal;
import org.testng.ClassMethodMap;
import org.testng.IMethodInstance;
import org.testng.ITestClass;
import org.testng.ITestContext;
import org.testng.ITestNGMethod;
import org.testng.ITestResult;
import org.testng.collections.Lists;
import org.testng.internal.thread.ThreadUtil;
import org.testng.internal.thread.graph.IWorker;
import org.testng.xml.XmlSuite;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* FIXME: reduce contention when this class is used through parallel invocation due to
* invocationCount and threadPoolSize by not invoking the @BeforeClass and @AfterClass
* which are already invoked on the original method.
*
* This class implements Runnable and will invoke the ITestMethod passed in its
* constructor on its run() method.
*
* @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
* @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
*/
public class TestMethodWorker implements IWorker<ITestNGMethod> {
// Map of the test methods and their associated instances
// It has to be a set because the same method can be passed several times
// and associated to a different instance
private IMethodInstance[] m_methodInstances;
private final IInvoker m_invoker;
private final Map<String, String> m_parameters;
private final XmlSuite m_suite;
private final ITestNGMethod[] m_allTestMethods;
private List<ITestResult> m_testResults = Lists.newArrayList();
private final ConfigurationGroupMethods m_groupMethods;
private final ClassMethodMap m_classMethodMap;
private final ITestContext m_testContext;
public TestMethodWorker(IInvoker invoker,
IMethodInstance[] testMethods,
XmlSuite suite,
Map<String, String> parameters,
ITestNGMethod[] allTestMethods,
ConfigurationGroupMethods groupMethods,
ClassMethodMap classMethodMap,
ITestContext testContext)
{
m_invoker = invoker;
m_methodInstances = testMethods;
m_suite = suite;
m_parameters = parameters;
m_allTestMethods = allTestMethods;
m_groupMethods = groupMethods;
m_classMethodMap = classMethodMap;
m_testContext = testContext;
}
/**
* Retrieves the maximum specified timeout of all ITestNGMethods to
* be run.
*
* @return the max timeout or 0 if no timeout was specified
*/
@Override
public long getTimeOut() {
long result = 0;
for (IMethodInstance mi : m_methodInstances) {
ITestNGMethod tm = mi.getMethod();
if (tm.getTimeOut() > result) {
result = tm.getTimeOut();
}
}
return result;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder("[Worker thread:" + Thread.currentThread().getId()
+ " priority:" + getPriority() + " ");
for (IMethodInstance m : m_methodInstances) {
result.append(m.getMethod()).append(" ");
}
result.append("]");
return result.toString();
}
/**
* Run all the ITestNGMethods passed in through the constructor.
*
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
for (IMethodInstance testMthdInst : m_methodInstances) {
ITestNGMethod testMethod = testMthdInst.getMethod();
ITestClass testClass = testMethod.getTestClass();
invokeBeforeClassMethods(testClass, testMthdInst);
// Invoke test method
try {
invokeTestMethods(testMethod, testMthdInst.getInstances(), m_testContext);
}
finally {
invokeAfterClassMethods(testClass, testMthdInst);
}
}
}
protected void invokeTestMethods(ITestNGMethod tm, Object[] instances,
ITestContext testContext)
{
// Potential bug here: we look up the method index of tm among all
// the test methods (not very efficient) but if this method appears
// several times and these methods are run in parallel, the results
// are unpredictable... Need to think about this more (and make it
// more efficient)
List<ITestResult> testResults =
m_invoker.invokeTestMethods(tm,
m_allTestMethods,
indexOf(tm, m_allTestMethods),
m_suite,
m_parameters,
m_groupMethods,
instances,
testContext);
if (testResults != null) {
m_testResults.addAll(testResults);
}
}
/**
* Invoke the @BeforeClass methods if not done already
* @param testClass
* @param mi
*/
protected void invokeBeforeClassMethods(ITestClass testClass, IMethodInstance mi) {
// if no BeforeClass than return immediately
// used for parallel case when BeforeClass were already invoked
if( (null == m_classMethodMap) || (null == m_classMethodMap.getInvokedBeforeClassMethods())) {
return;
}
ITestNGMethod[] classMethods= testClass.getBeforeClassMethods();
if(null == classMethods || classMethods.length == 0) {
return;
}
// the whole invocation must be synchronized as other threads must
// get a full initialized test object (not the same for @After)
Map<ITestClass, Set<Object>> invokedBeforeClassMethods =
m_classMethodMap.getInvokedBeforeClassMethods();
// System.out.println("SYNCHRONIZING ON " + testClass
// + " thread:" + Thread.currentThread().getId()
// + " invokedMap:" + invokedBeforeClassMethods.hashCode() + " "
// + invokedBeforeClassMethods);
synchronized(testClass) {
Set<Object> instances= invokedBeforeClassMethods.get(testClass);
if(null == instances) {
instances= new HashSet<Object>();
invokedBeforeClassMethods.put(testClass, instances);
}
for(Object instance: mi.getInstances()) {
if (! instances.contains(instance)) {
instances.add(instance);
m_invoker.invokeConfigurations(testClass,
testClass.getBeforeClassMethods(),
m_suite,
m_parameters,
null, /* no parameter values */
instance);
}
}
}
}
/**
* Invoke the @AfterClass methods if not done already
* @param testClass
* @param mi
*/
protected void invokeAfterClassMethods(ITestClass testClass, IMethodInstance mi) {
// if no BeforeClass than return immediately
// used for parallel case when BeforeClass were already invoked
if( (null == m_classMethodMap) || (null == m_classMethodMap.getInvokedAfterClassMethods()) ) {
return;
}
ITestNGMethod[] afterClassMethods= testClass.getAfterClassMethods();
if(null == afterClassMethods || afterClassMethods.length == 0) {
return;
}
//
// Invoke after class methods if this test method is the last one
//
List<Object> invokeInstances= Lists.newArrayList();
ITestNGMethod tm= mi.getMethod();
if (m_classMethodMap.removeAndCheckIfLast(tm, mi.getInstance())) {
Map<ITestClass, Set<Object>> invokedAfterClassMethods
= m_classMethodMap.getInvokedAfterClassMethods();
synchronized(invokedAfterClassMethods) {
Set<Object> instances = invokedAfterClassMethods.get(testClass);
if(null == instances) {
instances= new HashSet<Object>();
invokedAfterClassMethods.put(testClass, instances);
}
for(Object inst: mi.getInstances()) {
if(! instances.contains(inst)) {
invokeInstances.add(inst);
}
}
}
for(Object inst: invokeInstances) {
m_invoker.invokeConfigurations(testClass,
afterClassMethods,
m_suite,
m_parameters,
null, /* no parameter values */
inst);
}
}
}
protected int indexOf(ITestNGMethod tm, ITestNGMethod[] allTestMethods) {
for (int i = 0; i < allTestMethods.length; i++) {
if (allTestMethods[i] == tm) {
return i;
}
}
return -1;
}
public List<ITestResult> getTestResults() {
return m_testResults;
}
private void ppp(String s) {
Utils.log("TestMethodWorker", 2, ThreadUtil.currentThreadInfo() + ":" + s);
}
@Override
public List<ITestNGMethod> getTasks()
{
List<ITestNGMethod> result = Lists.newArrayList();
for (IMethodInstance m : m_methodInstances) {
result.add(m.getMethod());
}
return result;
}
@Override
public int compareTo(IWorker<ITestNGMethod> other) {
return getPriority() - other.getPriority();
}
/**
* The priority of a worker is the priority of the first method it's going to run.
*/
@Override
public int getPriority() {
return m_methodInstances.length > 0
? m_methodInstances[0].getMethod().getPriority()
: 0;
}
}
/**
* Extends {@code TestMethodWorker} and is used to work on only a single method
* instance
*/
class SingleTestMethodWorker extends TestMethodWorker {
private static final ConfigurationGroupMethods EMPTY_GROUP_METHODS =
new ConfigurationGroupMethods(new ITestNGMethod[0],
new HashMap<String, List<ITestNGMethod>>(), new HashMap<String, List<ITestNGMethod>>());
public SingleTestMethodWorker(IInvoker invoker,
MethodInstance testMethod,
XmlSuite suite,
Map<String, String> parameters,
ITestNGMethod[] allTestMethods,
ITestContext testContext)
{
super(invoker,
new MethodInstance[] {testMethod},
suite,
parameters,
allTestMethods,
EMPTY_GROUP_METHODS,
null,
testContext);
}
}