/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.test;
import gw.util.StreamUtil;
import junit.framework.Test;
import junit.framework.TestCase;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.LineNumberTable;
import org.apache.bcel.classfile.Method;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Helper methods for analyzing methods, and instantiating test classes.
*/
public class TestClassHelper {
private static final Map<Class<?>, List<Method>> cache = new ConcurrentHashMap<Class<?>, List<Method>>();
/**
* Returns list of methods according to their order in the source file.
* <p/>
* Supertype methods go first in the list.
* <p/>
* Returns empty list if cannot find class file for the specified class. Class file is retrieved by
* using {@link Class#getResourceAsStream} so it won't work for classes generated at runtine.
*
* @param clazz class to analyze
* @return list of method names
*/
@SuppressWarnings("unchecked")
public static <T extends TestCase> List<Method> getMethodsSorted(Class<T> clazz) {
List<Method> allMethods = cache.get(clazz);
if (allMethods != null) {
return allMethods;
}
JavaClass javaClass = parseClass(clazz);
if (javaClass == null) {
return Collections.emptyList();
}
List<Method> currentClassMethods = Arrays.asList(javaClass.getMethods());
Collections.sort(currentClassMethods, new Comparator<Method>() {
@Override
public int compare(Method o1, Method o2) {
return getMethodLineNumber(o1).compareTo(getMethodLineNumber(o2));
}
});
allMethods = new ArrayList<Method>();
// add method of super class first
Class<? super T> superclass = clazz.getSuperclass();
if (superclass != null && !superclass.getClass().equals(TestCase.class)) {
allMethods.addAll(getMethodsSorted((Class<? extends TestCase>) superclass));
}
allMethods.addAll(currentClassMethods);
cache.put(clazz, Collections.unmodifiableList(new ArrayList<Method>(allMethods)));
return allMethods;
}
private static JavaClass parseClass(Class<?> clazz) {
String pathToClassFile = "/" + clazz.getName().replace('.', '/') + ".class";
InputStream resourceAsStream = clazz.getResourceAsStream(pathToClassFile);
if (resourceAsStream == null) {
return null;
}
try {
return new ClassParser(resourceAsStream, pathToClassFile).parse();
} catch (IOException e) {
throw new RuntimeException("Error during analyzing byte code of the class " + clazz.getName(), e);
} finally {
StreamUtil.closeNoThrow(resourceAsStream);
}
}
private static Integer getMethodLineNumber(Method method) {
LineNumberTable lineNumberTable = method.getLineNumberTable();
return lineNumberTable == null ? -1 : lineNumberTable.getLineNumberTable()[0].getLineNumber();
}
public static <T extends TestCase> Test createTestSuite(Class<T> clazz, Iterable<String> methodNames) {
try {
Constructor<T> constructor = getConstructor(clazz);
junit.framework.TestSuite newSuite = new junit.framework.TestSuite();
for (String name : methodNames) {
TestCase test;
if (constructor.getParameterTypes().length == 0) {
test = constructor.newInstance();
test.setName(name);
} else {
test = constructor.newInstance(name);
}
newSuite.addTest(test);
}
return newSuite;
} catch (InstantiationException e) {
throw new RuntimeException("Cannot instantiate test class " + clazz.getName(), e);
} catch (IllegalAccessException e) {
throw new RuntimeException(clazz.getName() + " constructor is not accessible", e);
} catch (InvocationTargetException e) {
throw new RuntimeException(clazz.getName() + "(String name) constructor threw an exception", e);
}
}
private static <T extends TestCase> Constructor<T> getConstructor(Class<T> clazz) {
try {
Constructor<T> constructor = clazz.getConstructor(String.class);
if (Modifier.isPublic(constructor.getModifiers())) {
return constructor;
}
} catch (NoSuchMethodException e) {
}
try {
Constructor<T> constructor = clazz.getConstructor();
if (Modifier.isPublic(constructor.getModifiers())) {
return constructor;
}
} catch (NoSuchMethodException e) {
}
throw new RuntimeException("Did not find public " + clazz.getName() + "(String name) or " + clazz.getName()
+ "() constructor");
}
}