1 package org.testng.internal; 2 3 import java.lang.reflect.Method; 4 import java.util.Collection; 5 import java.util.List; 6 import java.util.Map; 7 import java.util.Set; 8 import java.util.concurrent.ConcurrentHashMap; 9 import java.util.regex.Pattern; 10 11 import org.testng.ITestClass; 12 import org.testng.ITestNGMethod; 13 import org.testng.annotations.IConfigurationAnnotation; 14 import org.testng.annotations.ITestOrConfiguration; 15 import org.testng.collections.Lists; 16 import org.testng.collections.Maps; 17 import org.testng.internal.annotations.AnnotationHelper; 18 import org.testng.internal.annotations.IAnnotationFinder; 19 import org.testng.internal.collections.Pair; 20 21 /** 22 * Collections of helper methods to help deal with test methods 23 * 24 * @author Cedric Beust <cedric@beust.com> 25 * @author nullin <nalin.makar * gmail.com> 26 * 27 */ 28 public class MethodGroupsHelper { 29 30 private static final Map<String, Pattern> PATTERN_CACHE = new ConcurrentHashMap<>(); 31 private static final Map<Pair<String, String>, Boolean> MATCH_CACHE = 32 new ConcurrentHashMap<>(); 33 34 /** 35 * Collect all the methods that belong to the included groups and exclude all 36 * the methods that belong to an excluded group. 37 */ collectMethodsByGroup(ITestNGMethod[] methods, boolean forTests, List<ITestNGMethod> outIncludedMethods, List<ITestNGMethod> outExcludedMethods, RunInfo runInfo, IAnnotationFinder finder, boolean unique)38 static void collectMethodsByGroup(ITestNGMethod[] methods, 39 boolean forTests, 40 List<ITestNGMethod> outIncludedMethods, 41 List<ITestNGMethod> outExcludedMethods, 42 RunInfo runInfo, 43 IAnnotationFinder finder, boolean unique) 44 { 45 for (ITestNGMethod tm : methods) { 46 boolean in = false; 47 Method m = tm.getMethod(); 48 // 49 // @Test method 50 // 51 if (forTests) { 52 in = MethodGroupsHelper.includeMethod(AnnotationHelper.findTest(finder, m), 53 runInfo, tm, forTests, unique, outIncludedMethods); 54 } 55 56 // 57 // @Configuration method 58 // 59 else { 60 IConfigurationAnnotation annotation = AnnotationHelper.findConfiguration(finder, m); 61 if (annotation.getAlwaysRun()) { 62 if (!unique || !MethodGroupsHelper.isMethodAlreadyPresent(outIncludedMethods, tm)) { 63 in = true; 64 } 65 } 66 else { 67 in = MethodGroupsHelper.includeMethod(AnnotationHelper.findTest(finder, tm), 68 runInfo, tm, forTests, unique, outIncludedMethods); 69 } 70 } 71 if (in) { 72 outIncludedMethods.add(tm); 73 } 74 else { 75 outExcludedMethods.add(tm); 76 } 77 } 78 } 79 includeMethod(ITestOrConfiguration annotation, RunInfo runInfo, ITestNGMethod tm, boolean forTests, boolean unique, List<ITestNGMethod> outIncludedMethods)80 private static boolean includeMethod(ITestOrConfiguration annotation, 81 RunInfo runInfo, ITestNGMethod tm, boolean forTests, boolean unique, List<ITestNGMethod> outIncludedMethods) 82 { 83 boolean result = false; 84 85 if (MethodHelper.isEnabled(annotation)) { 86 if (runInfo.includeMethod(tm, forTests)) { 87 if (unique) { 88 if (!MethodGroupsHelper.isMethodAlreadyPresent(outIncludedMethods, tm)) { 89 result = true; 90 } 91 } 92 else { 93 result = true; 94 } 95 } 96 } 97 98 return result; 99 } 100 101 /** 102 * @param result 103 * @param tm 104 * @return true if a method by a similar name (and same hierarchy) already 105 * exists 106 */ isMethodAlreadyPresent(List<ITestNGMethod> result, ITestNGMethod tm)107 private static boolean isMethodAlreadyPresent(List<ITestNGMethod> result, 108 ITestNGMethod tm) { 109 for (ITestNGMethod m : result) { 110 Method jm1 = m.getMethod(); 111 Method jm2 = tm.getMethod(); 112 if (jm1.getName().equals(jm2.getName())) { 113 // Same names, see if they are in the same hierarchy 114 Class<?> c1 = jm1.getDeclaringClass(); 115 Class<?> c2 = jm2.getDeclaringClass(); 116 if (c1.isAssignableFrom(c2) || c2.isAssignableFrom(c1)) { 117 return true; 118 } 119 } 120 } 121 122 return false; 123 } 124 125 /** 126 * Extracts the map of groups and their corresponding methods from the <code>classes</code>. 127 */ findGroupsMethods(Collection<ITestClass> classes, boolean before)128 public static Map<String, List<ITestNGMethod>> findGroupsMethods(Collection<ITestClass> classes, boolean before) { 129 Map<String, List<ITestNGMethod>> result = Maps.newHashMap(); 130 for (ITestClass cls : classes) { 131 ITestNGMethod[] methods = before ? cls.getBeforeGroupsMethods() : cls.getAfterGroupsMethods(); 132 for (ITestNGMethod method : methods) { 133 for (String group : before ? method.getBeforeGroups() : method.getAfterGroups()) { 134 List<ITestNGMethod> methodList = result.get(group); 135 if (methodList == null) { 136 methodList = Lists.newArrayList(); 137 result.put(group, methodList); 138 } 139 // NOTE(cbeust, 2007/01/23) 140 // BeforeGroups/AfterGroups methods should only be invoked once. 141 // I should probably use a map instead of a list for a contains(), but 142 // this list should usually be fairly short 143 if (! methodList.contains(method)) { 144 methodList.add(method); 145 } 146 } 147 } 148 } 149 150 return result; 151 } 152 findGroupTransitiveClosure(XmlMethodSelector xms, List<ITestNGMethod> includedMethods, List<ITestNGMethod> allMethods, String[] includedGroups, Set<String> outGroups, Set<ITestNGMethod> outMethods)153 protected static void findGroupTransitiveClosure(XmlMethodSelector xms, 154 List<ITestNGMethod> includedMethods, 155 List<ITestNGMethod> allMethods, 156 String[] includedGroups, 157 Set<String> outGroups, Set<ITestNGMethod> outMethods) 158 { 159 Map<ITestNGMethod, ITestNGMethod> runningMethods = Maps.newHashMap(); 160 for (ITestNGMethod m : includedMethods) { 161 runningMethods.put(m, m); 162 } 163 164 Map<String, String> runningGroups = Maps.newHashMap(); 165 for (String thisGroup : includedGroups) { 166 runningGroups.put(thisGroup, thisGroup); 167 } 168 169 boolean keepGoing = true; 170 171 Map<ITestNGMethod, ITestNGMethod> newMethods = Maps.newHashMap(); 172 while (keepGoing) { 173 for (ITestNGMethod m : includedMethods) { 174 175 // 176 // Depends on groups? 177 // Adds all included methods to runningMethods 178 // 179 String[] ig = m.getGroupsDependedUpon(); 180 for (String g : ig) { 181 if (! runningGroups.containsKey(g)) { 182 // Found a new included group, add all the methods it contains to 183 // our outMethod closure 184 runningGroups.put(g, g); 185 ITestNGMethod[] im = 186 MethodGroupsHelper.findMethodsThatBelongToGroup(m, 187 allMethods.toArray(new ITestNGMethod[allMethods.size()]), g); 188 for (ITestNGMethod thisMethod : im) { 189 if (! runningMethods.containsKey(thisMethod)) { 190 runningMethods.put(thisMethod, thisMethod); 191 newMethods.put(thisMethod, thisMethod); 192 } 193 } 194 } 195 } // groups 196 197 // 198 // Depends on methods? 199 // Adds all depended methods to runningMethods 200 // 201 String[] mdu = m.getMethodsDependedUpon(); 202 for (String tm : mdu) { 203 ITestNGMethod thisMethod = MethodGroupsHelper.findMethodNamed(tm, allMethods); 204 if (thisMethod != null && ! runningMethods.containsKey(thisMethod)) { 205 runningMethods.put(thisMethod, thisMethod); 206 newMethods.put(thisMethod, thisMethod); 207 } 208 } 209 210 } // methods 211 212 // 213 // Only keep going if new methods have been added 214 // 215 keepGoing = newMethods.size() > 0; 216 includedMethods = Lists.newArrayList(); 217 includedMethods.addAll(newMethods.keySet()); 218 newMethods = Maps.newHashMap(); 219 } // while keepGoing 220 221 outMethods.addAll(runningMethods.keySet()); 222 outGroups.addAll(runningGroups.keySet()); 223 } 224 findMethodNamed(String tm, List<ITestNGMethod> allMethods)225 private static ITestNGMethod findMethodNamed(String tm, List<ITestNGMethod> allMethods) { 226 for (ITestNGMethod m : allMethods) { 227 // TODO(cbeust): account for package 228 String methodName = 229 m.getMethod().getDeclaringClass().getName() + "." + m.getMethodName(); 230 if (methodName.equals(tm)) { 231 return m; 232 } 233 } 234 235 return null; 236 } 237 238 /** 239 * Only used if a group is missing to flag an error on that method 240 * 241 * @param method if no group is found, group regex is set as this method's missing group 242 * @param methods list of methods to search 243 * @param groupRegexp regex representing the group 244 * 245 * @return all the methods that belong to the group specified by the regular 246 * expression groupRegExp. methods[] is the list of all the methods we 247 * are choosing from and method is the method that owns the dependsOnGroups 248 * statement (only used if a group is missing to flag an error on that method). 249 */ findMethodsThatBelongToGroup( ITestNGMethod method, ITestNGMethod[] methods, String groupRegexp)250 protected static ITestNGMethod[] findMethodsThatBelongToGroup( 251 ITestNGMethod method, 252 ITestNGMethod[] methods, String groupRegexp) 253 { 254 ITestNGMethod[] found = findMethodsThatBelongToGroup(methods, groupRegexp); 255 256 if (found.length == 0) { 257 method.setMissingGroup(groupRegexp); 258 } 259 260 return found; 261 } 262 263 /** 264 * @param methods list of methods to search 265 * @param groupRegexp regex representing the group 266 * 267 * @return all the methods that belong to the group specified by the regular 268 * expression groupRegExp. methods[] is the list of all the methods we 269 * are choosing from. 270 */ findMethodsThatBelongToGroup(ITestNGMethod[] methods, String groupRegexp)271 protected static ITestNGMethod[] findMethodsThatBelongToGroup(ITestNGMethod[] methods, String groupRegexp) 272 { 273 List<ITestNGMethod> vResult = Lists.newArrayList(); 274 final Pattern pattern = getPattern(groupRegexp); 275 for (ITestNGMethod tm : methods) { 276 String[] groups = tm.getGroups(); 277 for (String group : groups) { 278 Boolean match = isMatch(pattern, group); 279 if (match) { 280 vResult.add(tm); 281 } 282 } 283 } 284 285 return vResult.toArray(new ITestNGMethod[vResult.size()]); 286 } 287 isMatch(Pattern pattern, String group)288 private static Boolean isMatch(Pattern pattern, String group) { 289 Pair<String, String> cacheKey = Pair.create(pattern.pattern(), group); 290 Boolean match = MATCH_CACHE.get(cacheKey); 291 if (match == null) { 292 match = pattern.matcher(group).matches(); 293 MATCH_CACHE.put(cacheKey, match); 294 } 295 return match; 296 } 297 getPattern(String groupRegexp)298 private static Pattern getPattern(String groupRegexp) { 299 Pattern groupPattern = PATTERN_CACHE.get(groupRegexp); 300 if (groupPattern == null) { 301 groupPattern = Pattern.compile(groupRegexp); 302 PATTERN_CACHE.put(groupRegexp, groupPattern); 303 } 304 return groupPattern; 305 } 306 307 308 } 309