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