    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
    18  package org.apache.commons.logging;
    19
    20  import java.util.Properties;
    21
    22  import junit.framework.Test;
    23  import junit.framework.TestResult;
    24  import junit.framework.TestSuite;
    25
    26  /**
    27   * Custom TestSuite class that can be used to control the context classloader
    28   * in operation when a test runs.
    29   * <p>
    30   * For tests that need to control exactly what the classloader hierarchy is
    31   * like when the test is run, something like the following is recommended:
    32   * <pre>
    33   * class SomeTestCase extends TestCase {
    34   *  public static Test suite() throws Exception {
    35   *   PathableClassLoader parent = new PathableClassLoader(null);
    36   *   parent.useSystemLoader("junit.");
    37   *
    38   *   PathableClassLoader child = new PathableClassLoader(parent);
    39   *   child.addLogicalLib("testclasses");
    40   *   child.addLogicalLib("log4j12");
    41   *   child.addLogicalLib("commons-logging");
    42   *
    43   *   Class testClass = child.loadClass(SomeTestCase.class.getName());
    44   *   ClassLoader contextClassLoader = child;
    45   *
    46   *   PathableTestSuite suite = new PathableTestSuite(testClass, child);
    47   *   return suite;
    48   *  }
    49   *
    50   *  // test methods go here
    51   * }
    52   * </pre>
    53   * Note that if the suite method throws an exception then this will be handled
    54   * reasonable gracefully by junit; it will report that the suite method for
    55   * a test case failed with exception yyy.
    56   * <p>
    57   * The use of PathableClassLoader is not required to use this class, but it
    58   * is expected that using the two classes together is common practice.
    59   * <p>
    60   * This class will run each test methods within the specified TestCase using
    61   * the specified context classloader and system classloader. If different
    62   * tests within the same class require different context classloaders,
    63   * then the context classloader passed to the constructor should be the
    64   * "lowest" one available, and tests that need the context set to some parent
    65   * of this "lowest" classloader can call
    66   * <pre>
    67   *  // NB: pseudo-code only
    68   *  setContextClassLoader(getContextClassLoader().getParent());
    69   * </pre>
    70   * This class ensures that any context classloader changes applied by a test
    71   * is undone after the test is run, so tests don't need to worry about
    72   * restoring the context classloader on exit. This class also ensures that
    73   * the system properties are restored to their original settings after each
    74   * test, so tests that manipulate those don't need to worry about resetting them.
    75   * <p>
    76   * This class does not provide facilities for manipulating system properties;
    77   * tests that need specific system properties can simply set them in the
    78   * fixture or at the start of a test method.
    79   * <p>
    80   * <b>Important!</b> When the test case is run, "this.getClass()" refers of
    81   * course to the Class object passed to the constructor of this class - which
    82   * is different from the class whose suite() method was executed to determine
    83   * the classpath. This means that the suite method cannot communicate with
    84   * the test cases simply by setting static variables (for example to make the
    85   * custom classloaders available to the test methods or setUp/tearDown fixtures).
    86   * If this is really necessary then it is possible to use reflection to invoke
    87   * static methods on the class object passed to the constructor of this class.
    88   * <p>
    89   * <h2>Limitations</h2>
    90   * <p>
    91   * This class cannot control the system classloader (ie what method
    92   * ClassLoader.getSystemClassLoader returns) because Java provides no
    93   * mechanism for setting the system classloader. In this case, the only
    94   * option is to invoke the unit test in a separate JVM with the appropriate
    95   * settings.
    96   * <p>
    97   * The effect of using this approach in a system that uses junit's
    98   * "reloading classloader" behaviour is unknown. This junit feature is
    99   * 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 }