blob: 3a716761c78d9b8a844f98f176f7f3ba1c27de17 [file] [log] [blame]
/*
* 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);
}
}
}