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.util.ReflectionUtils.instantiate;
51 import static org.apache.maven.surefire.util.ReflectionUtils.tryLoadClass;
52 import static org.apache.maven.surefire.util.internal.ConcurrencyUtils.countDownToZero;
53
54
55
56
57
58
59
60 final class TestNGExecutor
61 {
62
63 private static final String DEFAULT_SUREFIRE_SUITE_NAME = "Surefire suite";
64
65
66 private static final String DEFAULT_SUREFIRE_TEST_NAME = "Surefire test";
67
68 private static final boolean HAS_TEST_ANNOTATION_ON_CLASSPATH =
69 tryLoadClass( TestNGExecutor.class.getClassLoader(), "org.testng.annotations.Test" ) != null;
70
71 private TestNGExecutor()
72 {
73 throw new IllegalStateException( "not instantiable constructor" );
74 }
75
76 static void run( Iterable<Class<?>> testClasses, String testSourceDirectory,
77 Map<String, String> options,
78 RunListener reportManager, 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, 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 static void run( List<String> suiteFiles, String testSourceDirectory,
271 Map<String, String> options,
272 RunListener reportManager, File reportsDirectory, int skipAfterFailureCount )
273 throws TestSetFailedException
274 {
275 TestNG testng = new TestNG( true );
276 Configurator configurator = getConfigurator( options.get( "testng.configurator" ) );
277 configurator.configure( testng, options );
278 postConfigure( testng, testSourceDirectory, reportManager, reportsDirectory, skipAfterFailureCount,
279 extractVerboseLevel( options ) );
280 testng.setTestSuites( suiteFiles );
281 testng.run();
282 }
283
284 private static Configurator getConfigurator( String className )
285 {
286 try
287 {
288 return (Configurator) Class.forName( className ).newInstance();
289 }
290 catch ( InstantiationException e )
291 {
292 throw new RuntimeException( e );
293 }
294 catch ( IllegalAccessException e )
295 {
296 throw new RuntimeException( e );
297 }
298 catch ( ClassNotFoundException e )
299 {
300 throw new RuntimeException( e );
301 }
302 }
303
304 private static void postConfigure( TestNG testNG, String sourcePath, final RunListener reportManager,
305 File reportsDirectory, int skipAfterFailureCount, int verboseLevel )
306 {
307
308 testNG.setVerbose( verboseLevel );
309
310 TestNGReporter reporter = createTestNGReporter( reportManager );
311 testNG.addListener( (Object) reporter );
312
313 if ( skipAfterFailureCount > 0 )
314 {
315 ClassLoader cl = Thread.currentThread().getContextClassLoader();
316 testNG.addListener( instantiate( cl, "org.apache.maven.surefire.testng.utils.FailFastNotifier",
317 Object.class ) );
318 testNG.addListener( new FailFastListener( createStoppable( reportManager, skipAfterFailureCount ) ) );
319 }
320
321
322 if ( sourcePath != null )
323 {
324 testNG.setSourcePath( sourcePath );
325 }
326
327 testNG.setOutputDirectory( reportsDirectory.getAbsolutePath() );
328 }
329
330 private static Stoppable createStoppable( final RunListener reportManager, int skipAfterFailureCount )
331 {
332 final AtomicInteger currentFaultCount = new AtomicInteger( skipAfterFailureCount );
333
334 return new Stoppable()
335 {
336 public void fireStopEvent()
337 {
338 if ( countDownToZero( currentFaultCount ) )
339 {
340 FailFastEventsSingleton.getInstance().setSkipOnNextTest();
341 }
342
343 reportManager.testExecutionSkippedByUser();
344 }
345 };
346 }
347
348
349
350 private static TestNGReporter createTestNGReporter( RunListener reportManager )
351 {
352 try
353 {
354 Class.forName( "org.testng.internal.IResultListener" );
355 Class c = Class.forName( "org.apache.maven.surefire.testng.ConfigurationAwareTestNGReporter" );
356 @SuppressWarnings( "unchecked" ) Constructor<?> ctor = c.getConstructor( RunListener.class );
357 return (TestNGReporter) ctor.newInstance( reportManager );
358 }
359 catch ( InvocationTargetException e )
360 {
361 throw new RuntimeException( "Bug in ConfigurationAwareTestNGReporter", e.getCause() );
362 }
363 catch ( ClassNotFoundException e )
364 {
365 return new TestNGReporter( reportManager );
366 }
367 catch ( Exception e )
368 {
369 throw new RuntimeException( "Bug in ConfigurationAwareTestNGReporter", e );
370 }
371 }
372
373 private static int extractVerboseLevel( Map<String, String> options )
374 throws TestSetFailedException
375 {
376 try
377 {
378 String verbose = options.get( "surefire.testng.verbose" );
379 return verbose == null ? 0 : Integer.parseInt( verbose );
380 }
381 catch ( NumberFormatException e )
382 {
383 throw new TestSetFailedException( "Provider property 'surefire.testng.verbose' should refer to "
384 + "number -1 (debug mode), 0, 1 .. 10 (most detailed).", e );
385 }
386 }
387 }