/* | |
* 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.extensions.validator.test.base.util; | |
import java.io.File; | |
import java.io.IOException; | |
import java.io.PrintWriter; | |
import java.io.StringWriter; | |
import java.lang.reflect.Constructor; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
import java.lang.reflect.Modifier; | |
import java.net.MalformedURLException; | |
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 junit.framework.Test; | |
import junit.framework.TestCase; | |
import junit.framework.TestSuite; | |
import junit.runner.TestCaseClassLoader; | |
/** | |
* Allows the execution of each test of a TestCase into a different classloader. | |
* Specify the class of the test case in the constructor and return the instance | |
* of the ClassLoaderTestSuite as the result of the suite method. | |
* | |
* Most of the code is borrowed from junit.framework.TestSuite. | |
* | |
* @author Rudy De Busscher | |
* | |
*/ | |
@SuppressWarnings("unchecked") | |
public class ClassLoaderTestSuite extends TestSuite | |
{ | |
private static final Logger LOGGER = Logger.getLogger(ClassLoaderTestSuite.class.getName()); | |
// The classpath is needed because the custom class loader looks their to find the classes. | |
private static String classPath; | |
private static boolean classPathDetermined = false; | |
public ClassLoaderTestSuite(Class theClass) | |
{ | |
identifyTestMethods(theClass); | |
} | |
/** | |
* Identify test methods. | |
* | |
* @param theClass | |
* the class | |
*/ | |
private void identifyTestMethods(Class theClass) | |
{ | |
Class superClass = theClass; | |
Vector names = new Vector(); | |
while (Test.class.isAssignableFrom(superClass)) | |
{ | |
Method[] methods = superClass.getDeclaredMethods(); | |
for (int i = 0; i < methods.length; i++) | |
{ | |
addTestMethod(methods[i], names, theClass); | |
} | |
superClass = superClass.getSuperclass(); | |
} | |
} | |
/** | |
* Adds the method as a test method when it fulfill all requirements. | |
* | |
* @param method | |
* the method that is maybe a test method | |
* @param names | |
* the names with all the discovered test methods | |
* @param theClass | |
* the class | |
*/ | |
private void addTestMethod(Method method, Vector names, Class theClass) | |
{ | |
String name = method.getName(); | |
if (names.contains(name)) | |
{ | |
return; | |
} | |
if (!isPublicTestMethod(method)) | |
{ | |
if (isTestMethod(method)) | |
{ | |
addTest(warning("Test method isn't public: " + method.getName())); | |
} | |
return; | |
} | |
names.addElement(name); | |
addTest(createTestWithCustomClassLoader(theClass, name)); | |
} | |
/** | |
* The method creates a Test using a custom classloader for each test. | |
* | |
* @param theOriginalClass | |
* The class | |
* @param name | |
* The test method name | |
* @return The test where class is now loaded from a custom class loader. | |
*/ | |
static public Test createTestWithCustomClassLoader(Class theOriginalClass, String name) | |
{ | |
ClassLoader classLoader; | |
String surefireTestPath = getClassPath(); | |
if (surefireTestPath != null) | |
{ | |
classLoader = new TestCaseClassLoader(surefireTestPath); | |
} | |
else | |
{ | |
classLoader = new TestCaseClassLoader(); | |
} | |
Class theClass = null; | |
try | |
{ | |
// Use the custom classloader to load the test. The complete | |
// execution of the test will then be done using this new | |
// classloader. | |
theClass = classLoader.loadClass(theOriginalClass.getName()); | |
} catch (ClassNotFoundException e) | |
{ | |
return warning("Cannot custom load test case: " + name + " (" + exceptionToString(e) + ")"); | |
} | |
Constructor constructor; | |
try | |
{ | |
constructor = getTestConstructor(theClass); | |
} catch (NoSuchMethodException e) | |
{ | |
return warning("Class " + theClass.getName() | |
+ " has no public constructor TestCase(String name) or TestCase()"); | |
} | |
Object test; | |
try | |
{ | |
if (constructor.getParameterTypes().length == 0) | |
{ | |
test = constructor.newInstance(new Object[0]); | |
if (test instanceof TestCase) | |
((TestCase) test).setName(name); | |
} else | |
{ | |
test = constructor.newInstance(new Object[] { name }); | |
} | |
} catch (InstantiationException e) | |
{ | |
return (warning("Cannot instantiate test case: " + name + " (" + exceptionToString(e) + ")")); | |
} catch (InvocationTargetException e) | |
{ | |
return (warning("Exception in constructor: " + name + " (" + exceptionToString(e.getTargetException()) | |
+ ")")); | |
} catch (IllegalAccessException e) | |
{ | |
return (warning("Cannot access test case: " + name + " (" + exceptionToString(e) + ")")); | |
} | |
return (Test) test; | |
} | |
/** | |
* 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 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; | |
} | |
private static boolean isJar(String pathEntry) | |
{ | |
return pathEntry.endsWith(".jar") || pathEntry.endsWith(".zip"); | |
} | |
private static Vector scanPath(String classPath) | |
{ | |
String separator = System.getProperty("path.separator"); | |
Vector pathItems = new Vector(10); | |
StringTokenizer st = new StringTokenizer(classPath, separator); | |
while (st.hasMoreTokens()) | |
{ | |
pathItems.addElement(st.nextToken()); | |
} | |
return pathItems; | |
} | |
/** | |
* Checks if is public test method. | |
* | |
* @param method | |
* the method | |
* | |
* @return true, if is public test method | |
*/ | |
private boolean isPublicTestMethod(Method method) | |
{ | |
return isTestMethod(method) && Modifier.isPublic(method.getModifiers()); | |
} | |
/** | |
* Checks if is test method. | |
* | |
* @param method | |
* the method | |
* | |
* @return true, if is test method | |
*/ | |
private boolean isTestMethod(Method method) | |
{ | |
String name = method.getName(); | |
Class[] parameters = method.getParameterTypes(); | |
Class returnType = method.getReturnType(); | |
return parameters.length == 0 && name.startsWith("test") && returnType.equals(Void.TYPE); | |
} | |
/** | |
* Creates a Test that generates a failure with the supplied message. | |
* | |
* @param message | |
* the message | |
* | |
* @return the test | |
*/ | |
private static Test warning(final String message) | |
{ | |
return new TestCase("warning") | |
{ | |
protected void runTest() | |
{ | |
fail(message); | |
} | |
}; | |
} | |
/** | |
* Exception to string. | |
* | |
* @param t | |
* the t | |
* | |
* @return the string | |
*/ | |
private static String exceptionToString(Throwable t) | |
{ | |
StringWriter stringWriter = new StringWriter(); | |
PrintWriter writer = new PrintWriter(stringWriter); | |
t.printStackTrace(writer); | |
return stringWriter.toString(); | |
} | |
} |