/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2009-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.mail.test;
import java.lang.annotation.*;
import java.net.URL;
import java.net.MalformedURLException;
import java.net.URLClassLoader;
import java.util.List;
import java.util.ArrayList;
import org.junit.runners.Suite;
import org.junit.runners.ParentRunner;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.RunnerBuilder;
import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;
/**
* A special test suite that loads each of the test classes
* in a separate class loader, along with the class under test.
* This allows the tests to test methods whose behavior depends on
* the value of a System property that's read at class initialization
* time; each test can set a different value of the System property
* and the corresponding class under test will be loaded in a
* separate class loader. <p>
*
* To use this class, create a test suite class:
*
* <pre>
* @RunWith(ClassLoaderSuite.class)
* @SuiteClasses({ MyTest1.class, MyTest2.class })
* @TestClass(ClassToTest.class)
* public class MyTestSuite {
* }
* </pre>
*
* The MyTest1 and MyTest2 classes are written as normal JUnit
* test classes. Set the System property to test in the @BeforeClass
* method of these classes.
*
* @author Bill Shannon
*/
public class ClassLoaderSuite extends Suite {
/**
* An annotation to be used on the test suite class to indicate
* the class under test. The class is used to find the classpath
* to allow loading the class under test in a separate class loader.
* Note that other classes in the same classpath will also be loaded
* in the separate class loader, along with the test classes.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface TestClass {
public Class<?> value();
}
/**
* A special class loader that loads classes from its own class path
* (specified via URLs) before delegating to the parent class loader.
* This is used to load the test classes in separate class loaders,
* even though those classes are also loaded in the parent class loader.
*/
static class TestClassLoader extends URLClassLoader {
public TestClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
public Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
Class<?> c = null;
try {
c = findLoadedClass(name);
if (c != null)
return c;
c = findClass(name);
if (resolve)
resolveClass(c);
} catch (ClassNotFoundException cex) {
c = super.loadClass(name, resolve);
}
return c;
}
}
/**
* Constructor.
*/
public ClassLoaderSuite(Class<?> klass, RunnerBuilder builder)
throws InitializationError {
super(builder, klass,
reloadClasses(getTestClass(klass), getSuiteClasses(klass)));
}
/**
* Set the thread's context class loader to the class loader
* for the test class.
*/
@Override
protected void runChild(Runner runner, RunNotifier notifier) {
// XXX - is it safe to assume it's always a ParentRunner?
ParentRunner pr = (ParentRunner)runner;
ClassLoader cl = null;
try {
cl = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(
pr.getTestClass().getJavaClass().getClassLoader());
super.runChild(runner, notifier);
} finally {
Thread.currentThread().setContextClassLoader(cl);
}
}
/**
* Get the value of the SuiteClasses annotation.
*/
private static Class<?>[] getSuiteClasses(Class<?> klass)
throws InitializationError {
SuiteClasses annotation = klass.getAnnotation(SuiteClasses.class);
if (annotation == null)
throw new InitializationError("class '" + klass.getName() +
"' must have a SuiteClasses annotation");
return annotation.value();
}
/**
* Get the value of the TestClass annotation.
*/
private static Class<?> getTestClass(Class<?> klass)
throws InitializationError {
TestClass annotation = klass.getAnnotation(TestClass.class);
if (annotation == null)
throw new InitializationError("class '" + klass.getName() +
"' must have a TestClass annotation");
return annotation.value();
}
/**
* Reload the classes in a separate class loader.
*/
private static Class<?>[] reloadClasses(Class<?> testClass,
Class<?>[] suiteClasses) throws InitializationError {
URL[] urls = new URL[] {
classpathOf(testClass),
classpathOf(ClassLoaderSuite.class)
};
Class<?> sc = null;
try {
for (int i = 0; i < suiteClasses.length; i++) {
sc = suiteClasses[i];
ClassLoader cl = new TestClassLoader(urls,
ClassLoaderSuite.class.getClassLoader());
suiteClasses[i] = cl.loadClass(sc.getName());
}
return suiteClasses;
} catch (ClassNotFoundException cex) {
throw new InitializationError("could not reload class: " + sc);
}
}
/**
* Return the classpath entry used to load the named resource.
* XXX - Only handles file: and jar: URLs.
*/
private static URL classpathOf(Class<?> c) {
String name = "/" + c.getName().replace('.', '/') + ".class";
try {
URL url = ClassLoaderSuite.class.getResource(name);
if (url.getProtocol().equals("file")) {
String file = url.getPath();
if (file.endsWith(name)) // has to be true?
file = file.substring(0, file.length() - name.length() + 1);
//System.out.println("file URL " + url + " has CLASSPATH " + file);
return new URL("file", null, file);
} else if (url.getProtocol().equals("jar")) {
String file = url.getPath();
int i = file.lastIndexOf('!');
if (i >= 0)
file = file.substring(0, i);
//System.out.println("jar URL " + url + " has CLASSPATH " + file);
return new URL(file);
} else
return url;
} catch (MalformedURLException mex) {
return null;
}
}
}