package org.limewire.util;
import java.io.File;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Timer;
import java.util.TimerTask;
import org.limewire.service.ErrorCallback;
import org.limewire.service.ErrorService;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestResult;
import junit.framework.TestSuite;
/** Utility test-case class that others can extend for easier testing. */
public abstract class BaseTestCase extends AssertComparisons implements UncaughtExceptionHandler,
ErrorCallback {
protected volatile static Class _testClass;
private final static Timer _testKillerTimer = new Timer(true);
protected volatile static String _currentTestName;
protected Thread _testThread;
protected TestResult _testResult;
protected TimerTask _testKiller;
protected long _startTimeForTest;
protected Class<? extends Throwable> expectedException;
/**
* bug 6435126.
*/
private static final Thread INTERRUPT_FIXER = new Thread() {
@Override
public void run() {
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException ignore) {
}
}
};
static {
INTERRUPT_FIXER.setDaemon(true);
INTERRUPT_FIXER.start();
DeadlockMonitor.startDeadlockMonitoring();
}
/**
* The base constructor. Nothing should ever be initialized in the
* constructor. This is because of the way JUnit sets up tests -- It first
* builds a new instance of the class for every possible test, then it runs
* through those instances, calling the appropriate test. All pre & post
* initializations that are necessary for every test should be in the new
* 'preSetUp' and 'postTearDown' methods.
*/
public BaseTestCase() {
super();
_testClass = getClass();
}
/**
* The base constructor. Nothing should ever be initialized in the
* constructor. This is because of the way JUnit sets up tests -- It first
* builds a new instance of the class for every possible test, then it runs
* through those instances, calling the appropriate test. All pre & post
* initializations that are necessary for every test should be in the new
* 'preSetUp' and 'postTearDown' methods.
*/
public BaseTestCase(String name) {
super(name);
_testClass = getClass();
}
/**
* Build a test suite containing all of the test methods in the given class
*
* @param cls The test class (must be subclassed from TestCase)
* @return <tt>TestSuite</tt> object that can be returned by suite method
*/
@SuppressWarnings("unchecked")
protected static TestSuite buildSingleTestSuite(Class cls) {
_testClass = cls;
String method = System.getProperty("junit.test.method");
if (method != null) {
method = method.trim();
if (!"".equals(method) && !"${method}".equals(method)) {
StringTokenizer st = new StringTokenizer(method, ",");
List l = new LinkedList();
while (st.hasMoreTokens())
l.add(st.nextToken());
String[] tests = (String[]) l.toArray(new String[l.size()]);
return buildTestSuite(cls, tests);
}
}
return new LimeTestSuite(cls);
}
@SuppressWarnings("unchecked")
protected static TestSuite buildTestSuite(Class cls) {
TestSuite suite = buildSingleTestSuite(cls);
String timesP = System.getProperty("junit.test.times", "1");
int times = 1;
try {
times = Integer.parseInt(timesP);
} catch (NumberFormatException ignored) {
}
List tests = new LinkedList();
for (Enumeration e = suite.tests(); e.hasMoreElements();)
tests.add(e.nextElement());
while (times-- > 1) {
for (Iterator iter = tests.iterator(); iter.hasNext();)
suite.addTest((Test) iter.next());
}
// add a warning if we are running individual tests
if (!System.getProperty("junit.test.method", "${method}").equals("${method}"))
suite.addTest(warning("Warning - Full test suite has not been run."));
return suite;
}
/**
* Build a test suite containing a single test from a specified test class
*
* @param cls The test class (must be subclassed from TestCase)
* @param test The name of the test method in cls to be run
* @return <tt>TestSuite</tt> object that can be returned by suite method
*/
protected static TestSuite buildTestSuite(Class cls, String test) {
_testClass = cls;
return buildTestSuite(cls, new String[] { test });
}
/**
* Build a test suite containing a set of tests from a specified test class
*
* @param cls The test class (must be subclassed from TestCase)
* @param test Array containing the names of the test methods in cls to be
* run
* @return <tt>TestSuite</tt> object that can be returned by suite method
*/
protected static TestSuite buildTestSuite(Class cls, String[] tests) {
_testClass = cls;
TestSuite suite = new LimeTestSuite(false, cls);
for (int ii = 0; ii < tests.length; ii++) {
if (!tests[ii].startsWith("test"))
tests[ii] = "test" + tests[ii];
suite.addTest(TestSuite.createTest(cls, tests[ii]));
}
return suite;
}
/**
* Recursively delete a directory.
*/
protected static void cleanFiles(File dir, boolean deleteDirs) {
if (dir == null)
return;
File[] files = dir.listFiles();
for (int i = 0; files != null && i < files.length; i++) {
if (files[i].isDirectory()) {
cleanFiles(files[i], deleteDirs);
} else {
files[i].delete();
}
}
if (deleteDirs)
dir.delete();
}
/**
* Used in conjunction with <code>BaseTestCase</code>'s implementation of
* <code>ErrorService</code>. Allows a test to configure an expected
* <code>Exception</code>.
*
* @see #error(Throwable, String)
* @param t the expected Exception type
*/
protected void setExpectedException(Class<? extends Throwable> t) {
this.expectedException = t;
}
/*
* This is modified to run 'preSetUp' and 'postTearDown' as methods which
* all tests will run, regardless of their implementation (or lack of) of
* setUp and tearDown.
*
* It is also modified so that if setUp throws something, tearDown will
* still be run.
*/
@Override
public void runBare() throws Throwable {
_currentTestName = getName();
if (System.getProperty("junit.test.hidetestname", "${hidetestname}").equals(
"${hidetestname}")) {
System.out.println("Running test: " + _currentTestName);
// LogFactory.getLog(this.getClass()).info("Running test: " +
// _currentTestName);
}
assertNotNull(_currentTestName);
Throwable thrown = null;
try {
preSetUp();
setUp();
runTest();
} catch (Throwable t) {
thrown = t;
throw thrown;
} finally {
try {
tearDown();
} catch (Throwable tearDown) {
// don't let throwables during tearDown
// overwrite throwables from the test
if (thrown == null)
throw tearDown;
} finally {
postTearDown();
}
}
}
/**
* Intercepted to allow us to get a handle to the test result, so we can add
* errors from the ErrorService callback (giving us errors that were
* triggered from outside of the test thread).
*/
@Override
public void run(TestResult result) {
_testResult = result;
super.run(result);
}
/**
* Called before each test's setUp. Used to determine which thread the test
* is running in, set up the testing directories, and possibly print
* debugging information (such as the current test being run) This must also
* set the ErrorService's callback, so it associates with the correct test
* object.
*/
protected void preSetUp() throws Exception {
_testThread = Thread.currentThread();
ErrorService.setErrorCallback(this);
Thread.setDefaultUncaughtExceptionHandler(this);
Thread.currentThread().setUncaughtExceptionHandler(this);
setupTestTimer();
}
/**
* Called after each test's tearDown. Used to remove directories and
* possibly other things.
*/
protected void postTearDown() {
stopTestTimer();
}
/** After all tearDown/postTearDown/globalTearDown teardowns. */
public static void afterAllTestsTearDown() throws Throwable {
System.gc();
}
/**
* Sets up the TimerTask to kill the running test after a certain amount of
* time.
*/
private final void setupTestTimer() {
_startTimeForTest = System.currentTimeMillis();
_testKiller = new TimerTask() {
@Override
public void run() {
long now = System.currentTimeMillis();
error(new RuntimeException("Stalled! Took " + (now - _startTimeForTest) + " ms."),
"Test Took Too Long");
}
};
// kill in a bit.
_testKillerTimer.schedule(_testKiller, 7 * 60 * 1000);
}
/**
* Stops the test timer since the test finished.
*/
private final void stopTestTimer() {
_testKiller.cancel();
_testKiller = null;
}
/**
* Fails the test with an AssertionFailedError and another error as the root
* cause.
*/
public static void fail(Throwable e) {
fail(null, e);
}
/**
* Fails the test with an AssertionFailedError and another error as the root
* cause, with a message.
*/
public static void fail(String message, Throwable e) {
throw new UnexpectedExceptionError(message, e);
}
/**
* Stub for error(Throwable, String)
*/
public void error(Throwable ex) {
error(ex, null);
}
/**
* This is the callback from ErrorService, and why we implement
* ErrorCallback.
* <p>
* It is used to catch errors that may or may not be inside of the test
* thread. If it is in the thread, we can just rethrow the error, and the
* test will fail as normal. If it is outside of the thread, we want the
* test results to remember the error, but we must allow the test to
* continue as normal, possibly succeeding, failing or erroring.
* <p>
* If an <code>expectedException</code> has been set, then this method will
* check to see if the incoming exception is of the correct class. If it is,
* then the call to this method is essentially ignored.
* <p>
* Note that while the XML formatter can easily handle the case of multiple
* failures/errors in a single test, the XML->HTML converter doesn't do that
* good of a job. It correctly lists the amount of errors/failures, but it
* will only write the last one as the status of the test, and will also
* only write the last one as the message/stacktrace.
*/
public void error(Throwable ex, String detail) {
if (expectedException != null && ex != null) {
if (expectedException.isInstance(ex)) {
return;
}
}
ex = new UnexpectedExceptionError(detail, ex); // remember the detail &
// stack trace of the
// ErrorService.
if (_testThread != Thread.currentThread()) {
// the Eclipse JUnit plug-in does not report multiple errors per
// test case:
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=125296
// print out stack trace to make debugging of uncaught exceptions
// easier
for (StackTraceElement item : _testThread.getStackTrace()) {
if (item.getClassName().contains("org.eclipse")) {
ex.printStackTrace();
break;
}
}
_testResult.addError(this, ex);
_testThread.interrupt();
} else {
fail("ErrorService callback error", ex);
}
}
/**
* Returns a test which will fail and log a warning message. Copied from
* JUnit's TestSuite.java Note that it does not have to extend BaseTestCase,
* just TestCase. BaseTestCase would add needless complexity to an otherwise
* simple failure message.
*/
private static Test warning(final String message) {
return new TestCase("warning") {
@Override
protected void runTest() {
fail(message);
}
};
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
setExpectedException(null);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
// instead of immediately failing, we forward the error
// to ErrorService -- this is so that tests can replace
// the ErrorService callback if they're expecting
// an uncaught exception.
ErrorService.error(e);
}
}