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

gw.test.TestClass Maven / Gradle / Ivy

There is a newer version: 1.18.2
Show newest version
/*
 * Copyright 2014 Guidewire Software, Inc.
 */

package gw.test;

import gw.lang.GosuShop;
import gw.lang.reflect.IAnnotationInfo;
import gw.lang.reflect.IConstructorInfo;
import gw.lang.reflect.IHasJavaClass;
import gw.lang.reflect.IMethodInfo;
import gw.lang.reflect.IType;
import gw.lang.reflect.Modifier;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.java.IJavaType;
import gw.lang.reflect.java.JavaTypes;
import gw.testharness.IncludeInTestResults;
import gw.testharness.KnownBreak;
import gw.testharness.KnownBreakQualifier;
import gw.util.GosuExceptionUtil;
import gw.util.GosuObjectUtil;
import gw.util.GosuStringUtil;
import gw.util.Predicate;
import junit.framework.TestCase;
import junit.framework.TestResult;

import java.lang.annotation.Annotation;
import gw.util.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

public abstract class TestClass extends TestCase implements ITestWithMetadata {
  private String _pkgName;
  private String _className;
  private TestExecutionManager _executionManager;
  private static final ThreadLocal THREAD_LOCAL_EXECUTION_MANAGER = new ThreadLocal();
  private List _metadata = new ArrayList();
  private boolean _doNotRun;
  private boolean _knownBreak;
  private static final Map _numberOfInstancesCreatedByTypeName = new HashMap();
  private boolean _isGosuTest;

  protected TestClass() {
    super();
    initInternalData();
  }

  protected TestClass(String s) {
    super(s);
    initInternalData();
  }

  // This is a bit hacky:  some subclasses might need to delay the call
  // to the initInternalData() method until after the class performed some additional work
  protected TestClass(boolean shouldInit) {
    if (shouldInit) {
      initInternalData();
    }
  }

  // This is a bit hacky:  some subclasses might need to delay the call
  // to the init() method until after the class performed some additional work
  protected TestClass(String s, boolean shouldInit) {
    super(s);
    if (shouldInit) {
      initInternalData();
    }
  }

  public boolean isGosuTest() {
    return _isGosuTest;
  }

  public void setGosuTest(boolean gosuTest) {
    _isGosuTest = gosuTest;
  }

  protected void initInternalData() {
    String fullName = getFullClassNameInternal();
    int lastDot = fullName.lastIndexOf(".");
    _pkgName = fullName.substring(0, lastDot).replace("_proxy_.", "");
    _className = fullName.substring(lastDot + 1, fullName.length()).replace('$', '.');

    // When running from an IDE, we could be running just one method out of a class, or we could be running
    // the entire class.  Since we have no way to get ahold of the suite that the IDE has created, the only
    // way for us to tell how many methods we're running out of a given class is to track how many instances
    // of each class are created.  We can then use that to determine when to run the afterClass() hook.
    Integer numberOfInstances = _numberOfInstancesCreatedByTypeName.get(getTypeName());
    if (numberOfInstances == null) {
      _numberOfInstancesCreatedByTypeName.put(getTypeName(), 1);
    } else {
      _numberOfInstancesCreatedByTypeName.put(getTypeName(), numberOfInstances + 1);
    }
  }

  protected String getFullClassNameInternal() {
    return getClass().getName();
  }

  public static Integer getNumberOfInstancesOfTestClassCreated(String typeName) {
    return _numberOfInstancesCreatedByTypeName.get(typeName);
  }

  @Override
  protected final void setUp() throws Exception {
    super.setUp();
    if(!getType().getName().endsWith("Test")) {
      throw new IllegalStateException("All subclasses of TestClass must have a name that ends with \"Test\"");
    }
    if ( _executionManager == null || _executionManager.assertionsMustBeEnabled()) {
      try {
        assert false;
        throw new IllegalStateException("Assertions must be enabled for tests to be run properly.");
      } catch (AssertionError ae) {
        //ignore
      }
    }
  }

  public void setExecutionManager(TestExecutionManager executionManager) {
    _executionManager = executionManager;
  }

  @Override
  protected final void tearDown() throws Exception {
    super.tearDown();
  }

  public void beforeTestClass(){
  }

  public void beforeTestMethod(){
  }

  public void afterTestMethod(Throwable possibleException){
  }

  public void afterTestClass(){
  }

  @Override
  public void run(TestResult result) {
    getExecutionManager().runTestClass(this, result);
  }

  public void reallyRun(TestResult result) {
    super.run(result);
  }
  
  @Override
  public void runBare() throws Throwable {
    getExecutionManager().runTestClassBare(this);
  }

  public void reallyRunBare() throws Throwable {
    initMetadata(getName());
    super.runBare();
  }

  @Override
  public String toString() {
    return this.getName() + "(" + getTypeName() + ")";
  }

  @Override
  public void setName(String name) {
    super.setName(name);
  }

  @Override
  public String getName() {
    return super.getName();
  }

  protected TestExecutionManager getExecutionManager() {
    if (_executionManager == null) {
      return getThreadLocalExecutionManager();
    } else {
      return _executionManager;
    }
  }

  //Provides a simple thread local execution manager for all the tests being run
  //presumably from an IDE environment
  private TestExecutionManager getThreadLocalExecutionManager() {
    TestExecutionManager executionManager = THREAD_LOCAL_EXECUTION_MANAGER.get();
    if (executionManager == null) {
      executionManager = new TestExecutionManager();
      executionManager.setEnvironment(createDefaultEnvironment());
      THREAD_LOCAL_EXECUTION_MANAGER.set(executionManager);
      // TODO - AHK - Set up the default classpath?
    }
    return executionManager;
  }

  public TestEnvironment createDefaultEnvironment() {
    return new TestEnvironment();
  }

  @Override
  protected final void runTest() throws Throwable {
    // TODO - AHK - More properly log this, rather than using System.out
    if (_knownBreak) {
      System.out.println("**** Test method " + getName() + " is marked as a known break.  Run tests with -Dgw.tests.skip.knownbreak=true to skip known breaks.");
    }

    if (_doNotRun) {
      System.out.println("*** Skipping test method " + getName() + ", as it's marked @Disabled, @ManualTest, or @InProgress");
    } else if (_knownBreak && skipKnownBreakTests()) {
      System.out.println("*** Skipping test method " + getName() + ", as it's marked @KnownBreak and the gw.tests.skip.knownbreak system parameter is set to true.");
    } else {
      doRunTest( getName() );
    }
  }

  private static Boolean _skipKnownBreakTests = null;
  private static boolean skipKnownBreakTests() {
    // And no, I don't really care about thread-safety here
    if (_skipKnownBreakTests == null) {
      String propValue = System.getProperty("gw.tests.skip.knownbreak");
      if (propValue != null) {
        _skipKnownBreakTests = Boolean.valueOf(propValue);
      } else {
        _skipKnownBreakTests = false;
      }
    }

    return _skipKnownBreakTests;
  }

  protected void doRunTest( String name ) throws Throwable
  {
    IType type = getType();
    Method runMethod;

    if( type instanceof IJavaType && ((IHasJavaClass) getType()).getBackingClass() == null ) {
      // Handle case where we are getting IJavaClassInfo from source (getBackingClass() returns null)
      ClassLoader cl = type.getTypeLoader().getModule().getModuleTypeLoader().getDefaultTypeLoader().getGosuClassLoader().getActualLoader();
      Class testClass = Class.forName( type.getName(), true, cl );
      runMethod = testClass.getMethod( name );
    }
    else {
      runMethod = ((IHasJavaClass) getType()).getBackingClass().getMethod( name );
    }
    if (runMethod == null) {
      fail("Method \"" + name + "\" not found");
    }

    if (!Modifier.isPublic(runMethod.getModifiers())) {
      fail("Method \"" + name + "\" should be public");
    }

    try
    {
      runMethod.invoke(this);
    }
    catch( InvocationTargetException e )
    {
      throw GosuExceptionUtil.forceThrow( e.getTargetException() );
    }

//    IMethodInfo runMethod = null;
//    runMethod = getType().getTypeInfo().getMethod( name );
//    if (runMethod == null) {
//      fail("Method \"" + name + "\" not found");
//    }
//    if (!runMethod.isPublic()) {
//      fail("Method \"" + name + "\" should be public");
//    }
//    runMethod.getCallHandler().handleCall(this);
  }

  public IType getType() {
    return TypeSystem.getFromObject(this);
  }

  public String getTypeName() {
    return _pkgName + "." + _className;
  }

  //================================================================
  // Utility Methods
  //================================================================

  public String getClassName() {
    return _className;
  }

  public String getPackageName() {
    return _pkgName;
  }

  //================================================================
  // Assertion extensions
  //================================================================

  public interface EqualityTester {
    boolean equals(Object expected, Object got);
  }

  public static void assertArrayEquals(Object[] expected, Object[] got) {
    assertArrayEquals(expected, got, new EqualityTester() {
      @Override
      public boolean equals(Object expected, Object got) {
        if (expected != null && got != null && expected.getClass().isArray() && got.getClass().isArray() && Array.getLength(expected) == Array.getLength(got)) {
          int length = Array.getLength(expected);
          for (int i = 0; i < length; i++) {
            if (!equals(Array.get(expected, i), Array.get(got, i))) {
              return false;
            }
          }
          return true;
        }
        return GosuObjectUtil.equals(expected, got);
      }
    });
  }

  /**
   * Compare two byte arrays, first the size then each byte.
   * @param expected
   * @param actual
   */
  public static void assertArrayEquals(String message, byte[] expected, byte[] actual) {
    if (expected.length != actual.length) {
      fail(message+" - expected array length of "+expected.length+" but got "+actual.length);
    for (int i=0; i hist = new HashMap();
    if( o1 != null )
    {
      for (Object o : o1) {
        Integer integer = hist.get(o);
        if (integer == null) {
          hist.put(o, 0);
        } else {
          hist.put(o, ++integer);
        }
      }
    }
    return hist;
  }

  private static void assertIterableEquals(Iterable i1, Iterable i2, String s) {
    boolean equals = true;

    if (i1 == i2) return;

    Iterator e1 = i1.iterator();
    Iterator e2 = i2.iterator();
    while (e1.hasNext() && e2.hasNext()) {
      Object o1 = e1.next();
      Object o2 = e2.next();
      if (o1 == null) {
        if (o2 != null) {
          equals = false;
          break;
        }
      } else if (!o1.equals(o2)) {
        equals = false;
        break;
      }
    }
    if (equals) {
      equals = !(e1.hasNext() || e2.hasNext());
    }
    assertTrue( s + " were not equal.  Expected \n[" + GosuStringUtil.join(i1.iterator(), ",") + "] but found \n[" + GosuStringUtil.join(i2.iterator(), ",") + "]",  equals);
  }

  private static void assertIterableEquals(Iterable i1, Iterable i2, Comparator c, String s) {
    boolean equals = true;

    if (i1 == i2) return;

    Iterator e1 = i1.iterator();
    Iterator e2 = i2.iterator();
    while (e1.hasNext() && e2.hasNext()) {
      Object o1 = e1.next();
      Object o2 = e2.next();
      if (o1 == null) {
        if (o2 != null) {
          equals = false;
          break;
        }
      } else if (c.compare(o1, o2) != 0) {
        equals = false;
        break;
      }
    }
    if (equals) {
      equals = !(e1.hasNext() || e2.hasNext());
    }
    assertTrue( s + " were not equal.  Expected \n[" + GosuStringUtil.join(i1.iterator(), ",") + "] but found \n[" + GosuStringUtil.join(i2.iterator(), ",") + "]",  equals);
  }

  private static List makeList(Iterable o1) {
    ArrayList lst = new ArrayList();
    for (Object o : o1) {
      lst.add(o);
    }
    return lst;
  }

  public int getTotalNumTestMethods() {
    List methods = getType().getTypeInfo().getMethods();
    int count = 0;
    for (IMethodInfo method : methods) {
      if (!method.isStatic() && method.getName().startsWith("test") && method.getParameters().length == 0) {
        count++;
      }
    }
    return count;
  }

  @Override
  public List getMetadata() {
    return _metadata;
  }

  protected void addMetadata(Collection metadata) {
    _metadata.addAll(metadata);
    for (TestMetadata testMetadata : metadata) {
      if (testMetadata.shouldNotRunTest()) {
        _doNotRun = true;
      } else if (testMetadata.getName().equals(KnownBreak.class.getName())) {
        _knownBreak = true;
      }
    }
  }

  public Collection createMethodMetadata( String method )
  {
    if (getType() instanceof IJavaType) {
      // For Java types, we have to do things based on Method instead of MethodInfo, since Java TypeInfo isn't currently
      // reloadable, but the DCEVM means that Java classes themselves are
      try {
        Method testMethod = ((IJavaType) getType()).getBackingClass().getMethod(method);
        return createMetadata(testMethod.getAnnotations());
      } catch (NoSuchMethodException e) {
        throw new IllegalStateException( "Method not found: " + getType().getDisplayName() + "." + method);
      }
    } else {
      IMethodInfo testMethod = getType().getTypeInfo().getMethod(method);
      if(testMethod == null) {
        throw new IllegalStateException( "Method not found: " + getType().getDisplayName() + "." + method);
      }

      return createMetadata(testMethod.getAnnotations());
    }
  }


  public Collection createClassMetadata()
  {
    if (getType() instanceof IJavaType) {
      return createMetadata(((IJavaType) getType()).getBackingClass().getAnnotations());
    } else {
      return createMetadata(getType().getTypeInfo().getAnnotations());
    }
  }

  protected Collection createMetadata(List annotationInfos) {
    Map map = new HashMap();
    for (IAnnotationInfo ai : annotationInfos) {
      if (isMetaAnnotationInfo(ai)) {
        map.put(ai.getType(), new TestMetadata(ai));
      }
    }
    if (map.containsKey(TypeSystem.get( KnownBreak.class ))) {
      for (IAnnotationInfo ai : annotationInfos) {
        if (isKnownBreakQualifier(ai)) {
          Predicate qualifierPredicate;
          try {
            Object t = ai.getType().getTypeInfo().getAnnotation( TypeSystem.get( KnownBreakQualifier.class ) ).getFieldValue( "value" );
            IType type = t instanceof Class ? TypeSystem.get( (Class)t ) : (IType)t;
            qualifierPredicate = (Predicate)type.getTypeInfo().getConstructor().getConstructor().newInstance();
          } catch (RuntimeException e) {
            throw e;
          } catch (Exception e) {
            throw new RuntimeException(e);
          }
          if (!qualifierPredicate.evaluate(ai)) {
            map.remove(TypeSystem.get( KnownBreak.class ));
            break;
          }
        }
      }
    }
    return map.values();
  }

  private boolean isKnownBreakQualifier(IAnnotationInfo ai) {
    boolean isQualifier = false;
    for (IAnnotationInfo a : ai.getType().getTypeInfo().getAnnotations()) {
      if (a.getName().equals(KnownBreakQualifier.class.getName())) {
        isQualifier = true;
        break;
      }
    }
    return isQualifier;
  }

  protected boolean isMetaAnnotationInfo(IAnnotationInfo ai) {
    boolean isMetadata = false;
    for (IAnnotationInfo a : ai.getType().getTypeInfo().getAnnotations()) {
      if (a.getName().equals(IncludeInTestResults.class.getName())) {
        isMetadata = true;
        break;
      }
    }
    return isMetadata;
  }

  protected Collection createMetadata(Annotation[] annotations) {
    Map, TestMetadata> map = new HashMap, TestMetadata>();
    for (Annotation a : annotations) {
      if (isMetaAnnotation(a)) {
        map.put(a.annotationType(), new TestMetadata( GosuShop.getAnnotationInfoFactory().createJavaAnnotation( a, getType().getTypeInfo() ) ) );
      }
    }
    if (map.containsKey(KnownBreak.class)) {
      for (Annotation a : annotations) {
        if (isKnownBreakQualifier(a)) {
          Predicate qualifierPredicate;
          try {
            qualifierPredicate = a.annotationType().getAnnotation(KnownBreakQualifier.class).value().newInstance();
          } catch (RuntimeException e) {
            throw e;
          } catch (Exception e) {
            throw new RuntimeException(e);
          }
          if (!qualifierPredicate.evaluate( GosuShop.getAnnotationInfoFactory().createJavaAnnotation( a, getType().getTypeInfo() ) ) ) {
            map.remove(KnownBreak.class);
            break;
          }
        }
      }
    }
    return map.values();
  }

  private boolean isKnownBreakQualifier(Annotation a) {
    return a.annotationType().getAnnotation(KnownBreakQualifier.class) != null;
  }

  protected boolean isMetaAnnotation(Annotation ai) {
    boolean isMetadata = false;
    for (Annotation a : ai.annotationType().getAnnotations()) {
      if (a instanceof IncludeInTestResults) {
        isMetadata = true;
        break;
      }
    }
    return isMetadata;
  }

  public void initMetadata( String method )
  {
    addMetadata( createClassMetadata() );
    addMetadata( createMethodMetadata( method ) );
  }

  public static void assertCausesException( Runnable r, Class c )
  {
    try {
      r.run();
    } catch( Throwable t )
    {
      if( c.isAssignableFrom( t.getClass() ) )
      {
        return;
      }
      else
      {
        fail( "Expecting exception of type " + c + ", but got exception of type " + t.getClass() );
      }
    }
    fail( "No exception was thrown when executing " + r + ".  Expected exception of type " + c );
  }

  public static TestClass createTestClass(IType testType) {
    IConstructorInfo noArgConstructor = testType.getTypeInfo().getConstructor();
    if (noArgConstructor != null) {
      return (TestClass) noArgConstructor.getConstructor().newInstance();
    } else {
      IConstructorInfo oneArgConstructor = testType.getTypeInfo().getConstructor(JavaTypes.STRING());
      if (oneArgConstructor != null) {
        return (TestClass) oneArgConstructor.getConstructor().newInstance("temp");
      } else {
        throw new IllegalArgumentException("Type " + testType.getName() + " does not have either a no-arg constructor or a one-arg constructor that takes a String");
      }
    }
  }

  public static junit.framework.Test _suite(Class clazz) {
    return TestClassHelper.createTestSuite(clazz, TestSpec.extractTestMethods(clazz));
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy