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