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

gw.test.Suite Maven / Gradle / Ivy

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

package gw.test;

import gw.config.CommonServices;
import gw.fs.IDirectory;
import gw.fs.IFile;
import gw.lang.init.ClasspathToGosuPathEntryUtil;
import gw.lang.reflect.IMethodInfo;
import gw.lang.reflect.IType;
import gw.lang.reflect.TypeSystem;
import gw.util.ILogger;
import gw.util.Predicate;
import junit.framework.Test;
import junit.framework.TestResult;
import junit.framework.TestSuite;
import org.junit.internal.TextListener;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;

/**
 * This Suite class streamlines and simplifies the standard JUnit TestSuite.  It can be used in one of two ways:
 * you can add individual tests to the Suite via the withTest() methods, or you can use the filter methods to
 * pare down the tests that are found on the current classpath.
 */
public class Suite extends junit.framework.TestSuite {

  //suite status
  private boolean _testClassWrappersCreated;
  private boolean _suiteHasBeenSetUp;

  //filters
  private final List> _typeFilters = new ArrayList>();
  private final List> _iFileFilters = new ArrayList>();
  private final List> _packageFilters = new ArrayList>();
  private final List _withPackages = new ArrayList();

  //suite test data
  private final TreeSet _testSpecs = new TreeSet();
  private final List _gosuClassSearchPath = new ArrayList();
  private final List _javaClassSearchPath = new ArrayList();
  private final List _modules = new ArrayList();
  private boolean _logErrorInfo = false;

  private int _splitNum = 1;
  private int _splitPartition = 0;

  private TestExecutionManager _executionManager;
  private TestEnvironment _testEnvironment;
  public static final String GOSU_SUITE_INCLUDE_TYPES = "gs.suite.tests";
  public static final String SPLIT_PARTITION = "split.partition";

  public Suite() {
    System.setProperty("checkedArithmetic", "true");
    Integer splitPartition = readIntegerSystemProperty(SPLIT_PARTITION);
    if(splitPartition != null && splitPartition > 1){
      throw new UnsupportedOperationException("V3 tests doesn't support more than one split");
    }

    // By default, use a standard TestEnvironment to set things up, in case the specific suite class doesn't
    // bother to set one up
    _testEnvironment = new TestEnvironment();
    _executionManager = new TestExecutionManager();
    _executionManager.setEnvironment(_testEnvironment);
  }

  public TestEnvironment getTestEnvironment() {
    return _testEnvironment;
  }

  //=============================================================================================
  // Suite builder support
  //=============================================================================================

  /**
   * Used to indicate that this suite does not require assertions be enabled.  In general
   * it is recommended that you not use this option.
   */
  public final T javaAssertionsNotEnabled() {
    _executionManager.setAssertionsMustBeEnabled(false);
    return thisAsT();
  }

  /**
   * Adds a test to this Suite.  This method is not to be combined with the module/filter methods.
   */
  public final T withTest(IType type, String... methods) {
    verifyNoFilters();
    _testSpecs.add(new TestSpec(type.getName(), methods));
    return thisAsT();
  }

  /**
   * Adds a test to this Suite.  This method is not to be combined with the module/filter methods.
   */
  public final T withTest(String type, String... methods) {
    verifyNoFilters();
    _testSpecs.add(new TestSpec(type, methods));
    return thisAsT();
  }

  /**
   * Adds a test to this Suite.  This method is not to be combined with the module/filter methods.
   */
  public final T withTest(Class type, String... methods) {
    verifyNoFilters();
    _testSpecs.add(new TestSpec(type.getName(), methods));
    return thisAsT();
  }

  private void verifyNoFilters() {
    if (!_packageFilters.isEmpty() || !_iFileFilters.isEmpty() ||
            !_typeFilters.isEmpty() || !_withPackages.isEmpty()) {
      throw new IllegalStateException("You cannot combine filters with explicitly specified tests");
    }
  }

  /**
   * Adds a type filter to this suite, allowing certain tests to be excluded.  Note
   * that this is a significantly slower option than {@link #withIFileFilter(gw.util.Predicate)}
   * or {@link #withPackageFilter(gw.util.Predicate)} If
   * possible, it is advisable to use a file filter instead.
   */
  public final T withTestTypeFilter(Predicate filter) {
    if (!_testSpecs.isEmpty()) {
      throw new IllegalStateException("You should add a test filter before any tests are added to the suite.");
    }
    _typeFilters.add(filter);
    return thisAsT();
  }

  /**
   * Adds a file filter to this suite, allowing certain tests to be excluded.  Consider
   * using {@link #withPackageFilter(gw.util.Predicate)} if you wish to more easily apply a package
   * filter of tests without dealing with ugly file paths.  Package filters are just as fast as
   * file filters, both of which are much faster than test type filters.
   */
  public final T withIFileFilter(Predicate fileFilter) {
    ensureNoExplictlyAddedTests();
    _iFileFilters.add(fileFilter);
    return thisAsT();
  }

  /**
   * Adds a package filter that will only accept packages that are subpackages of the strings
   * passed in.
   */
  public final T withPackages(final String... packagePrefixes) {
    ensureNoExplictlyAddedTests();
    _withPackages.addAll(Arrays.asList(packagePrefixes));
    return thisAsT();
  }

  private void ensureNoExplictlyAddedTests() {
    if (!_testSpecs.isEmpty()) {
      throw new IllegalStateException("You cannot add filters to suites with explicitly added tests");
    }
  }

  /**
   * Adds a package filter to this suite, which allows you to exclude certain packages from consideration.
   * These filters are about as fast as file filters, but are easier to write and maintain.  For the
   * common case of simply wishing to test certain packages and their sub-packages, use {@link #withPackages(String...)} instead.
   */
  public final T withPackageFilter(Predicate fileFilter) {
    ensureNoExplictlyAddedTests();
    _packageFilters.add(fileFilter);
    return thisAsT();
  }

  public final T withTimeout(long seconds) {
    _executionManager.setSuiteTimeoutInMillis(seconds * 1000L);
    return thisAsT();
  }

  public T withTestEnvironment(TestEnvironment testEnvironment) {
    if (_testEnvironment.isRemoteExecutionEnvironment()) {
      throw new IllegalStateException("For now, withTestEnvironment must be called BEFORE withRemoteServer is called");
    }
    _testEnvironment = testEnvironment;
    _executionManager.setEnvironment(_testEnvironment);
    return thisAsT();
  }

  public final T logErrors() {
    _logErrorInfo = true;
    return thisAsT();
  }

  /**
   * This limits the gosu tests that are run to gosu tests that are located
   * within the specified module in a gsrc or gtest directory AND all and java
   * tests in the classpath that are not in a module.
   * @param moduleNames
   * @return
   */
  public final T withModules(String... moduleNames) {
    _modules.addAll(Arrays.asList(moduleNames));
    return thisAsT();
  }

  // TODO - AHK - Differentiate between Gosu and Java at this level?
  // TODO - AHK - Change this to just take an IDirectory instead?
  public T withClasspathEntry(File srcDir) {
    _gosuClassSearchPath.add(CommonServices.getFileSystem().getIDirectory(srcDir));
    _javaClassSearchPath.add(CommonServices.getFileSystem().getIDirectory(srcDir));
    return thisAsT();
  }

  //=============================================================================================
  // Implementation details
  //=============================================================================================
  @Override
  public final int countTestCases() {
    maybeCreateTestClassWrappers();
    return _testSpecs.size();
  }

  @Override
  public final Test testAt(int index) {
    maybeCreateTestClassWrappers();
    return super.testAt(index);
  }

  @Override
  public final int testCount() {
    maybeCreateTestClassWrappers();
    return super.testCount();
  }

  @Override
  public final Enumeration tests() {
    maybeCreateTestClassWrappers();
    return super.tests();
  }

  private void maybeCreateTestClassWrappers() {

    maybeInitSuite();

    if (!_testClassWrappersCreated) {
      long start = System.currentTimeMillis();

      if (_testSpecs.isEmpty()) {
        TestClassFinder finder = new TestClassFinder(_iFileFilters, _packageFilters, _withPackages, _typeFilters);
        _testSpecs.addAll(finder.findTests(_gosuClassSearchPath, _javaClassSearchPath));
      }

      restrictTestSpecsToSpecifiedNames();
      
      for (TestSpec spec : _testSpecs) {
        if (_testEnvironment.isRemoteExecutionEnvironment()) {
          String[] methods = spec.runAllMethods() ? null : spec.getMethods();
          Test remoteWrapper = ((IForwardingTestEnvironment) _testEnvironment).makeRemoteTestClassWrapper(_executionManager, spec.getTestTypeName(), methods);
          addTest(remoteWrapper);
        } else {
          IType type = spec.getTestType();
          addTest(new TestClassWrapper(_executionManager, type, spec.getMethods()));
        }
      }
      _testClassWrappersCreated = true;
      long end = System.currentTimeMillis();
      System.out.println("Test wrappers created in " + (end - start) + "ms");

      if (_testEnvironment.isDynamicallyDeterminedEnvironment()) {
        _testEnvironment = determineTestEnvironmentBasedOnTestDefaults();
        System.out.println("Dynamically determined the test environment to be " + _testEnvironment.getClass());
        // This is pretty hacky, but RemoteTestEnvironments need a chance to tell the remote server
        // what sort of environment to set up, and that actually needs to be done prior to beginning the suite
        if (_testEnvironment instanceof IForwardingTestEnvironment) {
          _testEnvironment.initializeTypeSystem();
        }
        _executionManager.setEnvironment(_testEnvironment);
      }
    }
  }

  private TestEnvironment determineTestEnvironmentBasedOnTestDefaults() {
    TestEnvironment defaultEnv = null;
    TestClass defaultEnvTest = null;

    for (Test t : testsAsList()) {
      if (t instanceof TestClassWrapper) {
        if (((TestClassWrapper) t).testCount() > 0) {
          Test firstTest = ((TestClassWrapper) t).tests().nextElement();
          if (firstTest instanceof TestClass) {
            TestEnvironment env = ((TestClass) firstTest).createDefaultEnvironment();
            if (defaultEnv == null) {
              defaultEnv = env;
              defaultEnvTest = (TestClass) firstTest;
            } else {
              if (!defaultEnv.getClass().equals(env.getClass())) {
                throw new IllegalStateException("The test " + defaultEnvTest.getClassName() + " requires the " +
                        defaultEnv.getClass() + " environment, while the " + ((TestClass) firstTest).getClassName() +
                        " test requires the " + env.getClass() + " environment.  Automatic resolution of the appropriate " +
                        "test environment will only work if all tests in the suite use the exact same default test environment.");
              }
            }
          } else {
            throw new IllegalStateException("Found a test that was a " + firstTest.getClass() + " instead of a TestClass");
          }
        } else {
          throw new IllegalStateException("Found a test class " + ((TestClassWrapper) t).getBackingType().getName() + " that has no tests.");
        }
      } else {
        throw new IllegalStateException("Found a test that was a " + t.getClass() + " instead of a TestClassWrapper");
      }
    }

    return defaultEnv;
  }

  private void maybeInitSuite() {
    if (!_suiteHasBeenSetUp) {

      initTypeSystem();
      if (_gosuClassSearchPath.isEmpty() && _javaClassSearchPath.isEmpty() && _testSpecs.isEmpty()) {
        _gosuClassSearchPath.addAll(createDefaultGosuClassSearchPath());
        _javaClassSearchPath.addAll(createDefaultJavaClassSearchPath());
      }

      _suiteHasBeenSetUp = true;
    }
  }

  protected List createDefaultGosuClassSearchPath() {
    List gosuClassSearchPath = new ArrayList();
    List sourceEntries = TypeSystem.getCurrentModule().getSourcePath();
    for (IDirectory dir : sourceEntries) {
      if (_modules.isEmpty()) {
        gosuClassSearchPath.add(dir);
      } else {
        // TODO - AHK - This seems rather unnecessary:  we should have a way to already know what the module root is
        IDirectory moduleRoot = ClasspathToGosuPathEntryUtil.findModuleRootFromSourceEntry(dir);
        if (_modules.contains(moduleRoot.getName())) {
          gosuClassSearchPath.add(dir);
        }
      }
    }

    return gosuClassSearchPath;
  }

  protected List createDefaultJavaClassSearchPath() {
    List javaClassSearchPath = new ArrayList<>();
    List classPathParts = ClassPathUtil.constructClasspathFromSystemClasspath();

    for (IDirectory dir : classPathParts) {
      javaClassSearchPath.add(dir);
    }

    return javaClassSearchPath;
  }

  private void initTypeSystem() {
    _executionManager.maybeInitTypeSystem();
  }

  public void logError(String o) {
    if( _logErrorInfo) {
      ILogger logger = CommonServices.getEntityAccess().getLogger();
      logger.warn(o);
    }
  }

  @Override
  public void run(TestResult result) {
    maybeCreateTestClassWrappers();
    _executionManager.setTestsFromSuite(testsAsListOfSuites());
    beforeSuite();
    super.run(result);
  }

  protected void beforeSuite() {
    // hook point for subclasses
  }

  /**
   * A convenience method for running this suite from a main method.  Subclasses of
   * Suite can create a main method like so:
   * 
   *   public static void main(String[] args) {
   *     System.exit( new GosuSuite().runSuite() ? 0 : 1 );
   *   }
   * 
* * @return a boolean saying if all tests passed */ public final boolean runViaStaticSuiteMethod() { IType iType = TypeSystem.getTypeFromObject(this); IMethodInfo method = iType.getTypeInfo().getMethod("suite"); Test test = (Test) method.getCallHandler().handleCall(null); return runImpl(test).wasSuccessful(); } private Result runImpl(Test test) { JUnitCore runner = new JUnitCore(); TextListener txtListener = new TextListener(System.out); runner.addListener(txtListener); return runner.run(test); } /** * Runs this Suite with a TextListener tied to System.out and returns the result. */ public final Result run() { return runImpl(this); } public List testsAsList() { Enumeration testEnumeration = tests(); ArrayList testArrayList = new ArrayList(); while (testEnumeration.hasMoreElements()) { Test test = testEnumeration.nextElement(); testArrayList.add(test); } return testArrayList; } public List testsAsListOfSuites() { Enumeration testEnumeration = tests(); List testArrayList = new ArrayList(); while (testEnumeration.hasMoreElements()) { Test test = testEnumeration.nextElement(); testArrayList.add((TestSuite) test); } return testArrayList; } public boolean isLoggingErrors() { return _logErrorInfo; } /** * Private method that casts the this value as a T to avoid unchecked cast errors all over the place * * @return this */ @SuppressWarnings({"unchecked"}) private T thisAsT() { return (T) this; } // TODO - AHK - Maybe kill everything around partitioning and splitting in Suite, or at least move it to PLSuite private Integer readIntegerSystemProperty(String name) { String splitString = System.getProperty(name); if (splitString != null) { try { return Integer.valueOf(splitString); } catch (NumberFormatException e) { System.err.println("Invalid System Property value for '" + name + "': \"" + splitString + "\". " + e.getMessage()); } } return null; } private void restrictTestSpecsToSpecifiedPartition() { int splitStart = 0; int splitEnd = _testSpecs.size(); if(_splitNum > 1) { float splitSize = (float)_testSpecs.size() / (float)_splitNum; splitStart = Math.round(splitSize * _splitPartition); if(_splitPartition != _splitNum - 1) { splitEnd = Math.round(splitSize * (_splitPartition + 1)) - 1; } } // PL-13675: To play nice with TH System.out.println("Number of classes = " + _testSpecs.size()); System.out.println("Number of splits = " + (_splitNum <= 0 ? 1: _splitNum)); int i = 0; for (Iterator it = _testSpecs.iterator(); it.hasNext();) { TestSpec spec = it.next(); if (i < splitStart || i >= splitEnd) { it.remove(); } i++; } } // TODO - AHK - This should be killed and re-implemented as a test type filter in PLSuite /** * This is for troubleshooting suite failures by running a subset of tests within a suite * without requiring any Java or Gosu code changes */ private void restrictTestSpecsToSpecifiedNames() { String includedTests = System.getProperty(GOSU_SUITE_INCLUDE_TYPES); if(includedTests != null){ System.out.println("System property " + GOSU_SUITE_INCLUDE_TYPES + " used, so only running tests specified:"); System.out.println(includedTests); String[] includedTestsArray = includedTests.replace(" ", "").split(","); HashSet includedTestSet = new HashSet(Arrays.asList(includedTestsArray)); for (Iterator it = _testSpecs.iterator(); it.hasNext();) { TestSpec spec = it.next(); if(!includedTestSet.contains(spec.getTestType().getName())){ it.remove(); } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy