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.report.Stoppable;
35  import org.apache.maven.surefire.api.testset.TestListResolver;
36  import org.apache.maven.surefire.api.testset.TestSetFailedException;
37  import org.apache.maven.surefire.shared.utils.StringUtils;
38  import org.apache.maven.surefire.testng.conf.Configurator;
39  import org.apache.maven.surefire.testng.utils.FailFastEventsSingleton;
40  import org.apache.maven.surefire.testng.utils.FailFastListener;
41  import org.apache.maven.surefire.testng.utils.FailFastNotifier;
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 }