Home | History | Annotate | Download | only in internal
      1 package org.testng.internal;
      2 
      3 import java.lang.reflect.Method;
      4 import java.util.Collection;
      5 import java.util.HashSet;
      6 import java.util.List;
      7 import java.util.Map;
      8 import java.util.Set;
      9 import java.util.regex.Pattern;
     10 
     11 import org.testng.IMethodSelector;
     12 import org.testng.IMethodSelectorContext;
     13 import org.testng.ITestNGMethod;
     14 import org.testng.TestNGException;
     15 import org.testng.collections.ListMultiMap;
     16 import org.testng.collections.Lists;
     17 import org.testng.collections.Maps;
     18 import org.testng.xml.XmlClass;
     19 import 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  */
     29 public 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