1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.logging;
18
19 import java.util.Properties;
20
21 import junit.framework.Test;
22 import junit.framework.TestResult;
23 import junit.framework.TestSuite;
24
25 /***
26 * Custom TestSuite class that can be used to control the context classloader
27 * in operation when a test runs.
28 * <p>
29 * For tests that need to control exactly what the classloader hierarchy is
30 * like when the test is run, something like the following is recommended:
31 * <pre>
32 * class SomeTestCase extends TestCase {
33 * public static Test suite() throws Exception {
34 * PathableClassLoader parent = new PathableClassLoader(null);
35 * parent.useSystemLoader("junit.");
36 *
37 * PathableClassLoader child = new PathableClassLoader(parent);
38 * child.addLogicalLib("testclasses");
39 * child.addLogicalLib("log4j12");
40 * child.addLogicalLib("commons-logging");
41 *
42 * Class testClass = child.loadClass(SomeTestCase.class.getName());
43 * ClassLoader contextClassLoader = child;
44 *
45 * PathableTestSuite suite = new PathableTestSuite(testClass, child);
46 * return suite;
47 * }
48 *
49 * // test methods go here
50 * }
51 * </pre>
52 * Note that if the suite method throws an exception then this will be handled
53 * reasonable gracefully by junit; it will report that the suite method for
54 * a test case failed with exception yyy.
55 * <p>
56 * The use of PathableClassLoader is not required to use this class, but it
57 * is expected that using the two classes together is common practice.
58 * <p>
59 * This class will run each test methods within the specified TestCase using
60 * the specified context classloader and system classloader. If different
61 * tests within the same class require different context classloaders,
62 * then the context classloader passed to the constructor should be the
63 * "lowest" one available, and tests that need the context set to some parent
64 * of this "lowest" classloader can call
65 * <pre>
66 * // NB: pseudo-code only
67 * setContextClassLoader(getContextClassLoader().getParent());
68 * </pre>
69 * This class ensures that any context classloader changes applied by a test
70 * is undone after the test is run, so tests don't need to worry about
71 * restoring the context classloader on exit. This class also ensures that
72 * the system properties are restored to their original settings after each
73 * test, so tests that manipulate those don't need to worry about resetting them.
74 * <p>
75 * This class does not provide facilities for manipulating system properties;
76 * tests that need specific system properties can simply set them in the
77 * fixture or at the start of a test method.
78 * <p>
79 * <b>Important!</b> When the test case is run, "this.getClass()" refers of
80 * course to the Class object passed to the constructor of this class - which
81 * is different from the class whose suite() method was executed to determine
82 * the classpath. This means that the suite method cannot communicate with
83 * the test cases simply by setting static variables (for example to make the
84 * custom classloaders available to the test methods or setUp/tearDown fixtures).
85 * If this is really necessary then it is possible to use reflection to invoke
86 * static methods on the class object passed to the constructor of this class.
87 * <p>
88 * <h2>Limitations</h2>
89 * <p>
90 * This class cannot control the system classloader (ie what method
91 * ClassLoader.getSystemClassLoader returns) because Java provides no
92 * mechanism for setting the system classloader. In this case, the only
93 * option is to invoke the unit test in a separate JVM with the appropriate
94 * settings.
95 * <p>
96 * The effect of using this approach in a system that uses junit's
97 * "reloading classloader" behaviour is unknown. This junit feature is
98 * intended for junit GUI apps where a test may be run multiple times
99 * within the same JVM - and in particular, when the .class file may
100 * be modified between runs of the test. How junit achieves this is
101 * actually rather weird (the whole junit code is rather weird in fact)
102 * and it is not clear whether this approach will work as expected in
103 * such situations.
104 */
105 public class PathableTestSuite extends TestSuite {
106
107 /***
108 * The classloader that should be set as the context classloader
109 * before each test in the suite is run.
110 */
111 private ClassLoader contextLoader;
112
113 /***
114 * Constructor.
115 *
116 * @param testClass is the TestCase that is to be run, as loaded by
117 * the appropriate ClassLoader.
118 *
119 * @param contextClassLoader is the loader that should be returned by
120 * calls to Thread.currentThread.getContextClassLoader from test methods
121 * (or any method called by test methods).
122 */
123 public PathableTestSuite(Class testClass, ClassLoader contextClassLoader) {
124 super(testClass);
125 contextLoader = contextClassLoader;
126 }
127
128 /***
129 * This method is invoked once for each Test in the current TestSuite.
130 * Note that a Test may itself be a TestSuite object (ie a collection
131 * of tests).
132 * <p>
133 * The context classloader and system properties are saved before each
134 * test, and restored after the test completes to better isolate tests.
135 */
136 public void runTest(Test test, TestResult result) {
137 ClassLoader origContext = Thread.currentThread().getContextClassLoader();
138 Properties oldSysProps = (Properties) System.getProperties().clone();
139 try {
140 Thread.currentThread().setContextClassLoader(contextLoader);
141 test.run(result);
142 } finally {
143 System.setProperties(oldSysProps);
144 Thread.currentThread().setContextClassLoader(origContext);
145 }
146 }
147 }