View Javadoc
1   package org.apache.maven.surefire.common.junit48;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collection;
25  import java.util.Collections;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Properties;
30  import java.util.Set;
31  
32  import org.apache.maven.shared.utils.io.SelectorUtils;
33  import org.apache.maven.surefire.booter.ProviderParameterNames;
34  import org.apache.maven.surefire.group.match.AndGroupMatcher;
35  import org.apache.maven.surefire.group.match.GroupMatcher;
36  import org.apache.maven.surefire.group.match.InverseGroupMatcher;
37  import org.apache.maven.surefire.group.parse.GroupMatcherParser;
38  import org.apache.maven.surefire.group.parse.ParseException;
39  import org.junit.experimental.categories.Category;
40  import org.junit.runner.Description;
41  import org.junit.runner.manipulation.Filter;
42  
43  /**
44   * @author Todd Lipcon
45   */
46  public class FilterFactory
47  {
48      private final ClassLoader testClassLoader;
49  
50      public FilterFactory( ClassLoader testClassLoader )
51      {
52          this.testClassLoader = testClassLoader;
53      }
54  
55      public Filter createGroupFilter( Properties providerProperties )
56      {
57          String groups = providerProperties.getProperty( ProviderParameterNames.TESTNG_GROUPS_PROP );
58          String excludedGroups = providerProperties.getProperty( ProviderParameterNames.TESTNG_EXCLUDEDGROUPS_PROP );
59  
60          GroupMatcher included = null;
61          if ( groups != null && groups.trim().length() > 0 )
62          {
63              try
64              {
65                  included = new GroupMatcherParser( groups ).parse();
66              }
67              catch ( ParseException e )
68              {
69                  throw new IllegalArgumentException( "Invalid group expression: '" + groups + "'. Reason: "
70                      + e.getMessage(), e );
71              }
72          }
73  
74          GroupMatcher excluded = null;
75          if ( excludedGroups != null && excludedGroups.trim().length() > 0 )
76          {
77              try
78              {
79                  excluded = new GroupMatcherParser( excludedGroups ).parse();
80              }
81              catch ( ParseException e )
82              {
83                  throw new IllegalArgumentException( "Invalid group expression: '" + excludedGroups + "'. Reason: "
84                      + e.getMessage(), e );
85              }
86          }
87  
88          if ( included != null && testClassLoader != null )
89          {
90              included.loadGroupClasses( testClassLoader );
91          }
92  
93          if ( excluded != null && testClassLoader != null )
94          {
95              excluded.loadGroupClasses( testClassLoader );
96          }
97  
98          return new GroupMatcherCategoryFilter( included, excluded );
99      }
100 
101     public Filter createMethodFilter( String requestedTestMethod )
102     {
103         return new MethodFilter( requestedTestMethod );
104     }
105 
106     public Filter createFailingMethodFilter( Map<Class<?>, Set<String>> failingClassMethodMap )
107     {
108         return new FailingMethodFilter( failingClassMethodMap );
109     }
110 
111     public Filter and( Filter filter1, Filter filter2 )
112     {
113         return new AndFilter( filter1, filter2 );
114     }
115 
116     private static class MethodFilter
117         extends Filter
118     {
119         private final String requestedTestMethod;
120 
121         public MethodFilter( String requestedTestMethod )
122         {
123             this.requestedTestMethod = requestedTestMethod;
124         }
125 
126         @Override
127         public boolean shouldRun( Description description )
128         {
129             for ( Description o : description.getChildren() )
130             {
131                 if ( isDescriptionMatch( o ) || shouldRun( o ) )
132                 {
133                     return true;
134                 }
135 
136             }
137             return isDescriptionMatch( description );
138         }
139 
140         private boolean isDescriptionMatch( Description description )
141         {
142             return description.getMethodName() != null
143                 && SelectorUtils.match( requestedTestMethod, description.getMethodName() );
144         }
145 
146         @Override
147         public String describe()
148         {
149             return "By method" + requestedTestMethod;
150         }
151     }
152 
153     // Only run test methods in the given input map, indexed by test class
154     private static class FailingMethodFilter
155         extends Filter
156     {
157         // Map from Class -> List of method names. Are the method names hashed to include the signature?
158         private final Map<Class<?>, Set<String>> failingClassMethodMap;
159 
160         public FailingMethodFilter( Map<Class<?>, Set<String>> failingClassMethodMap )
161         {
162             this.failingClassMethodMap = failingClassMethodMap;
163         }
164 
165         @Override
166         public boolean shouldRun( Description description )
167         {
168             return isDescriptionMatch( description );
169         }
170 
171         private boolean isDescriptionMatch( Description description )
172         {
173             if ( description.getTestClass() == null || description.getMethodName() == null )
174             {
175                 for ( Description childrenDescription : description.getChildren() )
176                 {
177                     if ( isDescriptionMatch( childrenDescription ) )
178                     {
179                         return true;
180                     }
181                 }
182                 return false;
183             }
184 
185             Set<String> testMethods = failingClassMethodMap.get( description.getTestClass() );
186             if ( testMethods == null )
187             {
188                 return false;
189             }
190             else
191             {
192                 return testMethods.contains( description.getMethodName() );
193             }
194         }
195 
196         @Override
197         public String describe()
198         {
199             return "By failing class method";
200         }
201     }
202 
203     private static class GroupMatcherCategoryFilter
204         extends Filter
205     {
206 
207         private AndGroupMatcher matcher;
208 
209         public GroupMatcherCategoryFilter( GroupMatcher included, GroupMatcher excluded )
210         {
211             GroupMatcher invertedExclude = excluded == null ? null : new InverseGroupMatcher( excluded );
212             if ( included != null || invertedExclude != null )
213             {
214                 matcher = new AndGroupMatcher();
215                 if ( included != null )
216                 {
217                     matcher.addMatcher( included );
218                 }
219 
220                 if ( invertedExclude != null )
221                 {
222                     matcher.addMatcher( invertedExclude );
223                 }
224             }
225         }
226 
227         @Override
228         public boolean shouldRun( Description description )
229         {
230             if ( description.getMethodName() == null || description.getTestClass() == null )
231             {
232                 return shouldRun( description, null, null );
233             }
234             else
235             {
236                 return shouldRun( description, Description.createSuiteDescription( description.getTestClass() ),
237                                   description.getTestClass() );
238             }
239         }
240 
241         private Collection<Class<?>> findSuperclassCategories( Class<?> clazz )
242         {
243             if ( clazz != null && clazz.getSuperclass() != null )
244             {
245                 Category cat = clazz.getSuperclass().getAnnotation( Category.class );
246                 if ( cat != null )
247                 {
248                     return new HashSet<Class<?>>( Arrays.asList( cat.value() ) );
249                 }
250                 else
251                 {
252                     return findSuperclassCategories( clazz.getSuperclass() );
253                 }
254             }
255 
256             return Collections.emptySet();
257         }
258 
259         private boolean shouldRun( Description description, Description parent, Class<?> parentClass )
260         {
261             if ( matcher == null )
262             {
263                 return true;
264             }
265 
266             Set<Class<?>> cats = new HashSet<Class<?>>();
267             Category cat = description.getAnnotation( Category.class );
268             if ( cat != null )
269             {
270                 cats.addAll( Arrays.asList( cat.value() ) );
271             }
272 
273             if ( parent != null )
274             {
275                 cat = parent.getAnnotation( Category.class );
276                 if ( cat != null )
277                 {
278                     cats.addAll( Arrays.asList( cat.value() ) );
279                 }
280             }
281 
282             if ( parentClass != null )
283             {
284                 cats.addAll( findSuperclassCategories( parentClass ) );
285             }
286 
287             boolean result = matcher.enabled( cats.toArray( new Class<?>[] {} ) );
288 
289             if ( parent == null )
290             {
291                 if ( cats.size() == 0 )
292                 {
293                     result = true;
294                 }
295                 else if ( !result )
296                 {
297                     ArrayList<Description> children = description.getChildren();
298                     if ( children != null )
299                     {
300                         for ( Description child : children )
301                         {
302                             if ( shouldRun( child, description, null ) )
303                             {
304                                 result = true;
305                                 break;
306                             }
307                         }
308                     }
309                 }
310             }
311 
312             return result;
313         }
314 
315         @Override
316         public String describe()
317         {
318             return matcher == null ? "ANY" : matcher.toString();
319         }
320 
321     }
322 
323     private static class AndFilter
324         extends Filter
325     {
326         private final Filter filter1;
327 
328         private final Filter filter2;
329 
330         public AndFilter( Filter filter1, Filter filter2 )
331         {
332             this.filter1 = filter1;
333             this.filter2 = filter2;
334         }
335 
336         @Override
337         public boolean shouldRun( Description description )
338         {
339             return filter1.shouldRun( description ) && filter2.shouldRun( description );
340         }
341 
342         @Override
343         public String describe()
344         {
345             return filter1.describe() + " AND " + filter2.describe();
346         }
347     }
348 
349     @SuppressWarnings( "unused" )
350     private static class CombinedCategoryFilter
351         extends Filter
352     {
353         private final List<Filter> includedFilters;
354 
355         private final List<Filter> excludedFilters;
356 
357         public CombinedCategoryFilter( List<Filter> includedFilters, List<Filter> excludedFilters )
358         {
359             this.includedFilters = includedFilters;
360             this.excludedFilters = excludedFilters;
361         }
362 
363         @Override
364         public boolean shouldRun( Description description )
365         {
366             return ( includedFilters.isEmpty() || inOneOfFilters( includedFilters, description ) )
367                 && ( excludedFilters.isEmpty() || !inOneOfFilters( excludedFilters, description ) );
368         }
369 
370         private boolean inOneOfFilters( List<Filter> filters, Description description )
371         {
372             for ( Filter f : filters )
373             {
374                 if ( f.shouldRun( description ) )
375                 {
376                     return true;
377                 }
378             }
379             return false;
380         }
381 
382         @Override
383         public String describe()
384         {
385             StringBuilder sb = new StringBuilder();
386             if ( !includedFilters.isEmpty() )
387             {
388                 sb.append( "(" );
389                 sb.append( joinFilters( includedFilters, " OR " ) );
390                 sb.append( ")" );
391                 if ( !excludedFilters.isEmpty() )
392                 {
393                     sb.append( " AND " );
394                 }
395             }
396             if ( !excludedFilters.isEmpty() )
397             {
398                 sb.append( "NOT (" );
399                 sb.append( joinFilters( includedFilters, " OR " ) );
400                 sb.append( ")" );
401             }
402 
403             return sb.toString();
404         }
405 
406         private String joinFilters( List<Filter> filters, String sep )
407         {
408             int i = 0;
409             StringBuilder sb = new StringBuilder();
410             for ( Filter f : filters )
411             {
412                 if ( i++ > 0 )
413                 {
414                     sb.append( sep );
415                 }
416                 sb.append( f.describe() );
417             }
418             return sb.toString();
419         }
420     }
421 
422 }