/* * 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; } } }