package org.testng.junit; | |
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.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; | |
/** | |
* A JUnit TestRunner that records/triggers all information/events necessary to TestNG. | |
* | |
* @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a> | |
*/ | |
public class JUnitTestRunner implements TestListener, IJUnitTestRunner { | |
public static final String SUITE_METHODNAME = "suite"; | |
private ITestResultNotifier m_parentRunner; | |
private Map<Test, TestRunInfo> m_tests= new WeakHashMap<Test, TestRunInfo>(); | |
private List<ITestNGMethod> m_methods= 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 | |
*/ | |
public List<ITestNGMethod> getTestMethods() { | |
return m_methods; | |
} | |
public void setTestResultNotifier(ITestResultNotifier notifier) { | |
m_parentRunner= notifier; | |
} | |
/** | |
* @see junit.framework.TestListener#startTest(junit.framework.Test) | |
*/ | |
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) | |
*/ | |
public void addError(Test test, Throwable t) { | |
recordFailure(test, t); | |
} | |
/** | |
* @see junit.framework.TestListener#addFailure(junit.framework.Test, junit.framework.AssertionFailedError) | |
*/ | |
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) | |
*/ | |
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()); | |
} | |
private org.testng.internal.TestResult recordResults(Test test, TestRunInfo tri) { | |
JUnitUtils.JUnitTestClass tc= new JUnitUtils.JUnitTestClass(test); | |
JUnitUtils.JUnitTestMethod tm= new JUnitUtils.JUnitTestMethod(test, tc); | |
org.testng.internal.TestResult tr= new org.testng.internal.TestResult(tc, | |
test, | |
tm, | |
tri.m_failure, | |
tri.m_start, | |
Calendar.getInstance().getTimeInMillis()); | |
if(tri.isFailure()) { | |
tr.setStatus(ITestResult.FAILURE); | |
m_parentRunner.addFailedTest(tm, tr); | |
} | |
else { | |
m_parentRunner.addPassedTest(tm, tr); | |
} | |
m_parentRunner.addInvokedMethod(new InvokedMethod(test, tm, new Object[0], true, false, tri.m_start)); | |
m_methods.add(tm); | |
return tr; | |
} | |
private static void runTestListeners(ITestResult tr, List<ITestListener> 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) { | |
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 <code>start</code> implementation that ignores the <code>TestResult</code> | |
* @param testClass the JUnit test class | |
*/ | |
public void run(Class testClass) { | |
start(testClass); | |
} | |
/** | |
* Starts a test run. Analyzes the command line arguments and runs the given | |
* test suite. | |
*/ | |
public TestResult start(Class testCase) { | |
try { | |
Test suite = getTest(testCase); | |
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; | |
} | |
} | |
} |