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