Hi All,
Quite a few of the JCL unit tests need to be run with specific
classloader hierarchies set.
The current solution is to avoid running the standard
junit.textui.TestRunner class and instead run the
org.apache.commons.logging.Wrapper class which sets up any custom
classpath needed then executes the test.
However this solution moves the critical information about what is being
tested out of the TestCase classes and into a combination of the Wrapper
class and the build.xml file. This makes it hard to understand the
testcase code. It also makes the build.xml file quite complicated. It
makes it impossible to use the <batchtest> junit task - which Maven
depends on, therefore also making it impossible to use Maven to run the
tests. It also provides insufficient control over classpaths etc. to
implement the existing tests in the "demonstration" directory as unit
tests (in particular it doesn't provide parent-first/child-first options
for classloader behaviour).
I have come up with an alternative solution: have the testcase classes
create customised TestSuite objects instead. Here's a modified
org.apache.commons.logging.log4j.CustomConfigTestCase class:
public static Test suite() throws Exception {
PathableClassLoader parent = new PathableClassLoader(null);
parent.useSystemLoader("junit.");
PathableClassLoader child = new PathableClassLoader(parent);
child.addLogicalLib("testclasses");
child.addLogicalLib("log4j12");
child.addLogicalLib("commons-logging");
Class testClass =
child.loadClass(CustomConfigTestCase.class.getName());
return new PathableTestSuite(testClass, child);
}
The PathableTestSuite class is an almost trivial extension of the
standard junit TestSuite class. It takes an extra constructor parameter
being the "contextClassLoader" that should be used when running the test
case. And it overrides the standard runTest method to ensure the context
classloader is set before each test is run.
Note also that in the above code we're forcing the CustomConfigTestCase
class to be reloaded via a special classloader, which has the result of
ensuring all the tests are executed using that classloader. This isn't a
special feature of PathableTestSuite though; it works that way in junit.
The PathableClassLoader is also fairly simple. It derives from
java.net.URLClassLoader but:
* supports parent-first or child-first lookup
* provides the ability to look in the System classloader rather than
its parent for classes with arbitrary prefixes. This means that the
tests can be run without the System classloader in the path at all
(making the classpath really clean) yet make a few critical classes
visible (esp. the junit classes) to the test case.
* provides the ability for the using code to specify "logical" library
names rather than real URLs. For each logical name there is expected
to be a system property provided with the real URL to use.
I have this all working pretty well, and have converted log4j and jdk14
unit tests over to using this (only the suite method needs to be
changed).
Benefits:
* reasonably minor impact on existing unit tests
* testcases control their own classpaths rather than have that info
held elsewhere.
* simplifies build.xml significantly. Hopefully we can even get to the
point of running unit tests from maven.
* it should be possible to implement most of the tests in the
demonstration directory as unit tests.
Limitations:
* Cannot control result of ClassLoader.getSystemClassLoader. The
only way to do that is to start a JVM passing the desired system
classpath, ie the way that the existing build.xml does it. However
there is only one place in the whole of JCL that this call is made,
and that's only in the diagnostics code so that the system classloader
can be marked as SYSTEM in the diagnostic output. Note that this
approach *can* avoid having the system classloader as an ancestor of
the classloader used to load the unit test class which is what really
matters.
What do people think about this? Should I:
* commit this
* commit this to a branch for review
* rethink because of .......
Regards,
Simon
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]