1package org.testng.junit;
2
3
4import java.lang.reflect.Constructor;
5import org.testng.ITestListener;
6import org.testng.ITestNGMethod;
7import org.testng.ITestResult;
8import org.testng.TestNGException;
9import org.testng.collections.Lists;
10import org.testng.internal.ITestResultNotifier;
11import org.testng.internal.InvokedMethod;
12
13import java.lang.reflect.InvocationTargetException;
14import java.lang.reflect.Method;
15import java.lang.reflect.Modifier;
16import java.util.Calendar;
17import java.util.Collection;
18import java.util.List;
19import java.util.Map;
20import java.util.WeakHashMap;
21
22import junit.framework.AssertionFailedError;
23import junit.framework.Test;
24import junit.framework.TestListener;
25import junit.framework.TestResult;
26import junit.framework.TestSuite;
27import org.testng.*;
28
29/**
30 * A JUnit TestRunner that records/triggers all information/events necessary to TestNG.
31 *
32 * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
33 */
34public class JUnitTestRunner implements TestListener, IJUnitTestRunner {
35  public static final String SUITE_METHODNAME = "suite";
36
37  private ITestResultNotifier m_parentRunner;
38
39  private Map<Test, TestRunInfo> m_tests= new WeakHashMap<>();
40  private List<ITestNGMethod> m_methods= Lists.newArrayList();
41  private Collection<IInvokedMethodListener> m_invokedMethodListeners = Lists.newArrayList();
42
43  public JUnitTestRunner() {
44  }
45
46  public JUnitTestRunner(ITestResultNotifier tr) {
47    m_parentRunner= tr;
48  }
49
50  /**
51   * Needed from TestRunner in order to figure out what JUnit test methods were run.
52   *
53   * @return the list of all JUnit test methods run
54   */
55  @Override
56  public List<ITestNGMethod> getTestMethods() {
57    return m_methods;
58  }
59
60  @Override
61  public void setTestResultNotifier(ITestResultNotifier notifier) {
62    m_parentRunner= notifier;
63  }
64
65  /**
66   * @see junit.framework.TestListener#startTest(junit.framework.Test)
67   */
68  @Override
69  public void startTest(Test test) {
70    m_tests.put(test, new TestRunInfo(Calendar.getInstance().getTimeInMillis()));
71  }
72
73
74  /**
75   * @see junit.framework.TestListener#addError(junit.framework.Test, java.lang.Throwable)
76   */
77  @Override
78  public void addError(Test test, Throwable t) {
79    recordFailure(test, t);
80  }
81
82  /**
83   * @see junit.framework.TestListener#addFailure(junit.framework.Test, junit.framework.AssertionFailedError)
84   */
85  @Override
86  public void addFailure(Test test, AssertionFailedError t) {
87    recordFailure(test, t);
88  }
89
90  private void recordFailure(Test test, Throwable t) {
91    TestRunInfo tri= m_tests.get(test);
92    if(null != tri) {
93      tri.setThrowable(t);
94    }
95  }
96
97  /**
98   * @see junit.framework.TestListener#endTest(junit.framework.Test)
99   */
100  @Override
101  public void endTest(Test test) {
102    TestRunInfo tri= m_tests.get(test);
103    if(null == tri) {
104      return; // HINT: this should never happen. How do I protect myself?
105    }
106
107    org.testng.internal.TestResult tr= recordResults(test, tri);
108
109    runTestListeners(tr, m_parentRunner.getTestListeners());
110  }
111
112    public void setInvokedMethodListeners(Collection<IInvokedMethodListener> listeners) {
113        m_invokedMethodListeners = listeners;
114    }
115
116
117  private org.testng.internal.TestResult recordResults(Test test, TestRunInfo tri)  {
118    JUnitTestClass tc= new JUnit3TestClass(test);
119    JUnitTestMethod tm= new JUnit3TestMethod(tc, test);
120
121    org.testng.internal.TestResult tr= new org.testng.internal.TestResult(tc,
122                                                                          test,
123                                                                          tm,
124                                                                          tri.m_failure,
125                                                                          tri.m_start,
126                                                                          Calendar.getInstance().getTimeInMillis(),
127                                                                          null);
128
129    if(tri.isFailure()) {
130      tr.setStatus(ITestResult.FAILURE);
131      m_parentRunner.addFailedTest(tm, tr);
132    }
133    else {
134      m_parentRunner.addPassedTest(tm, tr);
135    }
136
137    InvokedMethod im = new InvokedMethod(test, tm, new Object[0], tri.m_start, tr);
138    m_parentRunner.addInvokedMethod(im);
139    m_methods.add(tm);
140    for (IInvokedMethodListener l: m_invokedMethodListeners) {
141        l.beforeInvocation(im, tr);
142    }
143
144    return tr;
145  }
146
147  private static void runTestListeners(ITestResult tr, List<ITestListener> listeners) {
148    for (ITestListener itl : listeners) {
149      switch(tr.getStatus()) {
150        case ITestResult.SKIP: {
151          itl.onTestSkipped(tr);
152          break;
153        }
154        case ITestResult.SUCCESS_PERCENTAGE_FAILURE: {
155          itl.onTestFailedButWithinSuccessPercentage(tr);
156          break;
157        }
158        case ITestResult.FAILURE: {
159          itl.onTestFailure(tr);
160          break;
161        }
162        case ITestResult.SUCCESS: {
163          itl.onTestSuccess(tr);
164          break;
165        }
166
167        case ITestResult.STARTED: {
168          itl.onTestStart(tr);
169          break;
170        }
171
172        default: {
173          assert false : "UNKNOWN STATUS:" + tr;
174        }
175      }
176    }
177  }
178
179  /**
180   * Returns the Test corresponding to the given suite. This is
181   * a template method, subclasses override runFailed(), clearStatus().
182   */
183  protected Test getTest(Class testClass, String... methods) {
184    if (methods.length > 0) {
185      TestSuite ts = new TestSuite();
186      try {
187        Constructor c = testClass.getConstructor(String.class);
188        for (String m: methods) {
189          try {
190            ts.addTest((Test) c.newInstance(m));
191          } catch (InstantiationException ex) {
192            runFailed(testClass, "abstract class " + ex);
193          } catch (IllegalAccessException ex) {
194            runFailed(testClass, "constructor is not public " + ex);
195          } catch (IllegalArgumentException ex) {
196            runFailed(testClass, "actual and formal parameters differ " + ex);
197          } catch (InvocationTargetException ex) {
198            runFailed(testClass, "exception while instatiating test for method '" + m + "' " + ex);
199          }
200        }
201      } catch (NoSuchMethodException ex) {
202        runFailed(testClass, "no constructor accepting String argument found " + ex);
203      } catch (SecurityException ex) {
204        runFailed(testClass, "security exception " + ex);
205      }
206      return ts;
207    }
208    Method suiteMethod = null;
209    try {
210      suiteMethod = testClass.getMethod(SUITE_METHODNAME, new Class[0]);
211    }
212    catch (Exception e) {
213
214      // try to extract a test suite automatically
215      return new TestSuite(testClass);
216    }
217    if (!Modifier.isStatic(suiteMethod.getModifiers())) {
218      runFailed(testClass, "suite() method must be static");
219
220      return null;
221    }
222    Test test = null;
223    try {
224      test = (Test) suiteMethod.invoke(null, (Object[]) new Class[0]); // static method
225      if (test == null) {
226        return test;
227      }
228    }
229    catch (InvocationTargetException e) {
230      runFailed(testClass, "failed to invoke method suite():" + e.getTargetException().toString());
231
232      return null;
233    }
234    catch (IllegalAccessException e) {
235      runFailed(testClass, "failed to invoke method suite():" + e.toString());
236
237      return null;
238    }
239
240    return test;
241  }
242
243  /**
244   * A <code>start</code> implementation that ignores the <code>TestResult</code>
245   * @param testClass the JUnit test class
246   */
247  @Override
248  public void run(Class testClass, String... methods) {
249    start(testClass, methods);
250  }
251
252  /**
253   * Starts a test run. Analyzes the command line arguments and runs the given
254   * test suite.
255   */
256  public TestResult start(Class testCase, String... methods) {
257    try {
258      Test suite = getTest(testCase, methods);
259
260      if(null != suite) {
261        return doRun(suite);
262      }
263      else {
264        runFailed(testCase, "could not create/run JUnit test suite");
265      }
266    }
267    catch (Exception e) {
268      runFailed(testCase, "could not create/run JUnit test suite: " + e.getMessage());
269    }
270
271    return null;
272  }
273
274  protected void runFailed(Class clazz, String message) {
275    throw new TestNGException("Failure in JUnit mode for class " + clazz.getName() + ": " + message);
276  }
277
278  /**
279   * Creates the TestResult to be used for the test run.
280   */
281  protected TestResult createTestResult() {
282    return new TestResult();
283  }
284
285  protected TestResult doRun(Test suite) {
286    TestResult result = createTestResult();
287    result.addListener(this);
288    suite.run(result);
289
290    return result;
291  }
292
293  private static class TestRunInfo {
294    private final long m_start;
295    private Throwable m_failure;
296
297    public TestRunInfo(long start) {
298      m_start= start;
299    }
300
301    public boolean isFailure() {
302      return null != m_failure;
303    }
304
305    public void setThrowable(Throwable t) {
306      m_failure= t;
307    }
308  }
309}
310