1 package org.apache.maven.surefire.testset;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.maven.shared.utils.StringUtils;
23 import org.apache.maven.shared.utils.io.SelectorUtils;
24
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.LinkedHashSet;
29 import java.util.Set;
30
31 import static java.util.Collections.singleton;
32 import static java.util.Collections.emptySet;
33
34
35
36
37
38
39
40
41
42 public class TestListResolver
43 implements GenericTestPattern<TestListResolver, ResolvedTest, String, String>
44 {
45 private static final String JAVA_CLASS_FILE_EXTENSION = ".class";
46
47 private static final Set<ResolvedTest> EMPTY_TEST_PATTERNS = emptySet();
48
49 private static final Set<String> EMPTY_SPECIFIC_TESTS = emptySet();
50
51 private final Set<ResolvedTest> includedPatterns;
52
53 private final Set<ResolvedTest> excludedPatterns;
54
55 private final Set<String> specificClasses;
56
57 private final boolean hasIncludedMethodPatterns;
58
59 private final boolean hasExcludedMethodPatterns;
60
61 public TestListResolver( Collection<String> tests )
62 {
63 final IncludedExcludedPatterns patterns = new IncludedExcludedPatterns();
64 final Set<ResolvedTest> includedFilters = new LinkedHashSet<ResolvedTest>( 0 );
65 final Set<ResolvedTest> excludedFilters = new LinkedHashSet<ResolvedTest>( 0 );
66 final Set<String> specificClasses = new LinkedHashSet<String>( 0 );
67
68 for ( final String csvTests : tests )
69 {
70 if ( StringUtils.isNotBlank( csvTests ) )
71 {
72 for ( String request : StringUtils.split( csvTests, "," ) )
73 {
74 request = request.trim();
75 if ( request.length() != 0 && !request.equals( "!" ) )
76 {
77 resolveTestRequest( request, patterns, includedFilters, excludedFilters );
78 }
79 }
80 }
81 }
82
83 for ( ResolvedTest test : includedFilters )
84 {
85 populateSpecificClasses( specificClasses, test );
86 }
87
88 for ( ResolvedTest test : excludedFilters )
89 {
90 populateSpecificClasses( specificClasses, test );
91 }
92
93 this.specificClasses = Collections.unmodifiableSet( specificClasses );
94 this.includedPatterns = Collections.unmodifiableSet( includedFilters );
95 this.excludedPatterns = Collections.unmodifiableSet( excludedFilters );
96 this.hasIncludedMethodPatterns = patterns.hasIncludedMethodPatterns;
97 this.hasExcludedMethodPatterns = patterns.hasExcludedMethodPatterns;
98 }
99
100 public TestListResolver( String csvTests )
101 {
102 this( csvTests == null ? Collections.<String>emptySet() : singleton( csvTests ) );
103 }
104
105 public TestListResolver( Collection<String> included, Collection<String> excluded )
106 {
107 this( mergeIncludedAndExcludedTests( included, excluded ) );
108 }
109
110
111
112
113 private TestListResolver( boolean hasIncludedMethodPatterns, boolean hasExcludedMethodPatterns,
114 Set<String> specificClasses, Set<ResolvedTest> includedPatterns,
115 Set<ResolvedTest> excludedPatterns )
116 {
117 this.includedPatterns = includedPatterns;
118 this.excludedPatterns = excludedPatterns;
119 this.specificClasses = specificClasses;
120 this.hasIncludedMethodPatterns = hasIncludedMethodPatterns;
121 this.hasExcludedMethodPatterns = hasExcludedMethodPatterns;
122 }
123
124 public boolean hasIncludedMethodPatterns()
125 {
126 return hasIncludedMethodPatterns;
127 }
128
129 public boolean hasExcludedMethodPatterns()
130 {
131 return hasExcludedMethodPatterns;
132 }
133
134 public boolean hasMethodPatterns()
135 {
136 return hasIncludedMethodPatterns() || hasExcludedMethodPatterns();
137 }
138
139
140
141
142 public TestListResolver createMethodFilters()
143 {
144 boolean hasMethodPatterns = hasMethodPatterns();
145 Set<ResolvedTest> inc = hasMethodPatterns ? getIncludedPatterns() : EMPTY_TEST_PATTERNS;
146 Set<ResolvedTest> exc = hasMethodPatterns ? getExcludedPatterns() : EMPTY_TEST_PATTERNS;
147 Set<String> specificClasses = hasMethodPatterns ? getTestSpecificClasses() : EMPTY_SPECIFIC_TESTS;
148 return new TestListResolver( hasIncludedMethodPatterns(), hasExcludedMethodPatterns(), specificClasses,
149 inc, exc );
150 }
151
152 public TestListResolver createClassFilters()
153 {
154 return hasMethodPatterns() ? new TestListResolver( "" ) : this;
155 }
156
157 public TestFilter<String, String> and( final TestListResolver another )
158 {
159 return new TestFilter<String, String>()
160 {
161 public boolean shouldRun( String testClass, String methodName )
162 {
163 return TestListResolver.this.shouldRun( testClass, methodName )
164 && another.shouldRun( testClass, methodName );
165 }
166 };
167 }
168
169 public TestFilter<String, String> or( final TestListResolver another )
170 {
171 return new TestFilter<String, String>()
172 {
173 public boolean shouldRun( String testClass, String methodName )
174 {
175 return TestListResolver.this.shouldRun( testClass, methodName )
176 || another.shouldRun( testClass, methodName );
177 }
178 };
179 }
180
181 public boolean shouldRun( Class<?> testClass, String methodName )
182 {
183 return shouldRun( toClassFileName( testClass ), methodName );
184 }
185
186 public boolean shouldRun( String testClassFile, String methodName )
187 {
188 if ( isEmpty() || StringUtils.isBlank( testClassFile ) && StringUtils.isBlank( methodName ) )
189 {
190 return true;
191 }
192 else
193 {
194 boolean shouldRun = false;
195
196 if ( getIncludedPatterns().isEmpty() )
197 {
198 shouldRun = true;
199 }
200 else
201 {
202 for ( ResolvedTest filter : getIncludedPatterns() )
203 {
204 if ( filter.shouldRun( testClassFile, methodName ) )
205 {
206 shouldRun = true;
207 break;
208 }
209 }
210 }
211
212 if ( shouldRun )
213 {
214 for ( ResolvedTest filter : getExcludedPatterns() )
215 {
216 if ( filter.shouldRun( testClassFile, methodName ) )
217 {
218 shouldRun = false;
219 break;
220 }
221 }
222 }
223 return shouldRun;
224 }
225 }
226
227 public boolean isEmpty()
228 {
229 return getIncludedPatterns().isEmpty() && getExcludedPatterns().isEmpty();
230 }
231
232 public String getPluginParameterTest()
233 {
234 String aggregatedTest = aggregatedTest( "", getIncludedPatterns() );
235 aggregatedTest += aggregatedTest( "!", getExcludedPatterns() );
236 return aggregatedTest.length() == 0 ? null : aggregatedTest;
237 }
238
239 public Set<ResolvedTest> getIncludedPatterns()
240 {
241 return includedPatterns;
242 }
243
244 public Set<ResolvedTest> getExcludedPatterns()
245 {
246 return excludedPatterns;
247 }
248
249 public Set<String> getTestSpecificClasses()
250 {
251 return specificClasses;
252 }
253
254 @Override
255 public boolean equals( Object o )
256 {
257 if ( this == o )
258 {
259 return true;
260 }
261 if ( o == null || getClass() != o.getClass() )
262 {
263 return false;
264 }
265
266 TestListResolver that = (TestListResolver) o;
267
268 return getIncludedPatterns().equals( that.getIncludedPatterns() )
269 && getExcludedPatterns().equals( that.getExcludedPatterns() );
270
271 }
272
273 @Override
274 public int hashCode()
275 {
276 int result = getIncludedPatterns().hashCode();
277 result = 31 * result + getExcludedPatterns().hashCode();
278 return result;
279 }
280
281 @Override
282 public String toString()
283 {
284 return getPluginParameterTest();
285 }
286
287 public static String toClassFileName( Class<?> test )
288 {
289 return test == null ? null : toClassFileName( test.getName() );
290 }
291
292 public static String toClassFileName( String fullyQualifiedTestClass )
293 {
294 return fullyQualifiedTestClass == null
295 ? null
296 : fullyQualifiedTestClass.replace( '.', '/' ) + JAVA_CLASS_FILE_EXTENSION;
297 }
298
299 static String removeExclamationMark( String s )
300 {
301 return s.length() != 0 && s.charAt( 0 ) == '!' ? s.substring( 1 ) : s;
302 }
303
304 private static void updatedFilters( boolean isExcluded, ResolvedTest test, IncludedExcludedPatterns patterns,
305 Collection<ResolvedTest> includedFilters,
306 Collection<ResolvedTest> excludedFilters )
307 {
308 if ( isExcluded )
309 {
310 excludedFilters.add( test );
311 patterns.hasExcludedMethodPatterns |= test.hasTestMethodPattern();
312 }
313 else
314 {
315 includedFilters.add( test );
316 patterns.hasIncludedMethodPatterns |= test.hasTestMethodPattern();
317 }
318 }
319
320 private static void populateSpecificClasses( Set<String> specificClasses, ResolvedTest test )
321 {
322 String pattern = test.getTestClassPattern();
323 if ( pattern != null )
324 {
325 if ( !test.isRegexTestClassPattern() && pattern.endsWith( JAVA_CLASS_FILE_EXTENSION ) )
326 {
327 pattern = pattern.substring( 0, pattern.length() - JAVA_CLASS_FILE_EXTENSION.length() );
328 }
329 specificClasses.add( pattern );
330 }
331 }
332
333 private static String aggregatedTest( String testPrefix, Set<ResolvedTest> tests )
334 {
335 String aggregatedTest = "";
336 for ( ResolvedTest test : tests )
337 {
338 String readableTest = test.toString();
339 if ( aggregatedTest.length() != 0 && readableTest != null )
340 {
341 aggregatedTest += ",";
342 }
343 aggregatedTest += testPrefix + readableTest;
344 }
345 return aggregatedTest;
346 }
347
348 private static Collection<String> mergeIncludedAndExcludedTests( Collection<String> included,
349 Collection<String> excluded )
350 {
351 ArrayList<String> incExc = new ArrayList<String>( included );
352 incExc.removeAll( Collections.<String>singleton( null ) );
353 for ( String exc : excluded )
354 {
355 if ( exc != null )
356 {
357 exc = exc.trim();
358 if ( exc.length() != 0 )
359 {
360 if ( exc.contains( "!" ) )
361 {
362 throw new IllegalArgumentException( "Exclamation mark not expected in 'exclusion': " + exc );
363 }
364 exc = exc.replace( ",", ",!" );
365 if ( !exc.startsWith( "!" ) )
366 {
367 exc = "!" + exc;
368 }
369 incExc.add( exc );
370 }
371 }
372 }
373 return incExc;
374 }
375
376 static boolean isRegexPrefixedPattern( String pattern )
377 {
378 int indexOfRegex = pattern.indexOf( SelectorUtils.REGEX_HANDLER_PREFIX );
379 int prefixLength = SelectorUtils.REGEX_HANDLER_PREFIX.length();
380 if ( indexOfRegex != -1 )
381 {
382 if ( indexOfRegex != 0
383 || !pattern.endsWith( SelectorUtils.PATTERN_HANDLER_SUFFIX )
384 || pattern.indexOf( SelectorUtils.REGEX_HANDLER_PREFIX, prefixLength ) != -1 )
385 {
386 String msg = "Illegal test|includes|excludes regex '%s'. Expected %%regex[class#method] "
387 + "or !%%regex[class#method] " + "with optional class or #method.";
388 throw new IllegalArgumentException( String.format( msg, pattern ) );
389 }
390 return true;
391 }
392 else
393 {
394 return false;
395 }
396 }
397
398 static String[] unwrapRegex( String regex )
399 {
400 regex = regex.trim();
401 int from = SelectorUtils.REGEX_HANDLER_PREFIX.length();
402 int to = regex.length() - SelectorUtils.PATTERN_HANDLER_SUFFIX.length();
403 return unwrap( regex.substring( from, to ) );
404 }
405
406 static String[] unwrap( String request )
407 {
408 String[] classAndMethod = new String[] { "", "" };
409 int indexOfHash = request.indexOf( '#' );
410 if ( indexOfHash == -1 )
411 {
412 classAndMethod[0] = request.trim();
413 }
414 else
415 {
416 classAndMethod[0] = request.substring( 0, indexOfHash ).trim();
417 classAndMethod[1] = request.substring( 1 + indexOfHash ).trim();
418 }
419 return classAndMethod;
420 }
421
422 static void nonRegexClassAndMethods( String clazz, String methods, boolean isExcluded,
423 IncludedExcludedPatterns patterns,
424 Collection<ResolvedTest> includedFilters, Collection<ResolvedTest> excludedFilters )
425 {
426 for ( String method : StringUtils.split( methods, "+" ) )
427 {
428 method = method.trim();
429 ResolvedTest test = new ResolvedTest( clazz, method, false );
430 if ( !test.isEmpty() )
431 {
432 updatedFilters( isExcluded, test, patterns, includedFilters, excludedFilters );
433 }
434 }
435 }
436
437
438
439
440 static void resolveTestRequest( String request, IncludedExcludedPatterns patterns,
441 Collection<ResolvedTest> includedFilters, Collection<ResolvedTest> excludedFilters )
442 {
443 final boolean isExcluded = request.startsWith( "!" );
444 ResolvedTest test = null;
445 request = removeExclamationMark( request );
446 if ( isRegexPrefixedPattern( request ) )
447 {
448 final String[] unwrapped = unwrapRegex( request );
449 final boolean hasClass = unwrapped[0].length() != 0;
450 final boolean hasMethod = unwrapped[1].length() != 0;
451 if ( hasClass && hasMethod )
452 {
453 test = new ResolvedTest( unwrapped[0], unwrapped[1], true );
454 }
455 else if ( hasClass )
456 {
457 test = new ResolvedTest( ResolvedTest.Type.CLASS, unwrapped[0], true );
458 }
459 else if ( hasMethod )
460 {
461 test = new ResolvedTest( ResolvedTest.Type.METHOD, unwrapped[1], true );
462 }
463 }
464 else
465 {
466 final int indexOfMethodSeparator = request.indexOf( '#' );
467 if ( indexOfMethodSeparator == -1 )
468 {
469 test = new ResolvedTest( ResolvedTest.Type.CLASS, request, false );
470 }
471 else
472 {
473 String clazz = request.substring( 0, indexOfMethodSeparator );
474 String methods = request.substring( 1 + indexOfMethodSeparator );
475 nonRegexClassAndMethods( clazz, methods, isExcluded, patterns, includedFilters, excludedFilters );
476 }
477 }
478
479 if ( test != null && !test.isEmpty() )
480 {
481 updatedFilters( isExcluded, test, patterns, includedFilters, excludedFilters );
482 }
483 }
484 }