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    }