| package org.testng.internal; |
| |
| import bsh.EvalError; |
| import bsh.Interpreter; |
| |
| import org.testng.IMethodSelector; |
| import org.testng.IMethodSelectorContext; |
| import org.testng.ITestNGMethod; |
| import org.testng.TestNGException; |
| import org.testng.collections.Lists; |
| import org.testng.collections.Maps; |
| import org.testng.xml.XmlClass; |
| import org.testng.xml.XmlInclude; |
| |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.regex.Pattern; |
| |
| /** |
| * This class is the default method selector used by TestNG to determine |
| * which methods need to be included and excluded based on the specification |
| * given in testng.xml. |
| * |
| * Created on Sep 30, 2005 |
| * @author cbeust |
| */ |
| public class XmlMethodSelector implements IMethodSelector { |
| private static Interpreter s_interpreter; |
| |
| // Groups included and excluded for this run |
| private Map<String, String> m_includedGroups = Maps.newHashMap(); |
| private Map<String, String> m_excludedGroups = Maps.newHashMap(); |
| private List<XmlClass> m_classes = null; |
| // The BeanShell expression for this test, if any |
| private String m_expression = null; |
| // List of methods included implicitly |
| private Set<String> m_includedMethods = new HashSet<String>(); |
| |
| public boolean includeMethod(IMethodSelectorContext context, |
| ITestNGMethod tm, boolean isTestMethod) |
| { |
| // ppp("XML METHOD SELECTOR " + tm + " " + m_isInitialized); |
| |
| if (! m_isInitialized) { |
| m_isInitialized = true; |
| init(context); |
| } |
| |
| boolean result = false; |
| if (null != m_expression) { |
| result = includeMethodFromExpression(tm, isTestMethod); |
| } |
| else { |
| result = includeMethodFromIncludeExclude(tm, isTestMethod); |
| } |
| |
| return result; |
| } |
| |
| private static Interpreter getInterpreter() { |
| if(null == s_interpreter) { |
| s_interpreter= new Interpreter(); |
| } |
| |
| return s_interpreter; |
| } |
| |
| private boolean includeMethodFromExpression(ITestNGMethod tm, boolean isTestMethod) { |
| boolean result = false; |
| |
| Interpreter interpreter= getInterpreter(); |
| try { |
| Map<String, String> groups = Maps.newHashMap(); |
| for (String group : tm.getGroups()) { |
| groups.put(group, group); |
| } |
| setContext(interpreter, tm.getMethod(), groups, tm); |
| Object evalResult = interpreter.eval(m_expression); |
| result = ((Boolean) evalResult).booleanValue(); |
| } |
| catch (EvalError evalError) { |
| Utils.log("bsh.Interpreter", 2, "Cannot evaluate expression:" + m_expression + ":" + evalError.getMessage()); |
| } |
| finally { |
| resetContext(interpreter); |
| } |
| |
| return result; |
| |
| } |
| |
| private void resetContext(Interpreter interpreter) { |
| try { |
| interpreter.unset("method"); |
| interpreter.unset("groups"); |
| interpreter.unset("testngMethod"); |
| } |
| catch(EvalError evalError) { |
| Utils.log("bsh.Interpreter", 2, "Cannot reset interpreter:" + evalError.getMessage()); |
| } |
| } |
| |
| private void setContext(Interpreter interpreter, Method method, Map<String, String> groups, ITestNGMethod tm) { |
| try { |
| interpreter.set("method", method); |
| interpreter.set("groups", groups); |
| interpreter.set("testngMethod", tm); |
| } |
| catch(EvalError evalError) { |
| throw new TestNGException("Cannot set BSH interpreter", evalError); |
| } |
| } |
| |
| private boolean includeMethodFromIncludeExclude(ITestNGMethod tm, boolean isTestMethod) { |
| boolean result = false; |
| Method m = tm.getMethod(); |
| String[] groups = tm.getGroups(); |
| Map<String, String> includedGroups = m_includedGroups; |
| Map<String, String> excludedGroups = m_excludedGroups; |
| |
| // |
| // No groups were specified: |
| // |
| if (includedGroups.size() == 0 && excludedGroups.size() == 0 |
| && ! hasIncludedMethods() && ! hasExcludedMethods()) |
| // |
| // If we don't include or exclude any methods, method is in |
| // |
| { |
| result = true; |
| } |
| // |
| // If it's a configuration method and no groups were requested, we want it in |
| // |
| else if (includedGroups.size() == 0 && excludedGroups.size() == 0 && ! isTestMethod) |
| { |
| result = true; |
| } |
| |
| // |
| // Is this method included implicitly? |
| // |
| else if (m_includedMethods.contains(MethodHelper.calculateMethodCanonicalName(tm))) { |
| result = true; |
| } |
| |
| // |
| // Include or Exclude groups were specified: |
| // |
| else { |
| // |
| // Only add this method if it belongs to an included group and not |
| // to an excluded group |
| // |
| { |
| boolean isIncludedInGroups = isIncluded(groups, m_includedGroups.values()); |
| boolean isExcludedInGroups = isExcluded(groups, m_excludedGroups.values()); |
| |
| // |
| // Calculate the run methods by groups first |
| // |
| if (isIncludedInGroups && !isExcludedInGroups) { |
| result = true; |
| } |
| else if (isExcludedInGroups) { |
| result = false; |
| } |
| } |
| |
| if(isTestMethod) { |
| // |
| // Now filter by method name |
| // |
| Method method = tm.getMethod(); |
| Class methodClass= method.getDeclaringClass(); |
| String fullMethodName = methodClass.getName() |
| + "." |
| + method.getName(); |
| |
| String[] fullyQualifiedMethodName = new String[] { fullMethodName }; |
| |
| // |
| // Iterate through all the classes so we can gather all the included and |
| // excluded methods |
| // |
| for (XmlClass xmlClass : m_classes) { |
| // Only consider included/excluded methods that belong to the same class |
| // we are looking at |
| Class cls= ClassHelper.forName(xmlClass.getName()); |
| if(null == cls) { |
| Utils.log("XmlMethodSelector", 1, "Cannot find class in classpath " + xmlClass.getName()); |
| continue; |
| } |
| if(!assignable(methodClass, cls)) { |
| continue; |
| } |
| |
| List<String> includedMethods = |
| createQualifiedMethodNames(xmlClass, toStringList(xmlClass.getIncludedMethods())); |
| boolean isIncludedInMethods = isIncluded(fullyQualifiedMethodName, includedMethods); |
| List<String> excludedMethods = createQualifiedMethodNames(xmlClass, |
| xmlClass.getExcludedMethods()); |
| boolean isExcludedInMethods = isExcluded(fullyQualifiedMethodName, excludedMethods); |
| if (result) { |
| // If we're about to include this method by group, make sure |
| // it's included by method and not excluded by method |
| result = isIncludedInMethods && ! isExcludedInMethods; |
| } |
| // otherwise it's already excluded and nothing will bring it back, |
| // since exclusions preempt inclusions |
| } |
| } |
| } |
| |
| Package pkg = m.getDeclaringClass().getPackage(); |
| String methodName = pkg != null ? pkg.getName() + "." + m.getName() : m.getName(); |
| |
| logInclusion(result ? "Including" : "Excluding", "method", methodName + "()"); |
| |
| return result; |
| } |
| |
| @SuppressWarnings({"unchecked"}) |
| private boolean assignable(Class sourceClass, Class targetClass) { |
| return sourceClass.isAssignableFrom(targetClass) || targetClass.isAssignableFrom(sourceClass); |
| } |
| |
| private Map<String, String> m_logged = Maps.newHashMap(); |
| private void logInclusion(String including, String type, String name) { |
| if (! m_logged.containsKey(name)) { |
| log(4, including + " " + type + " " + name); |
| m_logged.put(name, name); |
| } |
| } |
| |
| private boolean hasIncludedMethods() { |
| for (XmlClass xmlClass : m_classes) { |
| if (xmlClass.getIncludedMethods().size() > 0) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| private boolean hasExcludedMethods() { |
| for (XmlClass xmlClass : m_classes) { |
| if (xmlClass.getExcludedMethods().size() > 0) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| private List<String> toStringList(List<XmlInclude> methods) { |
| List<String> result = Lists.newArrayList(); |
| for (XmlInclude m : methods) { |
| result.add(m.getName()); |
| } |
| return result; |
| } |
| |
| private List<String> createQualifiedMethodNames(XmlClass xmlClass, |
| List<String> methods) { |
| List<String> vResult = Lists.newArrayList(); |
| Class cls = xmlClass.getSupportClass(); |
| |
| while (null != cls) { |
| for (String im : methods) { |
| String methodName = im; |
| Method[] allMethods = cls.getDeclaredMethods(); |
| Pattern pattern = Pattern.compile(methodName); |
| for (Method m : allMethods) { |
| if (pattern.matcher(m.getName()).matches()) { |
| vResult.add(makeMethodName(cls.getName(), m.getName())); |
| } |
| } |
| } |
| cls = cls.getSuperclass(); |
| } |
| |
| return vResult; |
| } |
| |
| private String makeMethodName(String className, String methodName) { |
| return className + "." + methodName; |
| } |
| |
| public void setXmlClasses(List<XmlClass> classes) { |
| m_classes = classes; |
| for (XmlClass c : classes) { |
| for (XmlInclude m : c.getIncludedMethods()) { |
| m_includedMethods.add(makeMethodName(c.getName(), m.getName())); |
| } |
| } |
| } |
| |
| /** |
| * @return Returns the excludedGroups. |
| */ |
| public Map<String, String> getExcludedGroups() { |
| return m_excludedGroups; |
| } |
| |
| /** |
| * @return Returns the includedGroups. |
| */ |
| public Map<String, String> getIncludedGroups() { |
| return m_includedGroups; |
| } |
| |
| /** |
| * @param excludedGroups The excludedGroups to set. |
| */ |
| public void setExcludedGroups(Map<String, String> excludedGroups) { |
| m_excludedGroups = excludedGroups; |
| } |
| |
| /** |
| * @param includedGroups The includedGroups to set. |
| */ |
| public void setIncludedGroups(Map<String, String> includedGroups) { |
| m_includedGroups = includedGroups; |
| } |
| |
| private static boolean isIncluded(String[] groups, Collection<String> includedGroups) { |
| if (includedGroups.size() == 0) { |
| return true; |
| } |
| else { |
| return isMemberOf(groups, includedGroups); |
| } |
| } |
| |
| private static boolean isExcluded(String[] groups, Collection<String> excludedGroups) { |
| return isMemberOf(groups, excludedGroups); |
| } |
| |
| /** |
| * |
| * @param groups Array of groups on the method |
| * @param list Map of regexps of groups to be run |
| */ |
| private static boolean isMemberOf(String[] groups, Collection<String> list) { |
| for (String group : groups) { |
| for (Object o : list) { |
| String regexpStr = o.toString(); |
| boolean match = Pattern.matches(regexpStr, group); |
| if (match) { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| private static void log(int level, String s) { |
| Utils.log("XmlMethodSelector", level, s); |
| } |
| |
| private static void ppp(String s) { |
| System.out.println("[XmlMethodSelector] " + s); |
| } |
| |
| public void setExpression(String expression) { |
| m_expression = expression; |
| } |
| |
| private boolean m_isInitialized = false; |
| private List<ITestNGMethod> m_testMethods = null; |
| |
| public void setTestMethods(List<ITestNGMethod> testMethods) { |
| // Caution: this variable is initialized with an empty list first and then modified |
| // externally by the caller (TestRunner#fixMethodWithClass). Ugly. |
| m_testMethods = testMethods; |
| } |
| |
| private void init(IMethodSelectorContext context) { |
| String[] groups = m_includedGroups.keySet().toArray(new String[m_includedGroups.size()]); |
| Set<String> groupClosure = new HashSet<String>(); |
| Set<ITestNGMethod> methodClosure = new HashSet<ITestNGMethod>(); |
| |
| List<ITestNGMethod> includedMethods = Lists.newArrayList(); |
| for (ITestNGMethod m : m_testMethods) { |
| if (includeMethod(context, m, true)) includedMethods.add(m); |
| } |
| MethodHelper.findGroupTransitiveClosure(this, includedMethods, m_testMethods, |
| groups, groupClosure, methodClosure); |
| |
| // If we are asked to include or exclude specific groups, calculate |
| // the transitive closure of all the included groups. If no include groups |
| // were specified, don't do anything. |
| // Any group that is part of the transitive closure but not part of |
| // m_includedGroups is being added implicitly by TestNG so that if someone |
| // includes a group z that depends on a, b and c, they don't need to |
| // include a, b and c explicitly. |
| if (m_includedGroups.size() > 0) { |
| // Make the transitive closure our new included groups |
| for (String g : groupClosure) { |
| log(4, "Including group " |
| + (m_includedGroups.containsKey(g) ? |
| ": " : "(implicitly): ") + g); |
| m_includedGroups.put(g, g); |
| } |
| |
| // Make the transitive closure our new included methods |
| for (ITestNGMethod m : methodClosure) { |
| String methodName = |
| m.getMethod().getDeclaringClass().getName() + "." + m.getMethodName(); |
| m_includedMethods.add(methodName); |
| logInclusion("Including", "method ", methodName); |
| } |
| } |
| } |
| |
| private boolean m_verbose = true; |
| public void setVerbose(boolean b) { |
| m_verbose = b; |
| } |
| |
| } |