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;
}
}
}