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<String, SuiteAndNamedTests>();
99
100 List<XmlSuite> xmlSuites = new ArrayList<XmlSuite>();
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<String, XmlTest>();
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 ( InstantiationException e )
293 {
294 throw new RuntimeException( e );
295 }
296 catch ( IllegalAccessException e )
297 {
298 throw new RuntimeException( e );
299 }
300 catch ( ClassNotFoundException e )
301 {
302 throw new RuntimeException( e );
303 }
304 }
305
306 private static void postConfigure( TestNG testNG, String sourcePath, final RunListener reportManager,
307 File reportsDirectory, int skipAfterFailureCount, int verboseLevel )
308 {
309
310 testNG.setVerbose( verboseLevel );
311
312 TestNGReporter reporter = createTestNGReporter( reportManager );
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 )
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 = c.getConstructor( RunListener.class );
359 return (TestNGReporter) ctor.newInstance( reportManager );
360 }
361 catch ( InvocationTargetException e )
362 {
363 throw new RuntimeException( "Bug in ConfigurationAwareTestNGReporter", e.getCause() );
364 }
365 catch ( ClassNotFoundException e )
366 {
367 return new TestNGReporter( reportManager );
368 }
369 catch ( Exception e )
370 {
371 throw new RuntimeException( "Bug in ConfigurationAwareTestNGReporter", e );
372 }
373 }
374
375 private static int extractVerboseLevel( Map<String, String> options )
376 throws TestSetFailedException
377 {
378 try
379 {
380 String verbose = options.get( "surefire.testng.verbose" );
381 return verbose == null ? 0 : Integer.parseInt( verbose );
382 }
383 catch ( NumberFormatException e )
384 {
385 throw new TestSetFailedException( "Provider property 'surefire.testng.verbose' should refer to "
386 + "number -1 (debug mode), 0, 1 .. 10 (most detailed).", e );
387 }
388 }
389 }