package xapi.test.junit;
import static xapi.test.Assert.assertEquals;
import static xapi.test.Assert.assertFalse;
import static xapi.test.Assert.assertTrue;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.inject.Provider;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import xapi.annotation.compile.Resource;
import xapi.gwtc.api.Gwtc;
import xapi.util.X_Debug;
import com.google.gwt.core.shared.GWT;
@Gwtc(
includeSource="",
includeGwtXml=@Resource("xapi.X_Test")
)
public class JUnit4Runner {
public static void main(String [] args) {
}
private class Lifecycle {
Map<String, Method> after = newMap();
Map<String, Method> afterClass = newMap();
Map<String, Method> before = newMap();
Map<String, Method> beforeClass = newMap();
Map<String, Method> tests = newMap();
@SuppressWarnings("rawtypes")
public Lifecycle(Class testClass) {
Class init = testClass;
while (init != null && init != Object.class) {
try {
for (Method method : init.getDeclaredMethods()) {
if (method.getAnnotation(Test.class) != null) {
assertPublicZeroArgInstanceMethod(method, Test.class);
if (!tests.containsKey(method.getName()))
tests.put(method.getName(), method);
}
maybeAdd(method);
}
} catch (NoSuchMethodError ignored) {
debug("Class "+init+" is not enhanced", null);
} catch (Exception e) {
debug("Error getting declared methods for "+init, e);
}
init = init.getSuperclass();
}
}
public List<Method> after() {
return newList(after, true);
}
public List<Method> afterClass() {
return newList(afterClass, true);
}
public List<Method> before() {
return newList(before, true);
}
public List<Method> beforeClass() {
return newList(beforeClass, true);
}
public void maybeAdd(Method method) {
if (method.getAnnotation(Before.class) != null) {
assertPublicZeroArgInstanceMethod(method, Before.class);
if (!before.containsKey(method.getName()))
before.put(method.getName(), method);
}
if (method.getAnnotation(BeforeClass.class) != null) {
assertPublicZeroArgStaticMethod(method, BeforeClass.class);
if (!beforeClass.containsKey(method.getName()))
beforeClass.put(method.getName(), method);
}
if (method.getAnnotation(After.class) != null) {
assertPublicZeroArgInstanceMethod(method, After.class);
if (!after.containsKey(method.getName()))
after.put(method.getName(), method);
}
if (method.getAnnotation(AfterClass.class) != null) {
assertPublicZeroArgStaticMethod(method, AfterClass.class);
if (!afterClass.containsKey(method.getName()))
afterClass.put(method.getName(), method);
}
}
}
public static Method[] findTests(Class<?> testClass) throws Throwable {
return new JUnit4Runner().findAnnotated(testClass);
}
public static void runTest(Provider<Object> inst, Method m) throws Throwable {
new JUnit4Runner().run(inst, m);
}
public static void runTests(Class<?> testClass) throws Throwable {
new JUnit4Runner().runAll(testClass);
}
private Method[] findAnnotated(Class<?> testClass) {
Lifecycle lifecycle = new Lifecycle(testClass);
return lifecycle.tests.values().toArray(new Method[0]);
}
protected void assertPublicZeroArgInstanceMethod(Method method, Class<?> type) {
assertPublicZeroArgMethod(method, type);
assertFalse("@" + type.getSimpleName() + " methods must not be static",
Modifier.isStatic(method.getModifiers()));
}
protected void assertPublicZeroArgMethod(Method method, Class<?> type) {
assertTrue("@" + type.getSimpleName() + " methods must be public",
Modifier.isPublic(method.getModifiers()));
assertEquals("@" + type.getSimpleName() + " methods must be zero-arg",
0, method.getParameterTypes().length);
}
protected void assertPublicZeroArgStaticMethod(Method method, Class<?> type) {
assertPublicZeroArgMethod(method, type);
assertTrue("@" + type.getSimpleName() + " methods must be static",
Modifier.isStatic(method.getModifiers()));
}
protected void debug(String string, Throwable e) {
if (GWT.isProdMode()) {
GWT.log(string+" ("+e+")");
}
else
System.out.println(string);
while (e != null) {
e.printStackTrace(System.err);
e = e.getCause();
}
}
protected void execute(Provider<Object> inst, Map<String, Method> tests, List<Method> beforeClass,
List<Method> before, List<Method> after, List<Method> afterClass)
throws TestsFailed, IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodError {
Map<Method, Throwable> result = new LinkedHashMap<Method, Throwable>();
try {
for (Method m : beforeClass)
m.invoke(null);
for (Entry<String, Method> test : tests.entrySet()) {
result.put(test.getValue(), runTest(inst.get(), test.getValue(), before, after));
}
} finally {
for (Method m : afterClass)
m.invoke(null);
}
for (Entry<Method, Throwable> e : result.entrySet()) {
if (e.getValue() != null) {
TestsFailed failure = new TestsFailed(result);
debug("Tests Failed;\n", failure);
throw new AssertionError(failure.toString());
}
}
}
protected List<Method> newList(Map<String, Method> beforeClass, boolean reverse) {
List<Method> list;
if (reverse) {
list = new LinkedList<Method>();
for (Entry<String, Method> e : beforeClass.entrySet()) {
list.add(0, e.getValue());
}
} else {
list = new ArrayList<Method>();
for (Entry<String, Method> e : beforeClass.entrySet()) {
list.add(e.getValue());
}
}
return list;
}
protected Map<String, Method> newMap() {
return new LinkedHashMap<String, Method>();
}
protected void run(Provider<Object> inst, Method m) throws Throwable {
Lifecycle lifecycle = new Lifecycle(m.getDeclaringClass());
lifecycle.tests.clear();
lifecycle.tests.put(m.getName(), m);
execute(inst, lifecycle.tests,
lifecycle.beforeClass(), lifecycle.before(), lifecycle.after(), lifecycle.afterClass());
}
protected void runAll(final Class<?> testClass) throws Throwable {
Lifecycle lifecycle = new Lifecycle(testClass);
Provider<Object> provider = new Provider<Object>() {
@Override
public Object get() {
try {
return testClass.newInstance();
} catch (Exception e) {
throw X_Debug.rethrow(e);
}
}
};
if (lifecycle.tests.size() > 0) {
execute(provider, lifecycle.tests, lifecycle.beforeClass(), lifecycle.before(), lifecycle.after(), lifecycle.afterClass());
}
}
protected Throwable runTest(Object inst, Method value, List<Method> before, List<Method> after) {
Test test = null;
try {
test = value.getAnnotation(Test.class);
} catch (Exception e) {
debug("Error getting @Test annotation",e);
}
Class<? extends Throwable> expected = test == null ? Test.None.class : test.expected();
// We'll have to figure out timeouts in the actual JUnit jvm
try {
debug("Executing "+value+" on "+inst, null);
for (Method m : before) {
m.invoke(inst);
}
try {
value.invoke(inst);
} catch (InvocationTargetException e) {
throw e.getCause();
}
if (expected != Test.None.class)
return new AssertionError("Method "+value+" was supposed to throw "+expected.getName()
+", but failed to do so");
return null;
} catch (Throwable e) {
return expected.isAssignableFrom(e.getClass()) ? null : e;
} finally {
for (Method m : after) {
try {
m.invoke(inst);
} catch (Throwable e) {
debug("Error calling after methods", e);
return e;
}
}
}
}
}