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

org.testng.internal.MethodHelper Maven / Gradle / Ivy

There is a newer version: 7.10.1
Show newest version
package org.testng.internal;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

import org.testng.IInvokedMethod;
import org.testng.IMethodInstance;
import org.testng.ITestClass;
import org.testng.ITestNGMethod;
import org.testng.TestNGException;
import org.testng.annotations.IConfigurationAnnotation;
import org.testng.annotations.ITestAnnotation;
import org.testng.annotations.ITestOrConfiguration;
import org.testng.collections.Lists;
import org.testng.collections.Sets;
import org.testng.internal.Graph.Node;
import org.testng.internal.annotations.AnnotationHelper;
import org.testng.internal.annotations.IAnnotationFinder;
import org.testng.internal.collections.Pair;

/**
 * Collection of helper methods to help sort and arrange methods.
 *
 * @author Cedric Beust
 * @author Alexandru Popescu
 */
public class MethodHelper {
  private static final Map> GRAPH_CACHE =
          new ConcurrentHashMap<>();
  private static final Map CANONICAL_NAME_CACHE = new ConcurrentHashMap<>();
  private static final Map, Boolean> MATCH_CACHE =
          new ConcurrentHashMap<>();

  /**
   * Collects and orders test or configuration methods
   * @param methods methods to be worked on
   * @param forTests true for test methods, false for configuration methods
   * @param runInfo
   * @param finder annotation finder
   * @param unique true for unique methods, false otherwise
   * @param outExcludedMethods
   * @return list of ordered methods
   */
  public static ITestNGMethod[] collectAndOrderMethods(List methods,
      boolean forTests, RunInfo runInfo, IAnnotationFinder finder,
      boolean unique, List outExcludedMethods,
      Comparator comparator) {
    List includedMethods = Lists.newArrayList();
    MethodGroupsHelper.collectMethodsByGroup(methods.toArray(new ITestNGMethod[methods.size()]),
        forTests,
        includedMethods,
        outExcludedMethods,
        runInfo,
        finder,
        unique);

    return sortMethods(forTests, includedMethods, comparator).toArray(new ITestNGMethod[]{});
  }

  /**
   * Finds TestNG methods that the specified TestNG method depends upon
   * @param m TestNG method
   * @param methods list of methods to search for depended upon methods
   * @return list of methods that match the criteria
   */
  protected static ITestNGMethod[] findDependedUponMethods(ITestNGMethod m, List methods) {
    ITestNGMethod[] methodsArray = methods.toArray(new ITestNGMethod[methods.size()]);
    return findDependedUponMethods(m, methodsArray);
  }
  
  /**
   * Finds TestNG methods that the specified TestNG method depends upon
   * @param m TestNG method
   * @param methods list of methods to search for depended upon methods
   * @return list of methods that match the criteria
   */
  protected static ITestNGMethod[] findDependedUponMethods(ITestNGMethod m, ITestNGMethod[] methods) {

    String canonicalMethodName = calculateMethodCanonicalName(m);

    List vResult = Lists.newArrayList();
    String regexp = null;
    for (String fullyQualifiedRegexp : m.getMethodsDependedUpon()) {
      boolean foundAtLeastAMethod = false;

      if (null != fullyQualifiedRegexp) {
        // Escapes $ in regexps as it is not meant for end - line matching, but inner class matches.
        regexp = fullyQualifiedRegexp.replace("$", "\\$");
        MatchResults results = matchMethod(methods, regexp);
        foundAtLeastAMethod = results.foundAtLeastAMethod;
        vResult.addAll(results.matchedMethods);
        if (!foundAtLeastAMethod) {
          //Replace the declaring class name in the dependsOnMethods value with
          //the fully qualified test class name and retry the method matching.
          int lastIndex = regexp.lastIndexOf('.');
          String newMethodName;
          if (lastIndex != -1) {
            newMethodName = m.getTestClass().getRealClass().getName()  + regexp.substring(lastIndex);
            results =  matchMethod(methods, newMethodName);
            foundAtLeastAMethod = results.foundAtLeastAMethod;
            vResult.addAll(results.matchedMethods);
          }
        }
      }

      if (!foundAtLeastAMethod) {
        if (m.ignoreMissingDependencies()) {
          continue;
        }
        if (m.isAlwaysRun()) {
          continue;
        }
        Method maybeReferringTo = findMethodByName(m, regexp);
        if (maybeReferringTo != null) {
          throw new TestNGException(canonicalMethodName + "() is depending on method "
              + maybeReferringTo + ", which is not annotated with @Test or not included.");
        }
        throw new TestNGException(canonicalMethodName
            + "() depends on nonexistent method " + regexp);
      }
    }//end for

    return vResult.toArray(new ITestNGMethod[vResult.size()]);
  }

  /**
   * Finds method based on regex and TestNGMethod. If regex doesn't represent the
   * class name, uses the TestNG method's class name.
   * @param testngMethod TestNG method
   * @param regExp regex representing a method and/or related class name
   */
  private static Method findMethodByName(ITestNGMethod testngMethod, String regExp) {
    if (regExp == null) {
      return null;
    }
    int lastDot = regExp.lastIndexOf('.');
    String className, methodName;
    if (lastDot == -1) {
      className = testngMethod.getConstructorOrMethod().getDeclaringClass().getCanonicalName();
      methodName = regExp;
    } else {
      methodName = regExp.substring(lastDot + 1);
      className = regExp.substring(0, lastDot);
    }

    try {
      Class c = Class.forName(className);
      for (Method m : c.getDeclaredMethods()) {
        if (methodName.equals(m.getName())) {
          return m;
        }
      }
    }
    catch (Exception e) {
      //only logging
      Utils.log("MethodHelper", 3, "Caught exception while searching for methods using regex");
    }
    return null;
  }

  protected static boolean isEnabled(Class objectClass, IAnnotationFinder finder) {
    ITestAnnotation testClassAnnotation = AnnotationHelper.findTest(finder, objectClass);
    return isEnabled(testClassAnnotation);
  }

  protected static boolean isEnabled(Method m, IAnnotationFinder finder) {
    ITestAnnotation annotation = AnnotationHelper.findTest(finder, m);

    // If no method annotation, look for one on the class
    if (null == annotation) {
      annotation = AnnotationHelper.findTest(finder, m.getDeclaringClass());
    }

    return isEnabled(annotation);
  }

  protected static boolean isEnabled(ITestOrConfiguration test) {
    return null == test || test.getEnabled();
  }

  static boolean isDisabled(ITestOrConfiguration test) {
    return !isEnabled(test);
  }

  static boolean isAlwaysRun(IConfigurationAnnotation configurationAnnotation) {
    if(null == configurationAnnotation) {
      return false;
    }

    boolean alwaysRun= false;
    if ((configurationAnnotation.getAfterSuite()
            || configurationAnnotation.getAfterTest()
            || configurationAnnotation.getAfterTestClass()
            || configurationAnnotation.getAfterTestMethod()
            || configurationAnnotation.getBeforeTestMethod()
            || configurationAnnotation.getBeforeTestClass()
            || configurationAnnotation.getBeforeTest()
            || configurationAnnotation.getBeforeSuite())
            && configurationAnnotation.getAlwaysRun()) {
      alwaysRun = true;
    }

    return alwaysRun;
  }

  /**
   * Extracts the unique list of ITestNGMethods.
   */
  public static List uniqueMethodList(Collection> methods) {
    Set resultSet = Sets.newHashSet();

    for (List l : methods) {
      resultSet.addAll(l);
    }

    return Lists.newArrayList(resultSet);
  }

  private static Graph topologicalSort(ITestNGMethod[] methods,
      List sequentialList, List parallelList,
      final Comparator comparator) {
    Graph result = new Graph<>(new Comparator>() {
      @Override
      public int compare(Node o1, Node o2) {
        return comparator.compare(o1.getObject(), o2.getObject());
      }
    });

    if (methods.length == 0) {
      return result;
    }

    //
    // Create the graph
    //

    Map> testInstances = sortMethodsByInstance(methods);

    for (ITestNGMethod m : methods) {
      result.addNode(m);

      List predecessors = Lists.newArrayList();

      String[] methodsDependedUpon = m.getMethodsDependedUpon();
      String[] groupsDependedUpon = m.getGroupsDependedUpon();
      if (methodsDependedUpon.length > 0) {
        ITestNGMethod[] methodsNamed = null;
        // Method has instance
        if (m.getInstance() != null) {
          // Get other methods with the same instance
          List instanceMethods = testInstances.get(m.getInstance());
          try {
            // Search for other methods that depends upon with the same instance
            methodsNamed = MethodHelper.findDependedUponMethods(m, instanceMethods);
          } catch (TestNGException e) {
            //Maybe this method has a dependency on a method that resides in a different instance.
            //Lets try searching for all methods now
            methodsNamed = MethodHelper.findDependedUponMethods(m, methods);
          }
        } else {
          // Search all methods
          methodsNamed = MethodHelper.findDependedUponMethods(m, methods);
        }
        for (ITestNGMethod pred : methodsNamed) {
          predecessors.add(pred);
        }
      }
      if (groupsDependedUpon.length > 0) {
        for (String group : groupsDependedUpon) {
          ITestNGMethod[] methodsThatBelongToGroup =
            MethodGroupsHelper.findMethodsThatBelongToGroup(m, methods, group);
          for (ITestNGMethod pred : methodsThatBelongToGroup) {
            predecessors.add(pred);
          }
        }
      }

      for (ITestNGMethod predecessor : predecessors) {
        result.addPredecessor(m, predecessor);
      }
    }

    result.topologicalSort();
    sequentialList.addAll(result.getStrictlySortedNodes());
    parallelList.addAll(result.getIndependentNodes());

    return result;
  }

  /**
   * This method is used to create a map of test instances and their associated
   * method(s) . Used to decrease the scope to only a methods instance when trying
   * to find method dependencies.
   * 
   * @param methods
   *          Methods to be sorted
   * @return Map of Instances as the keys and the methods associated with the
   *         instance as the values
   */
  private static Map> sortMethodsByInstance(ITestNGMethod[] methods) {
    LinkedHashMap> result = new LinkedHashMap<>();
    for (ITestNGMethod method : methods) {
      // Get method instance
      Object methodInstance = method.getInstance();
      if (methodInstance == null) {
        continue;
      }
      //Look for method instance in list and update associated methods
      List methodList = result.get(methodInstance);
      if (methodList == null) {
        methodList = new ArrayList<>();
      }
      methodList.add(method);
      result.put(methodInstance, methodList);
    }
    return result;
  }
  
  protected static String calculateMethodCanonicalName(ITestNGMethod m) {
    return calculateMethodCanonicalName(m.getConstructorOrMethod().getMethod());
  }

  private static String calculateMethodCanonicalName(Method m) {
    String result = CANONICAL_NAME_CACHE.get(m);
    if (result != null) {
      return result;
    }

    String packageName = m.getDeclaringClass().getName() + "." + m.getName();

    // Try to find the method on this class or parents
    Class cls = m.getDeclaringClass();
    while (cls != Object.class) {
      try {
        if (cls.getDeclaredMethod(m.getName(), m.getParameterTypes()) != null) {
          packageName = cls.getName();
          break;
        }
      }
      catch (Exception e) {
        // ignore
      }
      cls = cls.getSuperclass();
    }

    result = packageName + "." + m.getName();
    CANONICAL_NAME_CACHE.put(m, result);
    return result;
  }

  private static List sortMethods(boolean forTests,
      List allMethods, Comparator comparator) {
    List sl = Lists.newArrayList();
    List pl = Lists.newArrayList();
    ITestNGMethod[] allMethodsArray = allMethods.toArray(new ITestNGMethod[allMethods.size()]);

    // Fix the method inheritance if these are @Configuration methods to make
    // sure base classes are invoked before child classes if 'before' and the
    // other way around if they are 'after'
    if (!forTests && allMethodsArray.length > 0) {
      ITestNGMethod m = allMethodsArray[0];
      boolean before = m.isBeforeClassConfiguration()
          || m.isBeforeMethodConfiguration() || m.isBeforeSuiteConfiguration()
          || m.isBeforeTestConfiguration();
      MethodInheritance.fixMethodInheritance(allMethodsArray, before);
    }

    topologicalSort(allMethodsArray, sl, pl, comparator);

    List result = Lists.newArrayList();
    result.addAll(sl);
    result.addAll(pl);
    return result;
  }

  /**
   * @return A sorted array containing all the methods 'method' depends on
   */
  public static List getMethodsDependedUpon(ITestNGMethod method,
      ITestNGMethod[] methods, Comparator comparator) {
    Graph g = GRAPH_CACHE.get(methods);
    if (g == null) {
      List parallelList = Lists.newArrayList();
      List sequentialList = Lists.newArrayList();
      g = topologicalSort(methods, sequentialList, parallelList, comparator);
      GRAPH_CACHE.put(methods, g);
    }

    List result = g.findPredecessors(method);
    return result;
  }

  //TODO: This needs to be revisited so that, we dont update the parameter list "methodList"
  //but we are returning the values.
  public static void fixMethodsWithClass(ITestNGMethod[] methods,
                                   ITestClass testCls,
                                   List methodList) {
    for (ITestNGMethod itm : methods) {
      itm.setTestClass(testCls);

      if (methodList != null) {
        methodList.add(itm);
      }
    }
  }

  public static List invokedMethodsToMethods(Collection invokedMethods) {
    List result = Lists.newArrayList();
    for (IInvokedMethod im : invokedMethods) {
      ITestNGMethod tm = im.getTestMethod();
      tm.setDate(im.getDate());
      result.add(tm);
    }

    return result;
  }


  public static List methodsToMethodInstances(List sl) {
    List result = new ArrayList<>();
    for (ITestNGMethod iTestNGMethod : sl) {
      result.add(new MethodInstance(iTestNGMethod));
    }
    return result;
  }

  public static List methodInstancesToMethods(List methodInstances) {
    List result = Lists.newArrayList();
    for (IMethodInstance imi : methodInstances) {
      result.add(imi.getMethod());
    }
    return result;
  }

  public static void dumpInvokedMethodsInfoToConsole(Collection iInvokedMethods, int currentVerbosity) {
    if (currentVerbosity < 3) {
      return;
    }
    System.out.println("===== Invoked methods");
    for (IInvokedMethod im : iInvokedMethods) {
      if (im.isTestMethod()) {
        System.out.print("    ");
      }
      else if (im.isConfigurationMethod()) {
        System.out.print("  ");
      }
      else {
        continue;
      }
      System.out.println("" + im);
    }
    System.out.println("=====");
  }


  protected static String calculateMethodCanonicalName(Class methodClass, String methodName) {
    Set methods = ClassHelper.getAvailableMethods(methodClass); // TESTNG-139
    Method result = null;
    for (Method m : methods) {
      if (methodName.equals(m.getName())) {
        result = m;
        break;
      }
    }

    return result != null ? calculateMethodCanonicalName(result) : null;
  }

  protected static long calculateTimeOut(ITestNGMethod tm) {
    long result = tm.getTimeOut() > 0 ? tm.getTimeOut() : tm.getInvocationTimeOut();
    return result;
  }

  private static MatchResults matchMethod(ITestNGMethod[] methods, String regexp) {
    MatchResults results = new MatchResults();
    boolean usePackage = regexp.indexOf('.') != -1;
    Pattern pattern = Pattern.compile(regexp);
    for (ITestNGMethod method : methods) {
      ConstructorOrMethod thisMethod = method.getConstructorOrMethod();
      String thisMethodName = thisMethod.getName();
      String methodName = usePackage ? calculateMethodCanonicalName(method) : thisMethodName;
      Pair cacheKey = Pair.create(regexp, methodName);
      Boolean match = MATCH_CACHE.get(cacheKey);
      if (match == null) {
        match = pattern.matcher(methodName).matches();
        MATCH_CACHE.put(cacheKey, match);
      }
      if (match) {
        results.matchedMethods.add(method);
        results.foundAtLeastAMethod = true;
      }
    }
    return results;
  }

  private static class MatchResults {
    private List matchedMethods = Lists.newArrayList();
    private boolean foundAtLeastAMethod = false;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy