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.Arrays;
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 java.util.stream.Collectors;
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.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 - {@link RunInfo} object.
   * @param finder annotation finder
   * @param unique true for unique methods, false otherwise
   * @param outExcludedMethods - A List of excluded {@link ITestNGMethod} methods.
   * @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[0]),
        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[0]);
    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[0]);
  }

  /**
   * 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.getBeforeGroups().length != 0
        || configurationAnnotation.getAfterGroups().length != 0)
        && 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<>((o1, o2) -> 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;
        // 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);
        }
        predecessors.addAll(Arrays.asList(methodsNamed));
      }
      if (groupsDependedUpon.length > 0) {
        for (String group : groupsDependedUpon) {
          ITestNGMethod[] methodsThatBelongToGroup =
              MethodGroupsHelper.findMethodsThatBelongToGroup(m, methods, group);
          predecessors.addAll(Arrays.asList(methodsThatBelongToGroup));
        }
      }

      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[0]);

    // 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);
    }

    return g.findPredecessors(method);
  }

  // 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) {
    return sl.stream().map(MethodInstance::new).collect(Collectors.toList());
  }

  public static List methodInstancesToMethods(
      List methodInstances) {
    return methodInstances.stream().map(IMethodInstance::getMethod).collect(Collectors.toList());
  }

  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
    return methods.stream()
        .filter(method -> methodName.equals(method.getName()))
        .findFirst()
        .map(MethodHelper::calculateMethodCanonicalName)
        .orElse(null);
  }

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

  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