| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.logging; |
| |
| import java.util.Properties; |
| |
| import junit.framework.Test; |
| import junit.framework.TestResult; |
| import junit.framework.TestSuite; |
| |
| /** |
| * Custom TestSuite class that can be used to control the context classloader |
| * in operation when a test runs. |
| * <p> |
| * For tests that need to control exactly what the classloader hierarchy is |
| * like when the test is run, something like the following is recommended: |
| * <pre> |
| * class SomeTestCase extends TestCase { |
| * public static Test suite() throws Exception { |
| * PathableClassLoader parent = new PathableClassLoader(null); |
| * parent.useSystemLoader("junit."); |
| * |
| * PathableClassLoader child = new PathableClassLoader(parent); |
| * child.addLogicalLib("testclasses"); |
| * child.addLogicalLib("log4j12"); |
| * child.addLogicalLib("commons-logging"); |
| * |
| * Class testClass = child.loadClass(SomeTestCase.class.getName()); |
| * ClassLoader contextClassLoader = child; |
| * |
| * PathableTestSuite suite = new PathableTestSuite(testClass, child); |
| * return suite; |
| * } |
| * |
| * // test methods go here |
| * } |
| * </pre> |
| * Note that if the suite method throws an exception then this will be handled |
| * reasonable gracefully by junit; it will report that the suite method for |
| * a test case failed with exception yyy. |
| * <p> |
| * The use of PathableClassLoader is not required to use this class, but it |
| * is expected that using the two classes together is common practice. |
| * <p> |
| * This class will run each test methods within the specified TestCase using |
| * the specified context classloader and system classloader. If different |
| * tests within the same class require different context classloaders, |
| * then the context classloader passed to the constructor should be the |
| * "lowest" one available, and tests that need the context set to some parent |
| * of this "lowest" classloader can call |
| * <pre> |
| * // NB: pseudo-code only |
| * setContextClassLoader(getContextClassLoader().getParent()); |
| * </pre> |
| * This class ensures that any context classloader changes applied by a test |
| * is undone after the test is run, so tests don't need to worry about |
| * restoring the context classloader on exit. This class also ensures that |
| * the system properties are restored to their original settings after each |
| * test, so tests that manipulate those don't need to worry about resetting them. |
| * <p> |
| * This class does not provide facilities for manipulating system properties; |
| * tests that need specific system properties can simply set them in the |
| * fixture or at the start of a test method. |
| * <p> |
| * <b>Important!</b> When the test case is run, "this.getClass()" refers of |
| * course to the Class object passed to the constructor of this class - which |
| * is different from the class whose suite() method was executed to determine |
| * the classpath. This means that the suite method cannot communicate with |
| * the test cases simply by setting static variables (for example to make the |
| * custom classloaders available to the test methods or setUp/tearDown fixtures). |
| * If this is really necessary then it is possible to use reflection to invoke |
| * static methods on the class object passed to the constructor of this class. |
| * <p> |
| * <h2>Limitations</h2> |
| * <p> |
| * This class cannot control the system classloader (ie what method |
| * ClassLoader.getSystemClassLoader returns) because Java provides no |
| * mechanism for setting the system classloader. In this case, the only |
| * option is to invoke the unit test in a separate JVM with the appropriate |
| * settings. |
| * <p> |
| * The effect of using this approach in a system that uses junit's |
| * "reloading classloader" behavior is unknown. This junit feature is |
| * intended for junit GUI apps where a test may be run multiple times |
| * within the same JVM - and in particular, when the .class file may |
| * be modified between runs of the test. How junit achieves this is |
| * actually rather weird (the whole junit code is rather weird in fact) |
| * and it is not clear whether this approach will work as expected in |
| * such situations. |
| */ |
| public class PathableTestSuite extends TestSuite { |
| |
| /** |
| * The classloader that should be set as the context classloader |
| * before each test in the suite is run. |
| */ |
| private final ClassLoader contextLoader; |
| |
| /** |
| * Constructor. |
| * |
| * @param testClass is the TestCase that is to be run, as loaded by |
| * the appropriate ClassLoader. |
| * |
| * @param contextClassLoader is the loader that should be returned by |
| * calls to Thread.currentThread.getContextClassLoader from test methods |
| * (or any method called by test methods). |
| */ |
| public PathableTestSuite(Class testClass, ClassLoader contextClassLoader) { |
| super(testClass); |
| contextLoader = contextClassLoader; |
| } |
| |
| /** |
| * This method is invoked once for each Test in the current TestSuite. |
| * Note that a Test may itself be a TestSuite object (ie a collection |
| * of tests). |
| * <p> |
| * The context classloader and system properties are saved before each |
| * test, and restored after the test completes to better isolate tests. |
| */ |
| public void runTest(Test test, TestResult result) { |
| ClassLoader origContext = Thread.currentThread().getContextClassLoader(); |
| Properties oldSysProps = (Properties) System.getProperties().clone(); |
| try { |
| Thread.currentThread().setContextClassLoader(contextLoader); |
| test.run(result); |
| } finally { |
| System.setProperties(oldSysProps); |
| Thread.currentThread().setContextClassLoader(origContext); |
| } |
| } |
| } |