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