001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.commons.logging;
019
020 import java.util.Properties;
021
022 import junit.framework.Test;
023 import junit.framework.TestResult;
024 import junit.framework.TestSuite;
025
026 /**
027 * Custom TestSuite class that can be used to control the context classloader
028 * in operation when a test runs.
029 * <p>
030 * For tests that need to control exactly what the classloader hierarchy is
031 * like when the test is run, something like the following is recommended:
032 * <pre>
033 * class SomeTestCase extends TestCase {
034 * public static Test suite() throws Exception {
035 * PathableClassLoader parent = new PathableClassLoader(null);
036 * parent.useSystemLoader("junit.");
037 *
038 * PathableClassLoader child = new PathableClassLoader(parent);
039 * child.addLogicalLib("testclasses");
040 * child.addLogicalLib("log4j12");
041 * child.addLogicalLib("commons-logging");
042 *
043 * Class testClass = child.loadClass(SomeTestCase.class.getName());
044 * ClassLoader contextClassLoader = child;
045 *
046 * PathableTestSuite suite = new PathableTestSuite(testClass, child);
047 * return suite;
048 * }
049 *
050 * // test methods go here
051 * }
052 * </pre>
053 * Note that if the suite method throws an exception then this will be handled
054 * reasonable gracefully by junit; it will report that the suite method for
055 * a test case failed with exception yyy.
056 * <p>
057 * The use of PathableClassLoader is not required to use this class, but it
058 * is expected that using the two classes together is common practice.
059 * <p>
060 * This class will run each test methods within the specified TestCase using
061 * the specified context classloader and system classloader. If different
062 * tests within the same class require different context classloaders,
063 * then the context classloader passed to the constructor should be the
064 * "lowest" one available, and tests that need the context set to some parent
065 * of this "lowest" classloader can call
066 * <pre>
067 * // NB: pseudo-code only
068 * setContextClassLoader(getContextClassLoader().getParent());
069 * </pre>
070 * This class ensures that any context classloader changes applied by a test
071 * is undone after the test is run, so tests don't need to worry about
072 * restoring the context classloader on exit. This class also ensures that
073 * the system properties are restored to their original settings after each
074 * test, so tests that manipulate those don't need to worry about resetting them.
075 * <p>
076 * This class does not provide facilities for manipulating system properties;
077 * tests that need specific system properties can simply set them in the
078 * fixture or at the start of a test method.
079 * <p>
080 * <b>Important!</b> When the test case is run, "this.getClass()" refers of
081 * course to the Class object passed to the constructor of this class - which
082 * is different from the class whose suite() method was executed to determine
083 * the classpath. This means that the suite method cannot communicate with
084 * the test cases simply by setting static variables (for example to make the
085 * custom classloaders available to the test methods or setUp/tearDown fixtures).
086 * If this is really necessary then it is possible to use reflection to invoke
087 * static methods on the class object passed to the constructor of this class.
088 * <p>
089 * <h2>Limitations</h2>
090 * <p>
091 * This class cannot control the system classloader (ie what method
092 * ClassLoader.getSystemClassLoader returns) because Java provides no
093 * mechanism for setting the system classloader. In this case, the only
094 * option is to invoke the unit test in a separate JVM with the appropriate
095 * settings.
096 * <p>
097 * The effect of using this approach in a system that uses junit's
098 * "reloading classloader" behaviour is unknown. This junit feature is
099 * intended for junit GUI apps where a test may be run multiple times
100 * within the same JVM - and in particular, when the .class file may
101 * be modified between runs of the test. How junit achieves this is
102 * actually rather weird (the whole junit code is rather weird in fact)
103 * and it is not clear whether this approach will work as expected in
104 * such situations.
105 */
106 public class PathableTestSuite extends TestSuite {
107
108 /**
109 * The classloader that should be set as the context classloader
110 * before each test in the suite is run.
111 */
112 private ClassLoader contextLoader;
113
114 /**
115 * Constructor.
116 *
117 * @param testClass is the TestCase that is to be run, as loaded by
118 * the appropriate ClassLoader.
119 *
120 * @param contextClassLoader is the loader that should be returned by
121 * calls to Thread.currentThread.getContextClassLoader from test methods
122 * (or any method called by test methods).
123 */
124 public PathableTestSuite(Class testClass, ClassLoader contextClassLoader) {
125 super(testClass);
126 contextLoader = contextClassLoader;
127 }
128
129 /**
130 * This method is invoked once for each Test in the current TestSuite.
131 * Note that a Test may itself be a TestSuite object (ie a collection
132 * of tests).
133 * <p>
134 * The context classloader and system properties are saved before each
135 * test, and restored after the test completes to better isolate tests.
136 */
137 public void runTest(Test test, TestResult result) {
138 ClassLoader origContext = Thread.currentThread().getContextClassLoader();
139 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 }