Home | History | Annotate | Download | only in internal
      1 package org.testng.internal;
      2 
      3 import java.lang.reflect.Method;
      4 import java.util.Collection;
      5 import java.util.Iterator;
      6 import java.util.List;
      7 import java.util.Map;
      8 import java.util.NoSuchElementException;
      9 import java.util.Set;
     10 import java.util.concurrent.ConcurrentHashMap;
     11 import java.util.regex.Pattern;
     12 
     13 import org.testng.ITestNGMethod;
     14 import org.testng.TestNGException;
     15 import org.testng.annotations.IExpectedExceptionsAnnotation;
     16 import org.testng.annotations.ITestAnnotation;
     17 import org.testng.annotations.ITestOrConfiguration;
     18 import org.testng.collections.Lists;
     19 import org.testng.collections.Sets;
     20 import org.testng.internal.annotations.AnnotationHelper;
     21 import org.testng.internal.annotations.IAnnotationFinder;
     22 import org.testng.internal.collections.Pair;
     23 
     24 /**
     25  * Collection of helper methods to help sort and arrange methods.
     26  *
     27  * @author <a href="mailto:cedric (at) beust.com">Cedric Beust</a>
     28  * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
     29  */
     30 public class MethodHelper {
     31   private static final Map<ITestNGMethod[], Graph<ITestNGMethod>> GRAPH_CACHE =
     32           new ConcurrentHashMap<>();
     33   private static final Map<Method, String> CANONICAL_NAME_CACHE = new ConcurrentHashMap<>();
     34   private static final Map<Pair<String, String>, Boolean> MATCH_CACHE =
     35           new ConcurrentHashMap<>();
     36 
     37   /**
     38    * Collects and orders test or configuration methods
     39    * @param methods methods to be worked on
     40    * @param forTests true for test methods, false for configuration methods
     41    * @param runInfo
     42    * @param finder annotation finder
     43    * @param unique true for unique methods, false otherwise
     44    * @param outExcludedMethods
     45    * @return list of ordered methods
     46    */
     47   public static ITestNGMethod[] collectAndOrderMethods(List<ITestNGMethod> methods,
     48       boolean forTests, RunInfo runInfo, IAnnotationFinder finder,
     49       boolean unique, List<ITestNGMethod> outExcludedMethods)
     50   {
     51     List<ITestNGMethod> includedMethods = Lists.newArrayList();
     52     MethodGroupsHelper.collectMethodsByGroup(methods.toArray(new ITestNGMethod[methods.size()]),
     53         forTests,
     54         includedMethods,
     55         outExcludedMethods,
     56         runInfo,
     57         finder,
     58         unique);
     59 
     60     return sortMethods(forTests, includedMethods, finder).toArray(new ITestNGMethod[]{});
     61   }
     62 
     63   /**
     64    * Finds TestNG methods that the specified TestNG method depends upon
     65    * @param m TestNG method
     66    * @param methods list of methods to search for depended upon methods
     67    * @return list of methods that match the criteria
     68    */
     69   protected static ITestNGMethod[] findDependedUponMethods(ITestNGMethod m,
     70       ITestNGMethod[] methods)
     71   {
     72     String canonicalMethodName = calculateMethodCanonicalName(m);
     73     List<ITestNGMethod> vResult = Lists.newArrayList();
     74     String regexp = null;
     75     for (String fullyQualifiedRegexp : m.getMethodsDependedUpon()) {
     76       boolean foundAtLeastAMethod = false;
     77 
     78       if (null != fullyQualifiedRegexp) {
     79         // Escapes $ in regexps as it is not meant for end - line matching, but inner class matches.
     80         regexp = fullyQualifiedRegexp.replace("$", "\\$");
     81         boolean usePackage = regexp.indexOf('.') != -1;
     82         Pattern pattern = Pattern.compile(regexp);
     83 
     84         for (ITestNGMethod method : methods) {
     85           Method thisMethod = method.getMethod();
     86           String thisMethodName = thisMethod.getName();
     87           String methodName = usePackage ?
     88               calculateMethodCanonicalName(thisMethod)
     89               : thisMethodName;
     90           Pair<String, String> cacheKey = Pair.create(regexp, methodName);
     91           Boolean match = MATCH_CACHE.get(cacheKey);
     92           if (match == null) {
     93               match = pattern.matcher(methodName).matches();
     94               MATCH_CACHE.put(cacheKey, match);
     95           }
     96           if (match) {
     97             vResult.add(method);
     98             foundAtLeastAMethod = true;
     99           }
    100         }
    101       }
    102 
    103       if (!foundAtLeastAMethod) {
    104         if (m.ignoreMissingDependencies()) {
    105           continue;
    106         }
    107         if (m.isAlwaysRun()) {
    108           continue;
    109         }
    110         Method maybeReferringTo = findMethodByName(m, regexp);
    111         if (maybeReferringTo != null) {
    112           throw new TestNGException(canonicalMethodName + "() is depending on method "
    113               + maybeReferringTo + ", which is not annotated with @Test or not included.");
    114         }
    115         throw new TestNGException(canonicalMethodName
    116             + "() depends on nonexistent method " + regexp);
    117       }
    118     }//end for
    119 
    120     return vResult.toArray(new ITestNGMethod[vResult.size()]);
    121   }
    122 
    123   /**
    124    * Finds method based on regex and TestNGMethod. If regex doesn't represent the
    125    * class name, uses the TestNG method's class name.
    126    * @param testngMethod TestNG method
    127    * @param regExp regex representing a method and/or related class name
    128    */
    129   private static Method findMethodByName(ITestNGMethod testngMethod, String regExp) {
    130     if (regExp == null) {
    131       return null;
    132     }
    133     int lastDot = regExp.lastIndexOf('.');
    134     String className, methodName;
    135     if (lastDot == -1) {
    136       className = testngMethod.getMethod().getDeclaringClass().getCanonicalName();
    137       methodName = regExp;
    138     } else {
    139       methodName = regExp.substring(lastDot + 1);
    140       className = regExp.substring(0, lastDot);
    141     }
    142 
    143     try {
    144       Class<?> c = Class.forName(className);
    145       for (Method m : c.getDeclaredMethods()) {
    146         if (methodName.equals(m.getName())) {
    147           return m;
    148         }
    149       }
    150     }
    151     catch (Exception e) {
    152       //only logging
    153       Utils.log("MethodHelper", 3, "Caught exception while searching for methods using regex");
    154     }
    155     return null;
    156   }
    157 
    158   protected static boolean isEnabled(Class<?> objectClass, IAnnotationFinder finder) {
    159     ITestAnnotation testClassAnnotation = AnnotationHelper.findTest(finder, objectClass);
    160     return isEnabled(testClassAnnotation);
    161   }
    162 
    163   protected static boolean isEnabled(Method m, IAnnotationFinder finder) {
    164     ITestAnnotation annotation = AnnotationHelper.findTest(finder, m);
    165 
    166     // If no method annotation, look for one on the class
    167     if (null == annotation) {
    168       annotation = AnnotationHelper.findTest(finder, m.getDeclaringClass());
    169     }
    170 
    171     return isEnabled(annotation);
    172   }
    173 
    174   protected static boolean isEnabled(ITestOrConfiguration test) {
    175     return null == test || test.getEnabled();
    176   }
    177 
    178   /**
    179    * Extracts the unique list of <code>ITestNGMethod</code>s.
    180    */
    181   public static List<ITestNGMethod> uniqueMethodList(Collection<List<ITestNGMethod>> methods) {
    182     Set<ITestNGMethod> resultSet = Sets.newHashSet();
    183 
    184     for (List<ITestNGMethod> l : methods) {
    185       resultSet.addAll(l);
    186     }
    187 
    188     return Lists.newArrayList(resultSet);
    189   }
    190 
    191   private static Graph<ITestNGMethod> topologicalSort(ITestNGMethod[] methods,
    192       List<ITestNGMethod> sequentialList, List<ITestNGMethod> parallelList) {
    193     Graph<ITestNGMethod> result = new Graph<>();
    194 
    195     if (methods.length == 0) {
    196       return result;
    197     }
    198 
    199     //
    200     // Create the graph
    201     //
    202     for (ITestNGMethod m : methods) {
    203       result.addNode(m);
    204 
    205       List<ITestNGMethod> predecessors = Lists.newArrayList();
    206 
    207       String[] methodsDependedUpon = m.getMethodsDependedUpon();
    208       String[] groupsDependedUpon = m.getGroupsDependedUpon();
    209       if (methodsDependedUpon.length > 0) {
    210         ITestNGMethod[] methodsNamed =
    211           MethodHelper.findDependedUponMethods(m, methods);
    212         for (ITestNGMethod pred : methodsNamed) {
    213           predecessors.add(pred);
    214         }
    215       }
    216       if (groupsDependedUpon.length > 0) {
    217         for (String group : groupsDependedUpon) {
    218           ITestNGMethod[] methodsThatBelongToGroup =
    219             MethodGroupsHelper.findMethodsThatBelongToGroup(m, methods, group);
    220           for (ITestNGMethod pred : methodsThatBelongToGroup) {
    221             predecessors.add(pred);
    222           }
    223         }
    224       }
    225 
    226       for (ITestNGMethod predecessor : predecessors) {
    227         result.addPredecessor(m, predecessor);
    228       }
    229     }
    230 
    231     result.topologicalSort();
    232     sequentialList.addAll(result.getStrictlySortedNodes());
    233     parallelList.addAll(result.getIndependentNodes());
    234 
    235     return result;
    236   }
    237 
    238   protected static String calculateMethodCanonicalName(ITestNGMethod m) {
    239     return calculateMethodCanonicalName(m.getMethod());
    240   }
    241 
    242   private static String calculateMethodCanonicalName(Method m) {
    243     String result = CANONICAL_NAME_CACHE.get(m);
    244     if (result != null) {
    245       return result;
    246     }
    247 
    248     String packageName = m.getDeclaringClass().getName() + "." + m.getName();
    249 
    250     // Try to find the method on this class or parents
    251     Class<?> cls = m.getDeclaringClass();
    252     while (cls != Object.class) {
    253       try {
    254         if (cls.getDeclaredMethod(m.getName(), m.getParameterTypes()) != null) {
    255           packageName = cls.getName();
    256           break;
    257         }
    258       }
    259       catch (Exception e) {
    260         // ignore
    261       }
    262       cls = cls.getSuperclass();
    263     }
    264 
    265     result = packageName + "." + m.getName();
    266     CANONICAL_NAME_CACHE.put(m, result);
    267     return result;
    268   }
    269 
    270   private static List<ITestNGMethod> sortMethods(boolean forTests,
    271       List<ITestNGMethod> allMethods, IAnnotationFinder finder) {
    272     List<ITestNGMethod> sl = Lists.newArrayList();
    273     List<ITestNGMethod> pl = Lists.newArrayList();
    274     ITestNGMethod[] allMethodsArray = allMethods.toArray(new ITestNGMethod[allMethods.size()]);
    275 
    276     // Fix the method inheritance if these are @Configuration methods to make
    277     // sure base classes are invoked before child classes if 'before' and the
    278     // other way around if they are 'after'
    279     if (!forTests && allMethodsArray.length > 0) {
    280       ITestNGMethod m = allMethodsArray[0];
    281       boolean before = m.isBeforeClassConfiguration()
    282           || m.isBeforeMethodConfiguration() || m.isBeforeSuiteConfiguration()
    283           || m.isBeforeTestConfiguration();
    284       MethodInheritance.fixMethodInheritance(allMethodsArray, before);
    285     }
    286 
    287     topologicalSort(allMethodsArray, sl, pl);
    288 
    289     List<ITestNGMethod> result = Lists.newArrayList();
    290     result.addAll(sl);
    291     result.addAll(pl);
    292     return result;
    293   }
    294 
    295   /**
    296    * @return A sorted array containing all the methods 'method' depends on
    297    */
    298   public static List<ITestNGMethod> getMethodsDependedUpon(ITestNGMethod method, ITestNGMethod[] methods) {
    299     Graph<ITestNGMethod> g = GRAPH_CACHE.get(methods);
    300     if (g == null) {
    301       List<ITestNGMethod> parallelList = Lists.newArrayList();
    302       List<ITestNGMethod> sequentialList = Lists.newArrayList();
    303       g = topologicalSort(methods, sequentialList, parallelList);
    304       GRAPH_CACHE.put(methods, g);
    305     }
    306 
    307     List<ITestNGMethod> result = g.findPredecessors(method);
    308     return result;
    309   }
    310 
    311   protected static Iterator<Object[]> createArrayIterator(final Object[][] objects) {
    312     ArrayIterator result = new ArrayIterator(objects);
    313     return result;
    314   }
    315 
    316   protected static String calculateMethodCanonicalName(Class<?> methodClass, String methodName) {
    317     Set<Method> methods = ClassHelper.getAvailableMethods(methodClass); // TESTNG-139
    318     Method result = null;
    319     for (Method m : methods) {
    320       if (methodName.equals(m.getName())) {
    321         result = m;
    322         break;
    323       }
    324     }
    325 
    326     return result != null ? calculateMethodCanonicalName(result) : null;
    327   }
    328 
    329   protected static long calculateTimeOut(ITestNGMethod tm) {
    330     long result = tm.getTimeOut() > 0 ? tm.getTimeOut() : tm.getInvocationTimeOut();
    331     return result;
    332   }
    333 }
    334 
    335 /**
    336  * Custom iterator class over a 2D array
    337  *
    338  */
    339 class ArrayIterator implements Iterator<Object[]> {
    340   private Object[][] m_objects;
    341   private int m_count;
    342 
    343   public ArrayIterator(Object[][] objects) {
    344     m_objects = objects;
    345     m_count = 0;
    346   }
    347 
    348   @Override
    349   public boolean hasNext() {
    350     return m_count < m_objects.length;
    351   }
    352 
    353   @Override
    354   public Object[] next() {
    355     if (m_count >= m_objects.length) {
    356       throw new NoSuchElementException();
    357     }
    358     return m_objects[m_count++];
    359   }
    360 
    361   @Override
    362   public void remove() {
    363     throw new UnsupportedOperationException("Remove operation is not supported on this iterator");
    364   }
    365 
    366 }
    367