View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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 class loader
27   * in operation when a test runs.
28   * <p>
29   * For tests that need to control exactly what the class loader 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 class loader and system class loader. If different
61   * tests within the same class require different context class loaders,
62   * then the context class loader 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" class loader can call
65   * <pre>
66   *  // NB: pseudo-code only
67   *  setContextClassLoader(getContextClassLoader().getParent());
68   * </pre>
69   * This class ensures that any context class loader 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 class loader 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 class loaders 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 class loader (ie what method
91   * ClassLoader.getSystemClassLoader returns) because Java provides no
92   * mechanism for setting the system class loader. 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 class loader" behavior 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 class loader that should be set as the context class loader
109      * before each test in the suite is run.
110      */
111     private final ClassLoader contextLoader;
112 
113     /**
114      * Constructs a new instance.
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(final Class testClass, final 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 class loader and system properties are saved before each
134      * test, and restored after the test completes to better isolate tests.
135      */
136     @Override
137     public void runTest(final Test test, final TestResult result) {
138         final ClassLoader origContext = Thread.currentThread().getContextClassLoader();
139         final Properties oldSysProps = (Properties) System.getProperties().clone();
140         try {
141             Thread.currentThread().setContextClassLoader(contextLoader);
142             test.run(result);
143         } finally {
144             System.setProperties(oldSysProps);
145             Thread.currentThread().setContextClassLoader(origContext);
146         }
147     }
148 }