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 }