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 }