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