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