001: /*
002: * Copyright 2005 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.commons.logging;
018:
019: import java.util.Properties;
020:
021: import junit.framework.Test;
022: import junit.framework.TestResult;
023: import junit.framework.TestSuite;
024:
025: /**
026: * Custom TestSuite class that can be used to control the context classloader
027: * in operation when a test runs.
028: * <p>
029: * For tests that need to control exactly what the classloader hierarchy is
030: * like when the test is run, something like the following is recommended:
031: * <pre>
032: * class SomeTestCase extends TestCase {
033: * public static Test suite() throws Exception {
034: * PathableClassLoader parent = new PathableClassLoader(null);
035: * parent.useSystemLoader("junit.");
036: *
037: * PathableClassLoader child = new PathableClassLoader(parent);
038: * child.addLogicalLib("testclasses");
039: * child.addLogicalLib("log4j12");
040: * child.addLogicalLib("commons-logging");
041: *
042: * Class testClass = child.loadClass(SomeTestCase.class.getName());
043: * ClassLoader contextClassLoader = child;
044: *
045: * PathableTestSuite suite = new PathableTestSuite(testClass, child);
046: * return suite;
047: * }
048: *
049: * // test methods go here
050: * }
051: * </pre>
052: * Note that if the suite method throws an exception then this will be handled
053: * reasonable gracefully by junit; it will report that the suite method for
054: * a test case failed with exception yyy.
055: * <p>
056: * The use of PathableClassLoader is not required to use this class, but it
057: * is expected that using the two classes together is common practice.
058: * <p>
059: * This class will run each test methods within the specified TestCase using
060: * the specified context classloader and system classloader. If different
061: * tests within the same class require different context classloaders,
062: * then the context classloader passed to the constructor should be the
063: * "lowest" one available, and tests that need the context set to some parent
064: * of this "lowest" classloader can call
065: * <pre>
066: * // NB: pseudo-code only
067: * setContextClassLoader(getContextClassLoader().getParent());
068: * </pre>
069: * This class ensures that any context classloader changes applied by a test
070: * is undone after the test is run, so tests don't need to worry about
071: * restoring the context classloader on exit. This class also ensures that
072: * the system properties are restored to their original settings after each
073: * test, so tests that manipulate those don't need to worry about resetting them.
074: * <p>
075: * This class does not provide facilities for manipulating system properties;
076: * tests that need specific system properties can simply set them in the
077: * fixture or at the start of a test method.
078: * <p>
079: * <b>Important!</b> When the test case is run, "this.getClass()" refers of
080: * course to the Class object passed to the constructor of this class - which
081: * is different from the class whose suite() method was executed to determine
082: * the classpath. This means that the suite method cannot communicate with
083: * the test cases simply by setting static variables (for example to make the
084: * custom classloaders available to the test methods or setUp/tearDown fixtures).
085: * If this is really necessary then it is possible to use reflection to invoke
086: * static methods on the class object passed to the constructor of this class.
087: * <p>
088: * <h2>Limitations</h2>
089: * <p>
090: * This class cannot control the system classloader (ie what method
091: * ClassLoader.getSystemClassLoader returns) because Java provides no
092: * mechanism for setting the system classloader. In this case, the only
093: * option is to invoke the unit test in a separate JVM with the appropriate
094: * settings.
095: * <p>
096: * The effect of using this approach in a system that uses junit's
097: * "reloading classloader" behaviour is unknown. This junit feature is
098: * intended for junit GUI apps where a test may be run multiple times
099: * within the same JVM - and in particular, when the .class file may
100: * be modified between runs of the test. How junit achieves this is
101: * actually rather weird (the whole junit code is rather weird in fact)
102: * and it is not clear whether this approach will work as expected in
103: * such situations.
104: */
105: public class PathableTestSuite extends TestSuite {
106:
107: /**
108: * The classloader that should be set as the context classloader
109: * before each test in the suite is run.
110: */
111: private ClassLoader contextLoader;
112:
113: /**
114: * Constructor.
115: *
116: * @param testClass is the TestCase that is to be run, as loaded by
117: * the appropriate ClassLoader.
118: *
119: * @param contextClassLoader is the loader that should be returned by
120: * calls to Thread.currentThread.getContextClassLoader from test methods
121: * (or any method called by test methods).
122: */
123: public PathableTestSuite(Class testClass,
124: 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()
139: .getContextClassLoader();
140: Properties oldSysProps = (Properties) System.getProperties()
141: .clone();
142: try {
143: Thread.currentThread().setContextClassLoader(contextLoader);
144: test.run(result);
145: } finally {
146: System.setProperties(oldSysProps);
147: Thread.currentThread().setContextClassLoader(origContext);
148: }
149: }
150: }
|