package org.testng.junit; import java.lang.reflect.Constructor; import org.testng.ITestListener; import org.testng.ITestNGMethod; import org.testng.ITestResult; import org.testng.TestNGException; import org.testng.collections.Lists; import org.testng.internal.ITestResultNotifier; import org.testng.internal.InvokedMethod; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Calendar; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import junit.framework.AssertionFailedError; import junit.framework.Test; import junit.framework.TestListener; import junit.framework.TestResult; import junit.framework.TestSuite; import org.testng.*; /** * A JUnit TestRunner that records/triggers all information/events necessary to TestNG. * * @author Alexandru Popescu */ public class JUnitTestRunner implements TestListener, IJUnitTestRunner { public static final String SUITE_METHODNAME = "suite"; private ITestResultNotifier m_parentRunner; private Map m_tests= new WeakHashMap<>(); private List m_methods= Lists.newArrayList(); private Collection m_invokedMethodListeners = Lists.newArrayList(); public JUnitTestRunner() { } public JUnitTestRunner(ITestResultNotifier tr) { m_parentRunner= tr; } /** * Needed from TestRunner in order to figure out what JUnit test methods were run. * * @return the list of all JUnit test methods run */ @Override public List getTestMethods() { return m_methods; } @Override public void setTestResultNotifier(ITestResultNotifier notifier) { m_parentRunner= notifier; } /** * @see junit.framework.TestListener#startTest(junit.framework.Test) */ @Override public void startTest(Test test) { m_tests.put(test, new TestRunInfo(Calendar.getInstance().getTimeInMillis())); } /** * @see junit.framework.TestListener#addError(junit.framework.Test, java.lang.Throwable) */ @Override public void addError(Test test, Throwable t) { recordFailure(test, t); } /** * @see junit.framework.TestListener#addFailure(junit.framework.Test, junit.framework.AssertionFailedError) */ @Override public void addFailure(Test test, AssertionFailedError t) { recordFailure(test, t); } private void recordFailure(Test test, Throwable t) { TestRunInfo tri= m_tests.get(test); if(null != tri) { tri.setThrowable(t); } } /** * @see junit.framework.TestListener#endTest(junit.framework.Test) */ @Override public void endTest(Test test) { TestRunInfo tri= m_tests.get(test); if(null == tri) { return; // HINT: this should never happen. How do I protect myself? } org.testng.internal.TestResult tr= recordResults(test, tri); runTestListeners(tr, m_parentRunner.getTestListeners()); } public void setInvokedMethodListeners(Collection listeners) { m_invokedMethodListeners = listeners; } private org.testng.internal.TestResult recordResults(Test test, TestRunInfo tri) { JUnitTestClass tc= new JUnit3TestClass(test); JUnitTestMethod tm= new JUnit3TestMethod(tc, test); org.testng.internal.TestResult tr= new org.testng.internal.TestResult(tc, test, tm, tri.m_failure, tri.m_start, Calendar.getInstance().getTimeInMillis(), null); if(tri.isFailure()) { tr.setStatus(ITestResult.FAILURE); m_parentRunner.addFailedTest(tm, tr); } else { m_parentRunner.addPassedTest(tm, tr); } InvokedMethod im = new InvokedMethod(test, tm, new Object[0], tri.m_start, tr); m_parentRunner.addInvokedMethod(im); m_methods.add(tm); for (IInvokedMethodListener l: m_invokedMethodListeners) { l.beforeInvocation(im, tr); } return tr; } private static void runTestListeners(ITestResult tr, List listeners) { for (ITestListener itl : listeners) { switch(tr.getStatus()) { case ITestResult.SKIP: { itl.onTestSkipped(tr); break; } case ITestResult.SUCCESS_PERCENTAGE_FAILURE: { itl.onTestFailedButWithinSuccessPercentage(tr); break; } case ITestResult.FAILURE: { itl.onTestFailure(tr); break; } case ITestResult.SUCCESS: { itl.onTestSuccess(tr); break; } case ITestResult.STARTED: { itl.onTestStart(tr); break; } default: { assert false : "UNKNOWN STATUS:" + tr; } } } } /** * Returns the Test corresponding to the given suite. This is * a template method, subclasses override runFailed(), clearStatus(). */ protected Test getTest(Class testClass, String... methods) { if (methods.length > 0) { TestSuite ts = new TestSuite(); try { Constructor c = testClass.getConstructor(String.class); for (String m: methods) { try { ts.addTest((Test) c.newInstance(m)); } catch (InstantiationException ex) { runFailed(testClass, "abstract class " + ex); } catch (IllegalAccessException ex) { runFailed(testClass, "constructor is not public " + ex); } catch (IllegalArgumentException ex) { runFailed(testClass, "actual and formal parameters differ " + ex); } catch (InvocationTargetException ex) { runFailed(testClass, "exception while instatiating test for method '" + m + "' " + ex); } } } catch (NoSuchMethodException ex) { runFailed(testClass, "no constructor accepting String argument found " + ex); } catch (SecurityException ex) { runFailed(testClass, "security exception " + ex); } return ts; } Method suiteMethod = null; try { suiteMethod = testClass.getMethod(SUITE_METHODNAME, new Class[0]); } catch (Exception e) { // try to extract a test suite automatically return new TestSuite(testClass); } if (!Modifier.isStatic(suiteMethod.getModifiers())) { runFailed(testClass, "suite() method must be static"); return null; } Test test = null; try { test = (Test) suiteMethod.invoke(null, (Object[]) new Class[0]); // static method if (test == null) { return test; } } catch (InvocationTargetException e) { runFailed(testClass, "failed to invoke method suite():" + e.getTargetException().toString()); return null; } catch (IllegalAccessException e) { runFailed(testClass, "failed to invoke method suite():" + e.toString()); return null; } return test; } /** * A start implementation that ignores the TestResult * @param testClass the JUnit test class */ @Override public void run(Class testClass, String... methods) { start(testClass, methods); } /** * Starts a test run. Analyzes the command line arguments and runs the given * test suite. */ public TestResult start(Class testCase, String... methods) { try { Test suite = getTest(testCase, methods); if(null != suite) { return doRun(suite); } else { runFailed(testCase, "could not create/run JUnit test suite"); } } catch (Exception e) { runFailed(testCase, "could not create/run JUnit test suite: " + e.getMessage()); } return null; } protected void runFailed(Class clazz, String message) { throw new TestNGException("Failure in JUnit mode for class " + clazz.getName() + ": " + message); } /** * Creates the TestResult to be used for the test run. */ protected TestResult createTestResult() { return new TestResult(); } protected TestResult doRun(Test suite) { TestResult result = createTestResult(); result.addListener(this); suite.run(result); return result; } private static class TestRunInfo { private final long m_start; private Throwable m_failure; public TestRunInfo(long start) { m_start= start; } public boolean isFailure() { return null != m_failure; } public void setThrowable(Throwable t) { m_failure= t; } } }