1package org.testng;
2
3import static org.testng.internal.Utils.isStringBlank;
4
5import org.testng.collections.Lists;
6import org.testng.collections.Maps;
7import org.testng.internal.Attributes;
8import org.testng.internal.IConfiguration;
9import org.testng.internal.IInvoker;
10import org.testng.internal.Utils;
11import org.testng.internal.annotations.IAnnotationFinder;
12import org.testng.internal.thread.ThreadUtil;
13import org.testng.reporters.JUnitXMLReporter;
14import org.testng.reporters.TestHTMLReporter;
15import org.testng.reporters.TextReporter;
16import org.testng.xml.XmlSuite;
17import org.testng.xml.XmlTest;
18
19import java.io.File;
20import java.io.Serializable;
21import java.lang.reflect.Method;
22import java.util.ArrayList;
23import java.util.Arrays;
24import java.util.Collection;
25import java.util.Collections;
26import java.util.Comparator;
27import java.util.LinkedHashMap;
28import java.util.List;
29import java.util.Map;
30import java.util.Set;
31import com.google.inject.Injector;
32
33/**
34 * <CODE>SuiteRunner</CODE> is responsible for running all the tests included in one
35 * suite. The test start is triggered by {@link #run()} method.
36 *
37 * @author Cedric Beust, Apr 26, 2004
38 */
39public class SuiteRunner implements ISuite, Serializable, IInvokedMethodListener {
40
41  /* generated */
42  private static final long serialVersionUID = 5284208932089503131L;
43
44  private static final String DEFAULT_OUTPUT_DIR = "test-output";
45
46  private Map<String, ISuiteResult> m_suiteResults = Collections.synchronizedMap(Maps.<String, ISuiteResult>newLinkedHashMap());
47  transient private List<TestRunner> m_testRunners = Lists.newArrayList();
48  transient private Map<Class<? extends ISuiteListener>, ISuiteListener> m_listeners = Maps.newHashMap();
49  transient private TestListenerAdapter m_textReporter = new TestListenerAdapter();
50
51  private String m_outputDir; // DEFAULT_OUTPUT_DIR;
52  private XmlSuite m_suite;
53  private Injector m_parentInjector;
54
55  transient private List<ITestListener> m_testListeners = Lists.newArrayList();
56  transient private List<IClassListener> m_classListeners = Lists.newArrayList();
57  transient private ITestRunnerFactory m_tmpRunnerFactory;
58
59  transient private ITestRunnerFactory m_runnerFactory;
60  transient private boolean m_useDefaultListeners = true;
61
62  // The remote host where this suite was run, or null if run locally
63  private String m_host;
64
65  // The configuration
66  transient private IConfiguration m_configuration;
67
68  transient private ITestObjectFactory m_objectFactory;
69  transient private Boolean m_skipFailedInvocationCounts = Boolean.FALSE;
70
71  transient private List<IMethodInterceptor> m_methodInterceptors;
72  private Map<Class<? extends IInvokedMethodListener>, IInvokedMethodListener> m_invokedMethodListeners;
73
74  /** The list of all the methods invoked during this run */
75  private List<IInvokedMethod> m_invokedMethods =
76      Collections.synchronizedList(Lists.<IInvokedMethod>newArrayList());
77
78  private List<ITestNGMethod> m_allTestMethods = Lists.newArrayList();
79
80//  transient private IAnnotationTransformer m_annotationTransformer = null;
81
82  public SuiteRunner(IConfiguration configuration, XmlSuite suite,
83      String outputDir)
84  {
85    this(configuration, suite, outputDir, null);
86  }
87
88  public SuiteRunner(IConfiguration configuration, XmlSuite suite, String outputDir,
89      ITestRunnerFactory runnerFactory)
90  {
91    this(configuration, suite, outputDir, runnerFactory, false);
92  }
93
94  public SuiteRunner(IConfiguration configuration,
95      XmlSuite suite,
96      String outputDir,
97      ITestRunnerFactory runnerFactory,
98      boolean useDefaultListeners)
99  {
100    this(configuration, suite, outputDir, runnerFactory, useDefaultListeners,
101        new ArrayList<IMethodInterceptor>() /* method interceptor */,
102        null /* invoked method listeners */,
103        null /* test listeners */,
104        null /* class listeners */);
105  }
106
107  protected SuiteRunner(IConfiguration configuration,
108      XmlSuite suite,
109      String outputDir,
110      ITestRunnerFactory runnerFactory,
111      boolean useDefaultListeners,
112      List<IMethodInterceptor> methodInterceptors,
113      List<IInvokedMethodListener> invokedMethodListeners,
114      List<ITestListener> testListeners,
115      List<IClassListener> classListeners)
116  {
117    init(configuration, suite, outputDir, runnerFactory, useDefaultListeners, methodInterceptors, invokedMethodListeners, testListeners, classListeners);
118  }
119
120  private void init(IConfiguration configuration,
121    XmlSuite suite,
122    String outputDir,
123    ITestRunnerFactory runnerFactory,
124    boolean useDefaultListeners,
125    List<IMethodInterceptor> methodInterceptors,
126    List<IInvokedMethodListener> invokedMethodListener,
127    List<ITestListener> testListeners,
128    List<IClassListener> classListeners)
129  {
130    m_configuration = configuration;
131    m_suite = suite;
132    m_useDefaultListeners = useDefaultListeners;
133    m_tmpRunnerFactory= runnerFactory;
134    m_methodInterceptors = methodInterceptors != null ? methodInterceptors : new ArrayList<IMethodInterceptor>();
135    setOutputDir(outputDir);
136    m_objectFactory = m_configuration.getObjectFactory();
137    if(m_objectFactory == null) {
138      m_objectFactory = suite.getObjectFactory();
139    }
140    // Add our own IInvokedMethodListener
141    m_invokedMethodListeners = Maps.newHashMap();
142    if (invokedMethodListener != null) {
143      for (IInvokedMethodListener listener : invokedMethodListener) {
144        m_invokedMethodListeners.put(listener.getClass(), listener);
145      }
146    }
147    m_invokedMethodListeners.put(getClass(), this);
148
149    m_skipFailedInvocationCounts = suite.skipFailedInvocationCounts();
150    if (null != testListeners) {
151      m_testListeners.addAll(testListeners);
152    }
153    if (null != classListeners) {
154      m_classListeners.addAll(classListeners);
155    }
156    m_runnerFactory = buildRunnerFactory();
157
158    // Order the <test> tags based on their order of appearance in testng.xml
159    List<XmlTest> xmlTests = m_suite.getTests();
160    Collections.sort(xmlTests, new Comparator<XmlTest>() {
161      @Override
162      public int compare(XmlTest arg0, XmlTest arg1) {
163        return arg0.getIndex() - arg1.getIndex();
164      }
165    });
166
167    for (XmlTest test : xmlTests) {
168      TestRunner tr = m_runnerFactory.newTestRunner(this, test, m_invokedMethodListeners.values(), m_classListeners);
169
170      //
171      // Install the method interceptor, if any was passed
172      //
173      for (IMethodInterceptor methodInterceptor : m_methodInterceptors) {
174        tr.addMethodInterceptor(methodInterceptor);
175      }
176
177      // Reuse the same text reporter so we can accumulate all the results
178      // (this is used to display the final suite report at the end)
179      tr.addListener(m_textReporter);
180      m_testRunners.add(tr);
181
182      // Add the methods found in this test to our global count
183      m_allTestMethods.addAll(Arrays.asList(tr.getAllTestMethods()));
184    }
185  }
186
187  @Override
188  public XmlSuite getXmlSuite() {
189    return m_suite;
190  }
191
192  @Override
193  public String getName() {
194    return m_suite.getName();
195  }
196
197  public void setObjectFactory(ITestObjectFactory objectFactory) {
198    m_objectFactory = objectFactory;
199  }
200
201  public void setReportResults(boolean reportResults) {
202    m_useDefaultListeners = reportResults;
203  }
204
205  private void invokeListeners(boolean start) {
206    for (ISuiteListener sl : m_listeners.values()) {
207      if (start) {
208        sl.onStart(this);
209      }
210      else {
211        sl.onFinish(this);
212      }
213    }
214  }
215
216  private void setOutputDir(String outputdir) {
217    if (isStringBlank(outputdir) && m_useDefaultListeners) {
218      outputdir = DEFAULT_OUTPUT_DIR;
219    }
220
221    m_outputDir = (null != outputdir) ? new File(outputdir).getAbsolutePath()
222        : null;
223  }
224
225  private ITestRunnerFactory buildRunnerFactory() {
226    ITestRunnerFactory factory = null;
227
228    if (null == m_tmpRunnerFactory) {
229      factory = new DefaultTestRunnerFactory(m_configuration,
230          m_testListeners.toArray(new ITestListener[m_testListeners.size()]),
231          m_useDefaultListeners, m_skipFailedInvocationCounts);
232    }
233    else {
234      factory = new ProxyTestRunnerFactory(
235          m_testListeners.toArray(new ITestListener[m_testListeners.size()]),
236          m_tmpRunnerFactory);
237    }
238
239    return factory;
240  }
241
242  @Override
243  public String getParallel() {
244    return m_suite.getParallel().toString();
245  }
246
247  public String getParentModule() {
248    return m_suite.getParentModule();
249  }
250
251  @Override
252  public String getGuiceStage() {
253    return m_suite.getGuiceStage();
254  }
255
256  public Injector getParentInjector() {
257    return m_parentInjector;
258  }
259
260  public void setParentInjector(Injector injector) {
261    m_parentInjector = injector;
262  }
263
264  @Override
265  public void run() {
266    invokeListeners(true /* start */);
267    try {
268      privateRun();
269    }
270    finally {
271      invokeListeners(false /* stop */);
272    }
273  }
274
275  private void privateRun() {
276
277    // Map for unicity, Linked for guaranteed order
278    Map<Method, ITestNGMethod> beforeSuiteMethods= new LinkedHashMap<>();
279    Map<Method, ITestNGMethod> afterSuiteMethods = new LinkedHashMap<>();
280
281    IInvoker invoker = null;
282
283    // Get the invoker and find all the suite level methods
284    for (TestRunner tr: m_testRunners) {
285      // TODO: Code smell.  Invoker should belong to SuiteRunner, not TestRunner
286      // -- cbeust
287      invoker = tr.getInvoker();
288
289      for (ITestNGMethod m : tr.getBeforeSuiteMethods()) {
290        beforeSuiteMethods.put(m.getMethod(), m);
291      }
292
293      for (ITestNGMethod m : tr.getAfterSuiteMethods()) {
294        afterSuiteMethods.put(m.getMethod(), m);
295      }
296    }
297
298    //
299    // Invoke beforeSuite methods (the invoker can be null
300    // if the suite we are currently running only contains
301    // a <file-suite> tag and no real tests)
302    //
303    if (invoker != null) {
304      if(beforeSuiteMethods.values().size() > 0) {
305        invoker.invokeConfigurations(null,
306            beforeSuiteMethods.values().toArray(new ITestNGMethod[beforeSuiteMethods.size()]),
307            m_suite, m_suite.getParameters(), null, /* no parameter values */
308            null /* instance */
309        );
310      }
311
312      Utils.log("SuiteRunner", 3, "Created " + m_testRunners.size() + " TestRunners");
313
314      //
315      // Run all the test runners
316      //
317      boolean testsInParallel = XmlSuite.ParallelMode.TESTS.equals(m_suite.getParallel());
318      if (!testsInParallel) {
319        runSequentially();
320      }
321      else {
322        runInParallelTestMode();
323      }
324
325//      SuitePlan sp = new SuitePlan();
326//      for (TestRunner tr : m_testRunners) {
327//        sp.addTestPlan(tr.getTestPlan());
328//      }
329
330//      sp.dump();
331
332      //
333      // Invoke afterSuite methods
334      //
335      if (afterSuiteMethods.values().size() > 0) {
336        invoker.invokeConfigurations(null,
337              afterSuiteMethods.values().toArray(new ITestNGMethod[afterSuiteMethods.size()]),
338              m_suite, m_suite.getAllParameters(), null, /* no parameter values */
339
340              null /* instance */);
341      }
342    }
343  }
344
345  private List<IReporter> m_reporters = Lists.newArrayList();
346
347  private void addReporter(IReporter listener) {
348    m_reporters.add(listener);
349  }
350
351  void addConfigurationListener(IConfigurationListener listener) {
352    m_configuration.addConfigurationListener(listener);
353  }
354
355  public List<IReporter> getReporters() {
356    return m_reporters;
357  }
358
359  private void runSequentially() {
360    for (TestRunner tr : m_testRunners) {
361      runTest(tr);
362    }
363  }
364
365  private void runTest(TestRunner tr) {
366    tr.run();
367
368    ISuiteResult sr = new SuiteResult(m_suite, tr);
369    m_suiteResults.put(tr.getName(), sr);
370  }
371
372  /**
373   * Implement <suite parallel="tests">.
374   * Since this kind of parallelism happens at the suite level, we need a special code path
375   * to execute it.  All the other parallelism strategies are implemented at the test level
376   * in TestRunner#createParallelWorkers (but since this method deals with just one <test>
377   * tag, it can't implement <suite parallel="tests">, which is why we're doing it here).
378   */
379  private void runInParallelTestMode() {
380    List<Runnable> tasks= Lists.newArrayList(m_testRunners.size());
381    for(TestRunner tr: m_testRunners) {
382      tasks.add(new SuiteWorker(tr));
383    }
384
385    ThreadUtil.execute(tasks, m_suite.getThreadCount(),
386        m_suite.getTimeOut(XmlTest.DEFAULT_TIMEOUT_MS), false);
387  }
388
389  private class SuiteWorker implements Runnable {
390      private TestRunner m_testRunner;
391
392      public SuiteWorker(TestRunner tr) {
393        m_testRunner = tr;
394      }
395
396      @Override
397      public void run() {
398        Utils.log("[SuiteWorker]", 4, "Running XML Test '"
399                  +  m_testRunner.getTest().getName() + "' in Parallel");
400        runTest(m_testRunner);
401      }
402  }
403
404  /**
405   * Registers ISuiteListeners interested in reporting the result of the current
406   * suite.
407   *
408   * @param reporter
409   */
410  protected void addListener(ISuiteListener reporter) {
411    m_listeners.put(reporter.getClass(), reporter);
412  }
413
414  @Override
415  public void addListener(ITestNGListener listener) {
416    if (listener instanceof IInvokedMethodListener) {
417      IInvokedMethodListener invokedMethodListener = (IInvokedMethodListener) listener;
418      m_invokedMethodListeners.put(invokedMethodListener.getClass(), invokedMethodListener);
419    }
420    if (listener instanceof ISuiteListener) {
421      addListener((ISuiteListener) listener);
422    }
423    if (listener instanceof IReporter) {
424      addReporter((IReporter) listener);
425    }
426    if (listener instanceof IConfigurationListener) {
427      addConfigurationListener((IConfigurationListener) listener);
428    }
429  }
430
431  @Override
432  public String getOutputDirectory() {
433    return m_outputDir + File.separatorChar + getName();
434  }
435
436  @Override
437  public Map<String, ISuiteResult> getResults() {
438    return m_suiteResults;
439  }
440
441  /**
442   * FIXME: should be removed?
443   *
444   * @see org.testng.ISuite#getParameter(java.lang.String)
445   */
446  @Override
447  public String getParameter(String parameterName) {
448    return m_suite.getParameter(parameterName);
449  }
450
451  /**
452   * @see org.testng.ISuite#getMethodsByGroups()
453   */
454  @Override
455  public Map<String, Collection<ITestNGMethod>> getMethodsByGroups() {
456    Map<String, Collection<ITestNGMethod>> result = Maps.newHashMap();
457
458    for (TestRunner tr : m_testRunners) {
459      ITestNGMethod[] methods = tr.getAllTestMethods();
460      for (ITestNGMethod m : methods) {
461        String[] groups = m.getGroups();
462        for (String groupName : groups) {
463          Collection<ITestNGMethod> testMethods = result.get(groupName);
464          if (null == testMethods) {
465            testMethods = Lists.newArrayList();
466            result.put(groupName, testMethods);
467          }
468          testMethods.add(m);
469        }
470      }
471    }
472
473    return result;
474  }
475
476  /**
477   * @see org.testng.ISuite#getInvokedMethods()
478   */
479  @Override
480  public Collection<ITestNGMethod> getInvokedMethods() {
481    return getIncludedOrExcludedMethods(true /* included */);
482  }
483
484  /**
485   * @see org.testng.ISuite#getExcludedMethods()
486   */
487  @Override
488  public Collection<ITestNGMethod> getExcludedMethods() {
489    return getIncludedOrExcludedMethods(false/* included */);
490  }
491
492  private Collection<ITestNGMethod> getIncludedOrExcludedMethods(boolean included) {
493    List<ITestNGMethod> result= Lists.newArrayList();
494
495    for (TestRunner tr : m_testRunners) {
496      Collection<ITestNGMethod> methods = included ? tr.getInvokedMethods() : tr.getExcludedMethods();
497      for (ITestNGMethod m : methods) {
498        result.add(m);
499      }
500    }
501
502    return result;
503  }
504
505  @Override
506  public IObjectFactory getObjectFactory() {
507    return m_objectFactory instanceof IObjectFactory ? (IObjectFactory) m_objectFactory : null;
508  }
509
510  @Override
511  public IObjectFactory2 getObjectFactory2() {
512    return m_objectFactory instanceof IObjectFactory2 ? (IObjectFactory2) m_objectFactory : null;
513  }
514
515  /**
516   * Returns the annotation finder for the given annotation type.
517   * @return the annotation finder for the given annotation type.
518   */
519  @Override
520  public IAnnotationFinder getAnnotationFinder() {
521    return m_configuration.getAnnotationFinder();
522  }
523
524  public static void ppp(String s) {
525    System.out.println("[SuiteRunner] " + s);
526  }
527
528  /**
529   * The default implementation of {@link ITestRunnerFactory}.
530   */
531  private static class DefaultTestRunnerFactory implements ITestRunnerFactory {
532    private ITestListener[] m_failureGenerators;
533    private boolean m_useDefaultListeners;
534    private boolean m_skipFailedInvocationCounts;
535    private IConfiguration m_configuration;
536
537    public DefaultTestRunnerFactory(IConfiguration configuration,
538        ITestListener[] failureListeners,
539        boolean useDefaultListeners,
540        boolean skipFailedInvocationCounts)
541    {
542      m_configuration = configuration;
543      m_failureGenerators = failureListeners;
544      m_useDefaultListeners = useDefaultListeners;
545      m_skipFailedInvocationCounts = skipFailedInvocationCounts;
546    }
547
548    @Override
549    public TestRunner newTestRunner(ISuite suite, XmlTest test,
550        Collection<IInvokedMethodListener> listeners, List<IClassListener> classListeners) {
551      boolean skip = m_skipFailedInvocationCounts;
552      if (! skip) {
553        skip = test.skipFailedInvocationCounts();
554      }
555      TestRunner testRunner = new TestRunner(m_configuration, suite, test,
556          suite.getOutputDirectory(), suite.getAnnotationFinder(), skip,
557          listeners, classListeners);
558
559      if (m_useDefaultListeners) {
560        testRunner.addListener(new TestHTMLReporter());
561        testRunner.addListener(new JUnitXMLReporter());
562
563        //TODO: Moved these here because maven2 has output reporters running
564        //already, the output from these causes directories to be created with
565        //files. This is not the desired behaviour of running tests in maven2.
566        //Don't know what to do about this though, are people relying on these
567        //to be added even with defaultListeners set to false?
568        testRunner.addListener(new TextReporter(testRunner.getName(), TestRunner.getVerbose()));
569      }
570
571      for (ITestListener itl : m_failureGenerators) {
572        testRunner.addListener(itl);
573      }
574      for (IConfigurationListener cl : m_configuration.getConfigurationListeners()) {
575        testRunner.addConfigurationListener(cl);
576      }
577
578      return testRunner;
579    }
580  }
581
582  private static class ProxyTestRunnerFactory implements ITestRunnerFactory {
583    private ITestListener[] m_failureGenerators;
584    private ITestRunnerFactory m_target;
585
586    public ProxyTestRunnerFactory(ITestListener[] failureListeners, ITestRunnerFactory target) {
587      m_failureGenerators = failureListeners;
588      m_target= target;
589    }
590
591    @Override
592    public TestRunner newTestRunner(ISuite suite, XmlTest test,
593        Collection<IInvokedMethodListener> listeners, List<IClassListener> classListeners) {
594      TestRunner testRunner= m_target.newTestRunner(suite, test, listeners, classListeners);
595
596      testRunner.addListener(new TextReporter(testRunner.getName(), TestRunner.getVerbose()));
597
598      for (ITestListener itl : m_failureGenerators) {
599        testRunner.addListener(itl);
600      }
601
602      return testRunner;
603    }
604  }
605
606  public void setHost(String host) {
607    m_host = host;
608  }
609
610  @Override
611  public String getHost() {
612    return m_host;
613  }
614
615  private SuiteRunState m_suiteState= new SuiteRunState();
616
617  /**
618   * @see org.testng.ISuite#getSuiteState()
619   */
620  @Override
621  public SuiteRunState getSuiteState() {
622    return m_suiteState;
623  }
624
625  public void setSkipFailedInvocationCounts(Boolean skipFailedInvocationCounts) {
626    if (skipFailedInvocationCounts != null) {
627      m_skipFailedInvocationCounts = skipFailedInvocationCounts;
628    }
629  }
630
631  private IAttributes m_attributes = new Attributes();
632
633  @Override
634  public Object getAttribute(String name) {
635    return m_attributes.getAttribute(name);
636  }
637
638  @Override
639  public void setAttribute(String name, Object value) {
640    m_attributes.setAttribute(name, value);
641  }
642
643  @Override
644  public Set<String> getAttributeNames() {
645    return m_attributes.getAttributeNames();
646  }
647
648  @Override
649  public Object removeAttribute(String name) {
650    return m_attributes.removeAttribute(name);
651  }
652
653  /////
654  // implements IInvokedMethodListener
655  //
656
657  @Override
658  public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
659  }
660
661  @Override
662  public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
663    if (method == null) {
664      throw new NullPointerException("Method should not be null");
665    }
666    m_invokedMethods.add(method);
667  }
668
669  //
670  // implements IInvokedMethodListener
671  /////
672
673  @Override
674  public List<IInvokedMethod> getAllInvokedMethods() {
675    return m_invokedMethods;
676  }
677
678  @Override
679  public List<ITestNGMethod> getAllMethods() {
680    return m_allTestMethods;
681  }
682}
683