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