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