All Downloads are FREE. Search and download functionalities are using the official Maven repository.

xapi.test.junit.JUnit4Runner Maven / Gradle / Ivy

Go to download

Everything needed to run a comprehensive dev environment. Just type X_ and pick a service from autocomplete; new dev modules will be added as they are built. The only dev service not included in the uber jar is xapi-dev-maven, as it includes all runtime dependencies of maven, adding ~4 seconds to build time, and 6 megabytes to the final output jar size (without xapi-dev-maven, it's ~1MB).

The newest version!
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 after = newMap();
    Map afterClass = newMap();
    Map before = newMap();
    Map beforeClass = newMap();
    Map 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 after() {
      return newList(after, true);
    }

    public List afterClass() {
      return newList(afterClass, true);
    }

    public List before() {
      return newList(before, true);
    }

    public List 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 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 inst, Map tests, List beforeClass,
      List before, List after, List afterClass) 
      throws TestsFailed, IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodError {
    Map result = new LinkedHashMap();
    try {

      for (Method m : beforeClass)
        m.invoke(null);
      
      for (Entry test : tests.entrySet()) {
        result.put(test.getValue(), runTest(inst.get(), test.getValue(), before, after));
      }
      
    } finally {
      
      for (Method m : afterClass)
        m.invoke(null);
      
    }
    for (Entry e : result.entrySet()) {
      if (e.getValue() != null) {
        TestsFailed failure = new TestsFailed(result);
        debug("Tests Failed;\n", failure);
        throw new AssertionError(failure.toString());
      }
    }
  }

  protected List newList(Map beforeClass, boolean reverse) {
    List list;
    if (reverse) {
      list = new LinkedList();
      for (Entry e : beforeClass.entrySet()) {
        list.add(0, e.getValue());
      }
    } else {
      list = new ArrayList();
      for (Entry e : beforeClass.entrySet()) {
        list.add(e.getValue());
      }
    }
    return list;
  }

  protected Map newMap() {
    return new LinkedHashMap();
  }
  protected void run(Provider 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 provider = new Provider() {
      @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 before, List after) {
    
    Test test = null;
    try {
      test = value.getAnnotation(Test.class);
    } catch (Exception e) {
      debug("Error getting @Test annotation",e);
    }
    Class 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;
        }
      }
    }
  }
  
}