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