1package org.testng.internal;
2
3import org.testng.ClassMethodMap;
4import org.testng.IClassListener;
5import org.testng.IMethodInstance;
6import org.testng.ITestClass;
7import org.testng.ITestContext;
8import org.testng.ITestNGMethod;
9import org.testng.ITestResult;
10import org.testng.collections.Lists;
11import org.testng.internal.thread.ThreadUtil;
12import org.testng.internal.thread.graph.IWorker;
13import org.testng.xml.XmlSuite;
14
15import java.util.HashMap;
16import java.util.HashSet;
17import java.util.List;
18import java.util.Map;
19import java.util.Set;
20
21/**
22 * FIXME: reduce contention when this class is used through parallel invocation due to
23 * invocationCount and threadPoolSize by not invoking the @BeforeClass and @AfterClass
24 * which are already invoked on the original method.
25 *
26 * This class implements Runnable and will invoke the ITestMethod passed in its
27 * constructor on its run() method.
28 *
29 * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
30 * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
31 */
32public class TestMethodWorker implements IWorker<ITestNGMethod> {
33  // Map of the test methods and their associated instances
34  // It has to be a set because the same method can be passed several times
35  // and associated to a different instance
36  private IMethodInstance[] m_methodInstances;
37  private final IInvoker m_invoker;
38  private final Map<String, String> m_parameters;
39  private final XmlSuite m_suite;
40  private List<ITestResult> m_testResults = Lists.newArrayList();
41  private final ConfigurationGroupMethods m_groupMethods;
42  private final ClassMethodMap m_classMethodMap;
43  private final ITestContext m_testContext;
44  private final List<IClassListener> m_listeners;
45
46  public TestMethodWorker(IInvoker invoker,
47                          IMethodInstance[] testMethods,
48                          XmlSuite suite,
49                          Map<String, String> parameters,
50                          ConfigurationGroupMethods groupMethods,
51                          ClassMethodMap classMethodMap,
52                          ITestContext testContext,
53                          List<IClassListener> listeners)
54  {
55    m_invoker = invoker;
56    m_methodInstances = testMethods;
57    m_suite = suite;
58    m_parameters = parameters;
59    m_groupMethods = groupMethods;
60    m_classMethodMap = classMethodMap;
61    m_testContext = testContext;
62    m_listeners = listeners;
63  }
64
65  /**
66   * Retrieves the maximum specified timeout of all ITestNGMethods to
67   * be run.
68   *
69   * @return the max timeout or 0 if no timeout was specified
70   */
71  @Override
72  public long getTimeOut() {
73    long result = 0;
74    for (IMethodInstance mi : m_methodInstances) {
75      ITestNGMethod tm = mi.getMethod();
76      if (tm.getTimeOut() > result) {
77        result = tm.getTimeOut();
78      }
79    }
80
81    return result;
82  }
83
84  @Override
85  public String toString() {
86    StringBuilder result = new StringBuilder("[Worker thread:" + Thread.currentThread().getId()
87        + " priority:" + getPriority() + " ");
88
89    for (IMethodInstance m : m_methodInstances) {
90      result.append(m.getMethod()).append(" ");
91    }
92    result.append("]");
93
94    return result.toString();
95  }
96
97  /**
98   * Run all the ITestNGMethods passed in through the constructor.
99   *
100   * @see java.lang.Runnable#run()
101   */
102  @Override
103  public void run() {
104    for (IMethodInstance testMthdInst : m_methodInstances) {
105      ITestNGMethod testMethod = testMthdInst.getMethod();
106      ITestClass testClass = testMethod.getTestClass();
107
108      invokeBeforeClassMethods(testClass, testMthdInst);
109
110      // Invoke test method
111      try {
112        invokeTestMethods(testMethod, testMthdInst.getInstance(), m_testContext);
113      }
114      finally {
115        invokeAfterClassMethods(testClass, testMthdInst);
116      }
117    }
118  }
119
120  protected void invokeTestMethods(ITestNGMethod tm, Object instance,
121      ITestContext testContext)
122  {
123    // Potential bug here:  we look up the method index of tm among all
124    // the test methods (not very efficient) but if this method appears
125    // several times and these methods are run in parallel, the results
126    // are unpredictable...  Need to think about this more (and make it
127    // more efficient)
128    List<ITestResult> testResults =
129        m_invoker.invokeTestMethods(tm,
130            m_suite,
131            m_parameters,
132            m_groupMethods,
133            instance,
134            testContext);
135
136    if (testResults != null) {
137      m_testResults.addAll(testResults);
138    }
139  }
140
141  /**
142   * Invoke the @BeforeClass methods if not done already
143   * @param testClass
144   * @param mi
145   */
146  protected void invokeBeforeClassMethods(ITestClass testClass, IMethodInstance mi) {
147    for (IClassListener listener : m_listeners) {
148      listener.onBeforeClass(testClass, mi);
149    }
150
151    // if no BeforeClass than return immediately
152    // used for parallel case when BeforeClass were already invoked
153    if( (null == m_classMethodMap) || (null == m_classMethodMap.getInvokedBeforeClassMethods())) {
154      return;
155    }
156    ITestNGMethod[] classMethods= testClass.getBeforeClassMethods();
157    if(null == classMethods || classMethods.length == 0) {
158      return;
159    }
160
161    // the whole invocation must be synchronized as other threads must
162    // get a full initialized test object (not the same for @After)
163    Map<ITestClass, Set<Object>> invokedBeforeClassMethods =
164        m_classMethodMap.getInvokedBeforeClassMethods();
165//    System.out.println("SYNCHRONIZING ON " + testClass
166//        + " thread:" + Thread.currentThread().getId()
167//        + " invokedMap:" + invokedBeforeClassMethods.hashCode() + " "
168//        + invokedBeforeClassMethods);
169    synchronized(testClass) {
170      Set<Object> instances= invokedBeforeClassMethods.get(testClass);
171      if(null == instances) {
172        instances= new HashSet<>();
173        invokedBeforeClassMethods.put(testClass, instances);
174      }
175      for(Object instance: mi.getInstances()) {
176        if (! instances.contains(instance)) {
177          instances.add(instance);
178          m_invoker.invokeConfigurations(testClass,
179                                         testClass.getBeforeClassMethods(),
180                                         m_suite,
181                                         m_parameters,
182                                         null, /* no parameter values */
183                                         instance);
184        }
185      }
186    }
187  }
188
189  /**
190   * Invoke the @AfterClass methods if not done already
191   * @param testClass
192   * @param mi
193   */
194  protected void invokeAfterClassMethods(ITestClass testClass, IMethodInstance mi) {
195    for (IClassListener listener : m_listeners) {
196      listener.onAfterClass(testClass, mi);
197    }
198
199    // if no BeforeClass than return immediately
200    // used for parallel case when BeforeClass were already invoked
201    if( (null == m_classMethodMap) || (null == m_classMethodMap.getInvokedAfterClassMethods()) ) {
202      return;
203    }
204    ITestNGMethod[] afterClassMethods= testClass.getAfterClassMethods();
205
206    if(null == afterClassMethods || afterClassMethods.length == 0) {
207      return;
208    }
209
210    //
211    // Invoke after class methods if this test method is the last one
212    //
213    List<Object> invokeInstances= Lists.newArrayList();
214    ITestNGMethod tm= mi.getMethod();
215    if (m_classMethodMap.removeAndCheckIfLast(tm, mi.getInstance())) {
216      Map<ITestClass, Set<Object>> invokedAfterClassMethods
217          = m_classMethodMap.getInvokedAfterClassMethods();
218      synchronized(invokedAfterClassMethods) {
219        Set<Object> instances = invokedAfterClassMethods.get(testClass);
220        if(null == instances) {
221          instances= new HashSet<>();
222          invokedAfterClassMethods.put(testClass, instances);
223        }
224        for(Object inst: mi.getInstances()) {
225          if(! instances.contains(inst)) {
226            invokeInstances.add(inst);
227          }
228        }
229      }
230
231      for(Object inst: invokeInstances) {
232        m_invoker.invokeConfigurations(testClass,
233                                       afterClassMethods,
234                                       m_suite,
235                                       m_parameters,
236                                       null, /* no parameter values */
237                                       inst);
238      }
239    }
240  }
241
242  protected int indexOf(ITestNGMethod tm, ITestNGMethod[] allTestMethods) {
243    for (int i = 0; i < allTestMethods.length; i++) {
244      if (allTestMethods[i] == tm) {
245        return i;
246      }
247    }
248    return -1;
249  }
250
251  public List<ITestResult> getTestResults() {
252    return m_testResults;
253  }
254
255  private void ppp(String s) {
256    Utils.log("TestMethodWorker", 2, ThreadUtil.currentThreadInfo() + ":" + s);
257  }
258
259  @Override
260  public List<ITestNGMethod> getTasks()
261  {
262    List<ITestNGMethod> result = Lists.newArrayList();
263    for (IMethodInstance m : m_methodInstances) {
264      result.add(m.getMethod());
265    }
266    return result;
267  }
268
269  @Override
270  public int compareTo(IWorker<ITestNGMethod> other) {
271    return getPriority() - other.getPriority();
272  }
273
274  /**
275   * The priority of a worker is the priority of the first method it's going to run.
276   */
277  @Override
278  public int getPriority() {
279    return m_methodInstances.length > 0
280        ? m_methodInstances[0].getMethod().getPriority()
281        : 0;
282  }
283}
284
285/**
286 * Extends {@code TestMethodWorker} and is used to work on only a single method
287 * instance
288 */
289class SingleTestMethodWorker extends TestMethodWorker {
290  private static final ConfigurationGroupMethods EMPTY_GROUP_METHODS =
291    new ConfigurationGroupMethods(new ITestNGMethod[0],
292        new HashMap<String, List<ITestNGMethod>>(), new HashMap<String, List<ITestNGMethod>>());
293
294  public SingleTestMethodWorker(IInvoker invoker,
295                                MethodInstance testMethod,
296                                XmlSuite suite,
297                                Map<String, String> parameters,
298                                ITestContext testContext,
299                                List<IClassListener> listeners)
300  {
301    super(invoker,
302          new MethodInstance[] {testMethod},
303          suite,
304          parameters,
305          EMPTY_GROUP_METHODS,
306          null,
307          testContext,
308          listeners);
309  }
310}
311