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