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