The root of this problem is that JUnit38ClassRunner is retained by the suite and hence JUnit38ClassRunner.fTest retains a reference to the test instance. Simply clearing this field (via reflection) replaces all the crud in the code above.
For our case, we have a custom suite builder so we are able to replace JUnit38ClassRunner with the following:
Code:
public static class MemoryReleasingJUnit38ClassRunner extends JUnit38ClassRunner {
private Description description;
public MemoryReleasingJUnit38ClassRunner(Class<?> klass) {
super(klass);
}
public MemoryReleasingJUnit38ClassRunner(Test test) {
super(test);
}
@Override
public void run(RunNotifier notifier) {
super.run(notifier);
description = getDescription();
Field testField = ReflectionUtils.findField(this.getClass(), "fTest");
ReflectionUtils.makeAccessible(testField);
ReflectionUtils.setField(testField, this, null);
}
@Override
public Description getDescription() {
if (description == null) {
return super.getDescription();
} else {
return description;
}
}
}
And then inject into junit via:
Code:
public class ClasspathSuite extends Suite {
public ClasspathSuite(Class<?> suiteClass) throws InitializationError {
super(getRunnerBuilder(), suiteClass, asArray(sortTestClasses(findTestClasses())));
}
private static class MemoryReleasingJUnit3Builder extends org.junit.internal.builders.JUnit3Builder {
@Override
public Runner runnerForClass(Class<?> testClass) throws Throwable {
if (isPre4Test_(testClass)) {
return new MemoryReleasingJUnit38ClassRunner(testClass);
}
return null;
}
private boolean isPre4Test_(Class<?> testClass) {
return junit.framework.TestCase.class.isAssignableFrom(testClass);
}
}
private static class AllDefaultPossibilitiesBuilder extends org.junit.internal.builders.AllDefaultPossibilitiesBuilder {
public AllDefaultPossibilitiesBuilder(boolean canUseSuiteMethod) {
super(canUseSuiteMethod);
}
@Override
protected MemoryReleasingJUnit3Builder junit3Builder() {
return new MemoryReleasingJUnit3Builder();
}
}
private static RunnerBuilder getRunnerBuilder() {
return new AllDefaultPossibilitiesBuilder(true);
}
...
FYI, this bit of complexity is only required if you have very large test suites. Our unit-test suite contains 10k+ tests and our functional-test suite contains 2k+ tests, hence this little hack is critical for us.