1package org.testng.internal; 2 3import java.lang.reflect.Method; 4import java.util.Collection; 5import java.util.HashSet; 6import java.util.List; 7import java.util.Map; 8import java.util.Set; 9import java.util.regex.Pattern; 10 11import org.testng.IMethodSelector; 12import org.testng.IMethodSelectorContext; 13import org.testng.ITestNGMethod; 14import org.testng.TestNGException; 15import org.testng.collections.ListMultiMap; 16import org.testng.collections.Lists; 17import org.testng.collections.Maps; 18import org.testng.xml.XmlClass; 19import org.testng.xml.XmlInclude; 20 21/** 22 * This class is the default method selector used by TestNG to determine 23 * which methods need to be included and excluded based on the specification 24 * given in testng.xml. 25 * 26 * Created on Sep 30, 2005 27 * @author cbeust 28 */ 29public class XmlMethodSelector implements IMethodSelector { 30 private static final long serialVersionUID = -9030548178025605629L; 31 32 // Groups included and excluded for this run 33 private Map<String, String> m_includedGroups = Maps.newHashMap(); 34 private Map<String, String> m_excludedGroups = Maps.newHashMap(); 35 private List<XmlClass> m_classes = null; 36 // The BeanShell expression for this test, if any 37 private String m_expression = null; 38 // List of methods included implicitly 39 private ListMultiMap<String, XmlInclude> m_includedMethods = Maps.newListMultiMap(); 40 private IBsh m_bsh = Dynamic.hasBsh() ? new Bsh() : new BshMock(); 41 42 @Override 43 public boolean includeMethod(IMethodSelectorContext context, 44 ITestNGMethod tm, boolean isTestMethod) 45 { 46// ppp("XML METHOD SELECTOR " + tm + " " + m_isInitialized); 47 48 if (! m_isInitialized) { 49 m_isInitialized = true; 50 init(context); 51 } 52 53 boolean result = false; 54 if (null != m_expression) { 55 return m_bsh.includeMethodFromExpression(m_expression, tm); 56 } 57 else { 58 result = includeMethodFromIncludeExclude(tm, isTestMethod); 59 } 60 61 return result; 62 } 63 64 private boolean includeMethodFromIncludeExclude(ITestNGMethod tm, boolean isTestMethod) { 65 boolean result = false; 66 Method m = tm.getMethod(); 67 String[] groups = tm.getGroups(); 68 Map<String, String> includedGroups = m_includedGroups; 69 Map<String, String> excludedGroups = m_excludedGroups; 70 List<XmlInclude> includeList = 71 m_includedMethods.get(MethodHelper.calculateMethodCanonicalName(tm)); 72 73 // 74 // No groups were specified: 75 // 76 if (includedGroups.size() == 0 && excludedGroups.size() == 0 77 && ! hasIncludedMethods() && ! hasExcludedMethods()) 78 // 79 // If we don't include or exclude any methods, method is in 80 // 81 { 82 result = true; 83 } 84 // 85 // If it's a configuration method and no groups were requested, we want it in 86 // 87 else if (includedGroups.size() == 0 && excludedGroups.size() == 0 && ! isTestMethod) 88 { 89 result = true; 90 } 91 92 // 93 // Is this method included implicitly? 94 // 95 else if (includeList != null) { 96 result = true; 97 } 98 99 // 100 // Include or Exclude groups were specified: 101 // 102 else { 103 // 104 // Only add this method if it belongs to an included group and not 105 // to an excluded group 106 // 107 { 108 boolean isIncludedInGroups = isIncluded(groups, m_includedGroups.values()); 109 boolean isExcludedInGroups = isExcluded(groups, m_excludedGroups.values()); 110 111 // 112 // Calculate the run methods by groups first 113 // 114 if (isIncludedInGroups && !isExcludedInGroups) { 115 result = true; 116 } 117 else if (isExcludedInGroups) { 118 result = false; 119 } 120 } 121 122 if(isTestMethod) { 123 // 124 // Now filter by method name 125 // 126 Method method = tm.getMethod(); 127 Class methodClass = method.getDeclaringClass(); 128 String fullMethodName = methodClass.getName() 129 + "." 130 + method.getName(); 131 132 String[] fullyQualifiedMethodName = new String[] { fullMethodName }; 133 134 // 135 // Iterate through all the classes so we can gather all the included and 136 // excluded methods 137 // 138 for (XmlClass xmlClass : m_classes) { 139 // Only consider included/excluded methods that belong to the same class 140 // we are looking at 141 Class cls = xmlClass.getSupportClass(); 142 if(!assignable(methodClass, cls)) { 143 continue; 144 } 145 146 List<String> includedMethods = 147 createQualifiedMethodNames(xmlClass, toStringList(xmlClass.getIncludedMethods())); 148 boolean isIncludedInMethods = isIncluded(fullyQualifiedMethodName, includedMethods); 149 List<String> excludedMethods = createQualifiedMethodNames(xmlClass, 150 xmlClass.getExcludedMethods()); 151 boolean isExcludedInMethods = isExcluded(fullyQualifiedMethodName, excludedMethods); 152 if (result) { 153 // If we're about to include this method by group, make sure 154 // it's included by method and not excluded by method 155 result = isIncludedInMethods && ! isExcludedInMethods; 156 } 157 // otherwise it's already excluded and nothing will bring it back, 158 // since exclusions preempt inclusions 159 } 160 } 161 } 162 163 Package pkg = m.getDeclaringClass().getPackage(); 164 String methodName = pkg != null ? pkg.getName() + "." + m.getName() : m.getName(); 165 166 logInclusion(result ? "Including" : "Excluding", "method", methodName + "()"); 167 168 return result; 169 } 170 171 @SuppressWarnings({"unchecked"}) 172 private boolean assignable(Class sourceClass, Class targetClass) { 173 return sourceClass.isAssignableFrom(targetClass) || targetClass.isAssignableFrom(sourceClass); 174 } 175 176 private Map<String, String> m_logged = Maps.newHashMap(); 177 private void logInclusion(String including, String type, String name) { 178 if (! m_logged.containsKey(name)) { 179 log(4, including + " " + type + " " + name); 180 m_logged.put(name, name); 181 } 182 } 183 184 private boolean hasIncludedMethods() { 185 for (XmlClass xmlClass : m_classes) { 186 if (xmlClass.getIncludedMethods().size() > 0) { 187 return true; 188 } 189 } 190 191 return false; 192 } 193 194 private boolean hasExcludedMethods() { 195 for (XmlClass xmlClass : m_classes) { 196 if (xmlClass.getExcludedMethods().size() > 0) { 197 return true; 198 } 199 } 200 201 return false; 202 } 203 204 private List<String> toStringList(List<XmlInclude> methods) { 205 List<String> result = Lists.newArrayList(); 206 for (XmlInclude m : methods) { 207 result.add(m.getName()); 208 } 209 return result; 210 } 211 212 private List<String> createQualifiedMethodNames(XmlClass xmlClass, 213 List<String> methods) { 214 List<String> vResult = Lists.newArrayList(); 215 Class cls = xmlClass.getSupportClass(); 216 217 while (null != cls) { 218 for (String im : methods) { 219 String methodName = im; 220 Method[] allMethods = cls.getDeclaredMethods(); 221 Pattern pattern = Pattern.compile(methodName); 222 for (Method m : allMethods) { 223 if (pattern.matcher(m.getName()).matches()) { 224 vResult.add(makeMethodName(cls.getName(), m.getName())); 225 } 226 } 227 } 228 cls = cls.getSuperclass(); 229 } 230 231 return vResult; 232 } 233 234 private String makeMethodName(String className, String methodName) { 235 return className + "." + methodName; 236 } 237 238 private void checkMethod(Class<?> c, String methodName) { 239 Pattern p = Pattern.compile(methodName); 240 for (Method m : c.getMethods()) { 241 if (p.matcher(m.getName()).matches()) { 242 return; 243 } 244 } 245 Utils.log("Warning", 2, "The regular expression \"" + methodName + "\" didn't match any" + 246 " method in class " + c.getName()); 247 } 248 249 public void setXmlClasses(List<XmlClass> classes) { 250 m_classes = classes; 251 for (XmlClass c : classes) { 252 for (XmlInclude m : c.getIncludedMethods()) { 253 checkMethod(c.getSupportClass(), m.getName()); 254 String methodName = makeMethodName(c.getName(), m.getName()); 255 m_includedMethods.put(methodName, m); 256 } 257 } 258 } 259 260 /** 261 * @return Returns the excludedGroups. 262 */ 263 public Map<String, String> getExcludedGroups() { 264 return m_excludedGroups; 265 } 266 267 /** 268 * @return Returns the includedGroups. 269 */ 270 public Map<String, String> getIncludedGroups() { 271 return m_includedGroups; 272 } 273 274 /** 275 * @param excludedGroups The excludedGroups to set. 276 */ 277 public void setExcludedGroups(Map<String, String> excludedGroups) { 278 m_excludedGroups = excludedGroups; 279 } 280 281 /** 282 * @param includedGroups The includedGroups to set. 283 */ 284 public void setIncludedGroups(Map<String, String> includedGroups) { 285 m_includedGroups = includedGroups; 286 } 287 288 private static boolean isIncluded(String[] groups, Collection<String> includedGroups) { 289 if (includedGroups.size() == 0) { 290 return true; 291 } 292 else { 293 return isMemberOf(groups, includedGroups); 294 } 295 } 296 297 private static boolean isExcluded(String[] groups, Collection<String> excludedGroups) { 298 return isMemberOf(groups, excludedGroups); 299 } 300 301 /** 302 * 303 * @param groups Array of groups on the method 304 * @param list Map of regexps of groups to be run 305 */ 306 private static boolean isMemberOf(String[] groups, Collection<String> list) { 307 for (String group : groups) { 308 for (Object o : list) { 309 String regexpStr = o.toString(); 310 boolean match = Pattern.matches(regexpStr, group); 311 if (match) { 312 return true; 313 } 314 } 315 } 316 317 return false; 318 } 319 320 private static void log(int level, String s) { 321 Utils.log("XmlMethodSelector", level, s); 322 } 323 324 private static void ppp(String s) { 325 System.out.println("[XmlMethodSelector] " + s); 326 } 327 328 public void setExpression(String expression) { 329 m_expression = expression; 330 } 331 332 private boolean m_isInitialized = false; 333 private List<ITestNGMethod> m_testMethods = null; 334 335 @Override 336 public void setTestMethods(List<ITestNGMethod> testMethods) { 337 // Caution: this variable is initialized with an empty list first and then modified 338 // externally by the caller (TestRunner#fixMethodWithClass). Ugly. 339 m_testMethods = testMethods; 340 } 341 342 private void init(IMethodSelectorContext context) { 343 String[] groups = m_includedGroups.keySet().toArray(new String[m_includedGroups.size()]); 344 Set<String> groupClosure = new HashSet<>(); 345 Set<ITestNGMethod> methodClosure = new HashSet<>(); 346 347 List<ITestNGMethod> includedMethods = Lists.newArrayList(); 348 for (ITestNGMethod m : m_testMethods) { 349 if (includeMethod(context, m, true)) { 350 includedMethods.add(m); 351 } 352 } 353 MethodGroupsHelper.findGroupTransitiveClosure(this, includedMethods, m_testMethods, 354 groups, groupClosure, methodClosure); 355 356 // If we are asked to include or exclude specific groups, calculate 357 // the transitive closure of all the included groups. If no include groups 358 // were specified, don't do anything. 359 // Any group that is part of the transitive closure but not part of 360 // m_includedGroups is being added implicitly by TestNG so that if someone 361 // includes a group z that depends on a, b and c, they don't need to 362 // include a, b and c explicitly. 363 if (m_includedGroups.size() > 0) { 364 // Make the transitive closure our new included groups 365 for (String g : groupClosure) { 366 log(4, "Including group " 367 + (m_includedGroups.containsKey(g) ? 368 ": " : "(implicitly): ") + g); 369 m_includedGroups.put(g, g); 370 } 371 372 // Make the transitive closure our new included methods 373 for (ITestNGMethod m : methodClosure) { 374 String methodName = 375 m.getMethod().getDeclaringClass().getName() + "." + m.getMethodName(); 376// m_includedMethods.add(methodName); 377 List<XmlInclude> includeList = m_includedMethods.get(methodName); 378 XmlInclude xi = new XmlInclude(methodName); 379 // TODO: set the XmlClass on this xi or we won't get inheritance of parameters 380 m_includedMethods.put(methodName, xi); 381 logInclusion("Including", "method ", methodName); 382 } 383 } 384 } 385} 386