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 protected IMethodInstance[] m_testMethods; protected IInvoker m_invoker = null; protected Map<String, String> m_parameters = null; protected XmlSuite m_suite = null; // protected Map<ITestClass, Set<Object>> m_invokedBeforeClassMethods = null; // protected Map<ITestClass, Set<Object>> m_invokedAfterClassMethods = null; protected ITestNGMethod[] m_allTestMethods; protected List<ITestResult> m_testResults = Lists.newArrayList(); protected ConfigurationGroupMethods m_groupMethods = null; protected ClassMethodMap m_classMethodMap = null; private ITestContext m_testContext = null; public TestMethodWorker(IInvoker invoker, IMethodInstance[] testMethods, XmlSuite suite, Map<String, String> parameters, ITestNGMethod[] allTestMethods, ConfigurationGroupMethods groupMethods, ClassMethodMap classMethodMap, ITestContext testContext) { m_invoker = invoker; m_testMethods = testMethods; m_suite = suite; m_parameters = parameters; // m_invokedBeforeClassMethods = invokedBeforeClassMethods; // m_invokedAfterClassMethods = invokedAfterClassMethods; 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 */ public long getTimeOut() { long result = 0; for (IMethodInstance mi : m_testMethods) { 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() + " "); result.append(m_testMethods[0].getMethod()); // result.append(" instances[0]:").append(m_testMethods[0].getInstances()[0]).append(" methods:"); // // for (IMethodInstance m : m_testMethods) { // result.append(m.getMethod()).append(" "); // } result.append("]"); return result.toString(); } /** * Run all the ITestNGMethods passed in through the constructor. * * @see java.lang.Runnable#run() */ public void run() { // Using an index here because we need to tell the invoker // the index of the current method for (int indexMethod = 0; indexMethod < m_testMethods.length; indexMethod++) { ITestNGMethod tm = m_testMethods[indexMethod].getMethod(); ITestClass testClass = tm.getTestClass(); invokeBeforeClassMethods(testClass, m_testMethods[indexMethod]); // // Invoke test method // try { invokeTestMethods(tm, m_testMethods[indexMethod].getInstances(), m_testContext); } finally { invokeAfterClassMethods(testClass, m_testMethods[indexMethod]); } } } 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 before class methods if not done already // 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); } } } } 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.getInstances()[0])) { 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); } public void setAllTestMethods(ITestNGMethod[] allTestMethods) { m_allTestMethods = allTestMethods; } @Override public List<ITestNGMethod> getTasks() { List<ITestNGMethod> result = Lists.newArrayList(); for (IMethodInstance m : m_testMethods) { result.add(m.getMethod()); } return result; } public int compareTo(IWorker<ITestNGMethod> other) { return getPriority() - other.getPriority(); } public int getPriority() { return m_testMethods.length == 1 ? m_testMethods[0].getMethod().getPriority() : 0; } } 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); } protected void invokeAfterClassMethods(ITestClass testClass, ITestNGMethod tm) { } protected void invokeBeforeClassMethods(ITestClass testClass) { } }