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