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@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    */
collectAndOrderMethods(List<ITestNGMethod> methods, boolean forTests, RunInfo runInfo, IAnnotationFinder finder, boolean unique, List<ITestNGMethod> outExcludedMethods)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    */
findDependedUponMethods(ITestNGMethod m, ITestNGMethod[] methods)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    */
findMethodByName(ITestNGMethod testngMethod, String regExp)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 
isEnabled(Class<?> objectClass, IAnnotationFinder finder)158   protected static boolean isEnabled(Class<?> objectClass, IAnnotationFinder finder) {
159     ITestAnnotation testClassAnnotation = AnnotationHelper.findTest(finder, objectClass);
160     return isEnabled(testClassAnnotation);
161   }
162 
isEnabled(Method m, IAnnotationFinder finder)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 
isEnabled(ITestOrConfiguration test)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    */
uniqueMethodList(Collection<List<ITestNGMethod>> methods)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 
topologicalSort(ITestNGMethod[] methods, List<ITestNGMethod> sequentialList, List<ITestNGMethod> parallelList)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 
calculateMethodCanonicalName(ITestNGMethod m)238   protected static String calculateMethodCanonicalName(ITestNGMethod m) {
239     return calculateMethodCanonicalName(m.getMethod());
240   }
241 
calculateMethodCanonicalName(Method m)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 
sortMethods(boolean forTests, List<ITestNGMethod> allMethods, IAnnotationFinder finder)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    */
getMethodsDependedUpon(ITestNGMethod method, ITestNGMethod[] methods)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 
createArrayIterator(final Object[][] objects)311   protected static Iterator<Object[]> createArrayIterator(final Object[][] objects) {
312     ArrayIterator result = new ArrayIterator(objects);
313     return result;
314   }
315 
calculateMethodCanonicalName(Class<?> methodClass, String methodName)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 
calculateTimeOut(ITestNGMethod tm)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 
ArrayIterator(Object[][] objects)343   public ArrayIterator(Object[][] objects) {
344     m_objects = objects;
345     m_count = 0;
346   }
347 
348   @Override
hasNext()349   public boolean hasNext() {
350     return m_count < m_objects.length;
351   }
352 
353   @Override
next()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
remove()362   public void remove() {
363     throw new UnsupportedOperationException("Remove operation is not supported on this iterator");
364   }
365 
366 }
367