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