/* | |
* 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.myfaces.test.runners; | |
import java.io.File; | |
import java.io.IOException; | |
import java.lang.annotation.Annotation; | |
import java.lang.reflect.Method; | |
import java.net.MalformedURLException; | |
import java.util.List; | |
import java.util.StringTokenizer; | |
import java.util.Vector; | |
import java.util.jar.Attributes; | |
import java.util.jar.Manifest; | |
import java.util.logging.Logger; | |
import java.util.zip.ZipEntry; | |
import java.util.zip.ZipFile; | |
import org.junit.After; | |
import org.junit.Before; | |
import org.junit.internal.runners.statements.Fail; | |
import org.junit.internal.runners.statements.RunAfters; | |
import org.junit.internal.runners.statements.RunBefores; | |
import org.junit.runners.model.FrameworkMethod; | |
import org.junit.runners.model.InitializationError; | |
import org.junit.runners.model.Statement; | |
import org.junit.runners.model.TestClass; | |
/** | |
* A Junit 4 runner that executes each Test method with a new Custom classloader so | |
* that all variables, | |
* also the final ones, are reinitialized. | |
* | |
* @author Rudy De Busscher | |
* @since 1.0.0 | |
*/ | |
public class TestPerClassLoaderRunner extends NamedRunner | |
{ | |
private static final Logger LOGGER = Logger | |
.getLogger(TestPerClassLoaderRunner.class.getName()); | |
// The classpath is needed because the custom class loader looks there to find the classes. | |
private static String classPath; | |
private static boolean classPathDetermined = false; | |
// Some data related to the class from the custom class loader. | |
private TestClass testClassFromClassLoader; | |
private Object beforeFromClassLoader; | |
private Object afterFromClassLoader; | |
/** | |
* Instantiates a new test per class loader runner. | |
* | |
* @param klass the klass | |
* | |
* @throws InitializationError the initialization error | |
*/ | |
public TestPerClassLoaderRunner(Class<?> klass) throws InitializationError | |
{ | |
super(klass); | |
} | |
@Override | |
protected Object createTest() throws Exception | |
{ | |
// Need an instance now from the class loaded by the custom loader. | |
return testClassFromClassLoader.getJavaClass().newInstance(); | |
} | |
/** | |
* Load classes (TestCase, @Before and @After with custom class loader. | |
* | |
* @throws ClassNotFoundException the class not found exception | |
*/ | |
private void loadClassesWithCustomClassLoader() | |
throws ClassNotFoundException | |
{ | |
ClassLoader classLoader; | |
// We need the classpath so that our custom loader can search for the requested classes. | |
String testPath = getClassPath(); | |
if (testPath != null) | |
{ | |
classLoader = new TestClassLoader(testPath); | |
} | |
else | |
{ | |
classLoader = new TestClassLoader(); | |
} | |
Thread.currentThread().setContextClassLoader(classLoader); | |
testClassFromClassLoader = new TestClass(classLoader | |
.loadClass(getTestClass().getJavaClass().getName())); | |
// See withAfters and withBefores for the reason. | |
beforeFromClassLoader = classLoader.loadClass(Before.class.getName()); | |
afterFromClassLoader = classLoader.loadClass(After.class.getName()); | |
} | |
@Override | |
protected Statement methodBlock(FrameworkMethod method) | |
{ | |
FrameworkMethod newMethod = null; | |
try | |
{ | |
// Need the class frmo the custom loader now, so lets load the class. | |
loadClassesWithCustomClassLoader(); | |
// The method as parameter is frmo the original class and thus not found in our | |
// class loaded by the custom name (reflection is class loader sensitive) | |
// So find the same method but now in the class frmo the class Loader. | |
Method methodFromNewlyLoadedClass = testClassFromClassLoader | |
.getJavaClass().getMethod(method.getName()); | |
newMethod = new FrameworkMethod(methodFromNewlyLoadedClass); | |
} | |
catch (ClassNotFoundException e) | |
{ | |
// Show any problem nicely as a JUnit Test failure. | |
return new Fail(e); | |
} | |
catch (SecurityException e) | |
{ | |
return new Fail(e); | |
} | |
catch (NoSuchMethodException e) | |
{ | |
return new Fail(e); | |
} | |
// We can carry out the normal JUnit functionality with our newly discoverd method now. | |
return super.methodBlock(newMethod); | |
} | |
@SuppressWarnings("unchecked") | |
@Override | |
protected Statement withAfters(FrameworkMethod method, Object target, | |
Statement statement) | |
{ | |
// We now to need to search in the class from the custom loader. | |
//We also need to search with the annotation loaded by the custom class loader or otherwise | |
// we don't find any method. | |
List<FrameworkMethod> afters = testClassFromClassLoader | |
.getAnnotatedMethods((Class<? extends Annotation>) afterFromClassLoader); | |
return new RunAfters(statement, afters, target); | |
} | |
@SuppressWarnings("unchecked") | |
@Override | |
protected Statement withBefores(FrameworkMethod method, Object target, | |
Statement statement) | |
{ | |
// We now to need to search in the class from the custom loader. | |
//We also need to search with the annotation loaded by the custom class loader or otherwise | |
// we don't find any method. | |
List<FrameworkMethod> befores = testClassFromClassLoader | |
.getAnnotatedMethods((Class<? extends Annotation>) beforeFromClassLoader); | |
return new RunBefores(statement, befores, target); | |
} | |
/** | |
* Gets the class path. This value is cached in a static variable for performance reasons. | |
* | |
* @return the class path | |
*/ | |
private static String getClassPath() | |
{ | |
if (classPathDetermined) | |
{ | |
return classPath; | |
} | |
classPathDetermined = true; | |
// running from maven, we have the classpath in this property. | |
classPath = System.getProperty("surefire.test.class.path"); | |
if (classPath != null) | |
{ | |
return classPath; | |
} | |
// For a multi module project, running it from the top we have to find it using another way. | |
// We also need to set useSystemClassLoader=true in the POM so that we gets a jar with the classpath in it. | |
String booterClassPath = System.getProperty("java.class.path"); | |
Vector<String> pathItems = null; | |
if (booterClassPath != null) | |
{ | |
pathItems = scanPath(booterClassPath); | |
} | |
// Do we have just 1 entry as classpath which is a jar? | |
if (pathItems != null && pathItems.size() == 1 | |
&& isJar((String) pathItems.get(0))) | |
{ | |
classPath = loadJarManifestClassPath((String) pathItems.get(0), | |
"META-INF/MANIFEST.MF"); | |
} | |
return classPath; | |
} | |
/** | |
* Load jar manifest class path. | |
* | |
* @param path the path | |
* @param fileName the file name | |
* | |
* @return the string | |
*/ | |
private static String loadJarManifestClassPath(String path, String fileName) | |
{ | |
File archive = new File(path); | |
if (!archive.exists()) | |
{ | |
return null; | |
} | |
ZipFile zipFile = null; | |
try | |
{ | |
zipFile = new ZipFile(archive); | |
} | |
catch (IOException io) | |
{ | |
return null; | |
} | |
ZipEntry entry = zipFile.getEntry(fileName); | |
if (entry == null) | |
{ | |
return null; | |
} | |
try | |
{ | |
Manifest mf = new Manifest(); | |
mf.read(zipFile.getInputStream(entry)); | |
return mf.getMainAttributes().getValue(Attributes.Name.CLASS_PATH) | |
.replaceAll(" ", System.getProperty("path.separator")) | |
.replaceAll("file:/", ""); | |
} | |
catch (MalformedURLException e) | |
{ | |
LOGGER.throwing("ClassLoaderTestSuite", "loadJarManifestClassPath", | |
e); | |
} | |
catch (IOException e) | |
{ | |
LOGGER.throwing("ClassLoaderTestSuite", "loadJarManifestClassPath", | |
e); | |
} | |
return null; | |
} | |
/** | |
* Checks if is jar. | |
* | |
* @param pathEntry the path entry | |
* | |
* @return true, if is jar | |
*/ | |
private static boolean isJar(String pathEntry) | |
{ | |
return pathEntry.endsWith(".jar") || pathEntry.endsWith(".zip"); | |
} | |
/** | |
* Scan path for all directories. | |
* | |
* @param classPath the class path | |
* | |
* @return the vector< string> | |
*/ | |
private static Vector<String> scanPath(String classPath) | |
{ | |
String separator = System.getProperty("path.separator"); | |
Vector<String> pathItems = new Vector<String>(10); | |
StringTokenizer st = new StringTokenizer(classPath, separator); | |
while (st.hasMoreTokens()) | |
{ | |
pathItems.addElement(st.nextToken()); | |
} | |
return pathItems; | |
} | |
} |