blob: 32e2c1296389cc42485763c5b709ef8c15baf0f0 [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.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
*/
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();
}
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;
}
}