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