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