1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.surefire.testng;
20
21 import java.io.File;
22 import java.lang.annotation.Annotation;
23 import java.lang.reflect.Constructor;
24 import java.lang.reflect.Method;
25 import java.util.ArrayList;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.concurrent.atomic.AtomicInteger;
30
31 import org.apache.maven.surefire.api.booter.ProviderParameterNames;
32 import org.apache.maven.surefire.api.cli.CommandLineOption;
33 import org.apache.maven.surefire.api.report.RunListener;
34 import org.apache.maven.surefire.api.testset.TestListResolver;
35 import org.apache.maven.surefire.api.testset.TestSetFailedException;
36 import org.apache.maven.surefire.shared.utils.StringUtils;
37 import org.apache.maven.surefire.testng.conf.Configurator;
38 import org.apache.maven.surefire.testng.utils.FailFastEventsSingleton;
39 import org.apache.maven.surefire.testng.utils.FailFastListener;
40 import org.apache.maven.surefire.testng.utils.FailFastNotifier;
41 import org.apache.maven.surefire.testng.utils.Stoppable;
42 import org.testng.ITestNGListener;
43 import org.testng.TestNG;
44 import org.testng.annotations.Test;
45 import org.testng.xml.XmlClass;
46 import org.testng.xml.XmlMethodSelector;
47 import org.testng.xml.XmlSuite;
48 import org.testng.xml.XmlTest;
49
50 import static org.apache.maven.surefire.api.cli.CommandLineOption.LOGGING_LEVEL_DEBUG;
51 import static org.apache.maven.surefire.api.cli.CommandLineOption.SHOW_ERRORS;
52 import static org.apache.maven.surefire.api.util.ReflectionUtils.instantiate;
53 import static org.apache.maven.surefire.api.util.ReflectionUtils.invokeSetter;
54 import static org.apache.maven.surefire.api.util.ReflectionUtils.newInstance;
55 import static org.apache.maven.surefire.api.util.ReflectionUtils.tryGetConstructor;
56 import static org.apache.maven.surefire.api.util.ReflectionUtils.tryGetMethod;
57 import static org.apache.maven.surefire.api.util.ReflectionUtils.tryLoadClass;
58 import static org.apache.maven.surefire.api.util.internal.ConcurrencyUtils.runIfZeroCountDown;
59
60
61
62
63
64
65
66 final class TestNGExecutor {
67
68 private static final String DEFAULT_SUREFIRE_SUITE_NAME = "Surefire suite";
69
70
71 private static final String DEFAULT_SUREFIRE_TEST_NAME = "Surefire test";
72
73 private static final boolean HAS_TEST_ANNOTATION_ON_CLASSPATH =
74 tryLoadClass(TestNGExecutor.class.getClassLoader(), "org.testng.annotations.Test") != null;
75
76
77
78
79 private static final Method XML_CLASS_SET_INDEX = tryGetMethod(XmlClass.class, "setIndex", int.class);
80
81
82
83
84 private static final Constructor<XmlClass> XML_CLASS_CONSTRUCTOR_WITH_INDEX =
85 tryGetConstructor(XmlClass.class, String.class, boolean.class, int.class);
86
87 private TestNGExecutor() {
88 throw new IllegalStateException("not instantiable constructor");
89 }
90
91 @SuppressWarnings("checkstyle:parameternumbercheck")
92 static void run(
93 Iterable<Class<?>> testClasses,
94 String testSourceDirectory,
95 Map<String, String> options,
96 TestNGReporter testNGReporter,
97 File reportsDirectory,
98 TestListResolver methodFilter,
99 List<CommandLineOption> mainCliOptions,
100 int skipAfterFailureCount)
101 throws TestSetFailedException {
102 TestNG testng = new TestNG(true);
103
104 Configurator configurator = getConfigurator(options.get("testng.configurator"));
105
106 if (isCliDebugOrShowErrors(mainCliOptions)) {
107 System.out.println(
108 "Configuring TestNG with: " + configurator.getClass().getSimpleName());
109 }
110
111 XmlMethodSelector groupMatchingSelector = createGroupMatchingSelector(options);
112 XmlMethodSelector methodNameFilteringSelector = createMethodNameFilteringSelector(methodFilter);
113
114 Map<String, SuiteAndNamedTests> suitesNames = new HashMap<>();
115
116 List<XmlSuite> xmlSuites = new ArrayList<>();
117 for (Class<?> testClass : testClasses) {
118 TestMetadata metadata = findTestMetadata(testClass);
119
120 SuiteAndNamedTests suiteAndNamedTests = suitesNames.get(metadata.suiteName);
121 if (suiteAndNamedTests == null) {
122 suiteAndNamedTests = new SuiteAndNamedTests();
123 suiteAndNamedTests.xmlSuite.setName(metadata.suiteName);
124 configurator.configure(suiteAndNamedTests.xmlSuite, options);
125 xmlSuites.add(suiteAndNamedTests.xmlSuite);
126
127 suitesNames.put(metadata.suiteName, suiteAndNamedTests);
128 }
129
130 XmlTest xmlTest = suiteAndNamedTests.testNameToTest.get(metadata.testName);
131 if (xmlTest == null) {
132 xmlTest = new XmlTest(suiteAndNamedTests.xmlSuite);
133 xmlTest.setName(metadata.testName);
134 addSelector(xmlTest, groupMatchingSelector);
135 addSelector(xmlTest, methodNameFilteringSelector);
136 xmlTest.setXmlClasses(new ArrayList<>());
137
138 suiteAndNamedTests.testNameToTest.put(metadata.testName, xmlTest);
139 }
140
141 xmlTest.getXmlClasses()
142 .add(newXmlClassInstance(
143 testClass.getName(), xmlTest.getXmlClasses().size()));
144 }
145
146 testng.setXmlSuites(xmlSuites);
147 configurator.configure(testng, options);
148 postConfigure(
149 testng,
150 testSourceDirectory,
151 testNGReporter,
152 reportsDirectory,
153 skipAfterFailureCount,
154 extractVerboseLevel(options));
155 testng.run();
156 }
157
158 private static XmlClass newXmlClassInstance(String testClassName, int index) {
159
160
161
162
163
164
165
166
167
168 if (XML_CLASS_SET_INDEX != null) {
169 XmlClass xmlClass = new XmlClass(testClassName);
170 invokeSetter(xmlClass, XML_CLASS_SET_INDEX, index);
171 return xmlClass;
172 }
173 if (XML_CLASS_CONSTRUCTOR_WITH_INDEX != null) {
174 boolean loadClass = true;
175 return newInstance(XML_CLASS_CONSTRUCTOR_WITH_INDEX, testClassName, loadClass, index);
176 }
177 return new XmlClass(testClassName);
178 }
179
180 private static boolean isCliDebugOrShowErrors(List<CommandLineOption> mainCliOptions) {
181 return mainCliOptions.contains(LOGGING_LEVEL_DEBUG) || mainCliOptions.contains(SHOW_ERRORS);
182 }
183
184 private static TestMetadata findTestMetadata(Class<?> testClass) {
185 TestMetadata result = new TestMetadata();
186 if (HAS_TEST_ANNOTATION_ON_CLASSPATH) {
187 Test testAnnotation = findAnnotation(testClass, Test.class);
188 if (null != testAnnotation) {
189 if (!StringUtils.isBlank(testAnnotation.suiteName())) {
190 result.suiteName = testAnnotation.suiteName();
191 }
192
193 if (!StringUtils.isBlank(testAnnotation.testName())) {
194 result.testName = testAnnotation.testName();
195 }
196 }
197 }
198 return result;
199 }
200
201 private static <T extends Annotation> T findAnnotation(Class<?> clazz, Class<T> annotationType) {
202 if (clazz == null) {
203 return null;
204 }
205
206 T result = clazz.getAnnotation(annotationType);
207 if (result != null) {
208 return result;
209 }
210
211 return findAnnotation(clazz.getSuperclass(), annotationType);
212 }
213
214 private static class TestMetadata {
215 private String testName = DEFAULT_SUREFIRE_TEST_NAME;
216
217 private String suiteName = DEFAULT_SUREFIRE_SUITE_NAME;
218 }
219
220 private static class SuiteAndNamedTests {
221 private final XmlSuite xmlSuite = new XmlSuite();
222
223 private final Map<String, XmlTest> testNameToTest = new HashMap<>();
224 }
225
226 private static void addSelector(XmlTest xmlTest, XmlMethodSelector selector) {
227 if (selector != null) {
228 xmlTest.getMethodSelectors().add(selector);
229 }
230 }
231
232 @SuppressWarnings("checkstyle:magicnumber")
233 private static XmlMethodSelector createMethodNameFilteringSelector(TestListResolver methodFilter)
234 throws TestSetFailedException {
235 if (methodFilter != null && !methodFilter.isEmpty()) {
236
237 String clazzName = "org.apache.maven.surefire.testng.utils.MethodSelector";
238 try {
239 Class<?> clazz = Class.forName(clazzName);
240 Method method = clazz.getMethod("setTestListResolver", TestListResolver.class);
241 method.invoke(null, methodFilter);
242 } catch (Exception e) {
243 throw new TestSetFailedException(e.getMessage(), e);
244 }
245
246 XmlMethodSelector xms = new XmlMethodSelector();
247
248 xms.setName(clazzName);
249
250 xms.setPriority(10000);
251
252 return xms;
253 } else {
254 return null;
255 }
256 }
257
258 @SuppressWarnings("checkstyle:magicnumber")
259 private static XmlMethodSelector createGroupMatchingSelector(Map<String, String> options)
260 throws TestSetFailedException {
261 final String groups = options.get(ProviderParameterNames.TESTNG_GROUPS_PROP);
262 final String excludedGroups = options.get(ProviderParameterNames.TESTNG_EXCLUDEDGROUPS_PROP);
263
264 if (groups == null && excludedGroups == null) {
265 return null;
266 }
267
268
269 final String clazzName = "org.apache.maven.surefire.testng.utils.GroupMatcherMethodSelector";
270 try {
271 Class<?> clazz = Class.forName(clazzName);
272
273
274 Method method = clazz.getMethod("setGroups", String.class, String.class);
275 method.invoke(null, groups, excludedGroups);
276 } catch (Exception e) {
277 throw new TestSetFailedException(e.getMessage(), e);
278 }
279
280 XmlMethodSelector xms = new XmlMethodSelector();
281
282 xms.setName(clazzName);
283
284 xms.setPriority(9999);
285
286 return xms;
287 }
288
289 static void run(
290 List<String> suiteFiles,
291 String testSourceDirectory,
292 Map<String, String> options,
293 TestNGReporter testNGReporter,
294 File reportsDirectory,
295 int skipAfterFailureCount)
296 throws TestSetFailedException {
297 TestNG testng = new TestNG(true);
298 Configurator configurator = getConfigurator(options.get("testng.configurator"));
299 configurator.configure(testng, options);
300 postConfigure(
301 testng,
302 testSourceDirectory,
303 testNGReporter,
304 reportsDirectory,
305 skipAfterFailureCount,
306 extractVerboseLevel(options));
307 testng.setTestSuites(suiteFiles);
308 testng.run();
309 }
310
311 private static Configurator getConfigurator(String className) {
312 try {
313 return (Configurator) Class.forName(className).newInstance();
314 } catch (ReflectiveOperationException e) {
315 throw new RuntimeException(e);
316 }
317 }
318
319 private static void postConfigure(
320 TestNG testNG,
321 String sourcePath,
322 TestNGReporter testNGReporter,
323 File reportsDirectory,
324 int skipAfterFailureCount,
325 int verboseLevel) {
326
327 testNG.setVerbose(verboseLevel);
328 testNG.addListener((ITestNGListener) testNGReporter);
329
330 if (skipAfterFailureCount > 0) {
331 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
332 testNG.addListener(instantiate(classLoader, FailFastNotifier.class.getName(), Object.class));
333 testNG.addListener(
334 new FailFastListener(createStoppable(testNGReporter.getRunListener(), skipAfterFailureCount)));
335 }
336
337
338 if (sourcePath != null) {
339 testNG.setSourcePath(sourcePath);
340 }
341
342 testNG.setOutputDirectory(reportsDirectory.getAbsolutePath());
343 }
344
345 private static Stoppable createStoppable(final RunListener reportManager, int skipAfterFailureCount) {
346 final AtomicInteger currentFaultCount = new AtomicInteger(skipAfterFailureCount);
347
348 return () -> {
349 runIfZeroCountDown(() -> FailFastEventsSingleton.getInstance().setSkipOnNextTest(), currentFaultCount);
350 reportManager.testExecutionSkippedByUser();
351 };
352 }
353
354 private static int extractVerboseLevel(Map<String, String> options) throws TestSetFailedException {
355 try {
356 String verbose = options.get("surefire.testng.verbose");
357 return verbose == null ? 0 : Integer.parseInt(verbose);
358 } catch (NumberFormatException e) {
359 throw new TestSetFailedException(
360 "Provider property 'surefire.testng.verbose' should refer to "
361 + "number -1 (debug mode), 0, 1 .. 10 (most detailed).",
362 e);
363 }
364 }
365 }