package org.limewire.util;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.limewire.service.ErrorCallback;
import org.limewire.service.ErrorService;
import junit.framework.AssertionFailedError;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestResult;
import junit.framework.TestSuite;
/**
* A modified TestSuite that allows the backends to always be shutdown after
* tests finished, regardless of halt-on-failure or halt-on-error.
*/
public class LimeTestSuite extends TestSuite implements ErrorCallback {
private TestResult _testResult = null;
private boolean _beforeTests = false;
private final Class _testClass;
private static final String preTestName = "globalSetUp";
private static final String postTestName = "globalTearDown";
/**
* Constructors...
*/
LimeTestSuite(boolean singleTest, Class testClass) {
super();
_testClass = testClass;
}
LimeTestSuite(Class a, String b) {
super(a, b);
_testClass = a;
}
LimeTestSuite(Class a) {
super(a);
_testClass = a;
}
/**
* Modified run. Before & after tests are run, it sets the error callback to
* itself to catch any stray errors.
*/
@Override
public void run(TestResult result) {
_beforeTests = true;
_testResult = result;
ErrorService.setErrorCallback(this);
// First try doing the before-tests-setup
try {
runStaticMethod("beforeAllTestsSetUp");
} catch (Throwable t) {
// If there is an error here, report it,
// run the after all tests tear down, and exit.
error(t);
try {
runStaticMethod("afterAllTestsTearDown");
} catch (Throwable t2) {
error(t2);
}
// /CLOVER:FLUSH
return;
}
// Then try running the preTest method
try {
runStaticMethod(preTestName);
} catch (TestFailedException tfe) {
// If it fails, run the post test and exit.
try {
runStaticMethod(postTestName);
} catch (TestFailedException tfe2) {
// oh well.
}
// /CLOVER:FLUSH
return;
}
// Try running all the tests.
try {
super.run(result);
} finally {
// Regardless of if any fail or not,
// always run the last post test & after all tests methods.
_beforeTests = false;
ErrorService.setErrorCallback(this);
try {
runStaticMethod(postTestName);
} catch (TestFailedException tfe) {
// oh well.
}
try {
runStaticMethod("afterAllTestsTearDown");
} catch (Throwable t) {
error(t);
}
}
// /CLOVER:FLUSH
}
public void runStaticMethod(String name) throws TestFailedException {
List methods = null;
try {
methods = getAllStaticMethods(_testClass, name);
if (methods.isEmpty())
return;
} catch (NoSuchMethodException e) {
return;
}
for (Iterator i = methods.iterator(); i.hasNext();) {
Method m = (Method) i.next();
if (!Modifier.isStatic(m.getModifiers())) {
runTest(warning("Method " + name + " must be declared static."), _testResult);
throw new TestFailedException();
} else if (!Modifier.isPublic(m.getModifiers())) {
runTest(warning("Method " + name + " must be declared public."), _testResult);
throw new TestFailedException();
} else {
try {
// If this method takes the class parameter, send it
if (m.getParameterTypes().length == 1)
m.invoke(null, new Object[] { _testClass });
// Otherwise use the no-arg invocation
else
m.invoke(null, new Object[] {});
} catch (InvocationTargetException e) {
runTest(reportError(e.getCause(), _testResult), _testResult);
throw new TestFailedException();
} catch (IllegalAccessException e) {
runTest(warning("Cannot access method: " + name, e), _testResult);
throw new TestFailedException();
}
}
}
}
/**
* Retrieves all static methods of the specified class (and all subclasses)
* that are the specified name and either take no parameters or a Class
* parameter. The method that takes a Class parameter takes priority if both
* are found.
* <p>
* Throws NoSuchMethodException if none are found.
*/
@SuppressWarnings("unchecked")
public List getAllStaticMethods(Class entryClass, String methodName)
throws NoSuchMethodException {
List<Method> methods = new LinkedList<Method>();
Class clazz = entryClass;
Class[] classes = new Class[] { Class.class };
while (clazz != null) {
Method add = null;
try {
add = clazz.getDeclaredMethod(methodName, classes);
} catch (NoSuchMethodException tryAgain) {
// If nothing with the class parameter, try with none
try {
add = clazz.getDeclaredMethod(methodName, (Class[]) null);
} catch (NoSuchMethodException ignored) {
}
}
// If we found a method, add it to the beginning of the list
if (add != null)
methods.add(0, add);
// Try again with a superclass.
clazz = clazz.getSuperclass();
}
if (methods.isEmpty())
throw new NoSuchMethodException("Invalid method: " + methodName);
return methods;
}
/**
* Stub for error(Throwable, String)
*/
public void error(Throwable ex) {
error(ex, null);
}
/**
* The error service callback.
*<p>
* This does a somewhat unusual thing to report the error. Instead of just
* appending it to the _testResult, it creates a new TestCase and has that
* fail. This has the advantage of reporting the error in a different
* 'test', ensuring that people aren't confused as to how the error was
* caused.
*/
public void error(Throwable ex, String detail) {
if (_testResult != null)
runTest(reportError(ex, _testResult), _testResult);
else
ex.printStackTrace();
}
/**
* Returns a test which will fail and log a warning message. Modified from
* JUnit's TestSuite.java's warning method. Note that it does not have to
* extend BaseTestCase, just TestCase. BaseTestCase would add needless
* complexity to an otherwise simple error report.
*/
private Test reportError(final Throwable thrown, final TestResult result) {
String testName = _beforeTests ? "LimeTestSuite - Before Test Errors"
: "LimeTestSuite - After Test Errors";
return new TestCase(testName) {
@Override
protected void runTest() {
if (thrown instanceof AssertionFailedError)
result.addFailure(this, (AssertionFailedError) thrown);
else
result.addError(this, thrown);
}
};
}
/**
* Returns a test which will fail and log a warning message.
*/
public static Test warning(final String message) {
return new TestCase("warning") {
@Override
protected void runTest() {
fail(message);
}
};
}
/**
* Returns a test which will fail and log a warning message.
*/
public static Test warning(final String message, final Throwable thrown) {
return new BaseTestCase("warning") {
@Override
public void preSetUp() {
}
@Override
public void postTearDown() {
}
@Override
protected void runTest() {
fail(message, thrown);
}
};
}
private class TestFailedException extends Exception {
TestFailedException() {
super();
}
}
}