View Javadoc
1   package org.apache.maven.surefire.testng;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.surefire.booter.ProviderParameterNames;
23  import org.apache.maven.surefire.report.RunListener;
24  import org.apache.maven.surefire.testng.conf.Configurator;
25  import org.apache.maven.surefire.testset.TestSetFailedException;
26  import org.apache.maven.surefire.util.ReflectionUtils;
27  import org.apache.maven.surefire.util.internal.StringUtils;
28  import org.testng.TestNG;
29  import org.testng.annotations.Test;
30  import org.testng.xml.XmlClass;
31  import org.testng.xml.XmlMethodSelector;
32  import org.testng.xml.XmlSuite;
33  import org.testng.xml.XmlTest;
34  
35  import java.io.File;
36  import java.lang.annotation.Annotation;
37  import java.lang.reflect.Constructor;
38  import java.lang.reflect.InvocationTargetException;
39  import java.lang.reflect.Method;
40  import java.util.ArrayList;
41  import java.util.HashMap;
42  import java.util.List;
43  import java.util.Map;
44  
45  /**
46   * Contains utility methods for executing TestNG.
47   *
48   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
49   * @author <a href='mailto:the[dot]mindstorm[at]gmail[dot]com'>Alex Popescu</a>
50   */
51  public class TestNGExecutor
52  {
53      /** The default name for a suite launched from the maven surefire plugin */
54      public static final String DEFAULT_SUREFIRE_SUITE_NAME = "Surefire suite";
55  
56      /** The default name for a test launched from the maven surefire plugin */
57      public static final String DEFAULT_SUREFIRE_TEST_NAME = "Surefire test";
58  
59      private static final boolean HAS_TEST_ANNOTATION_ON_CLASSPATH =
60          null != ReflectionUtils.tryLoadClass( TestNGExecutor.class.getClassLoader(), "org.testng.annotations.Test" );
61  
62      private TestNGExecutor()
63      {
64          // noop
65      }
66  
67      public static void run( Class[] testClasses, String testSourceDirectory, Map options, RunListener reportManager,
68                              TestNgTestSuite suite, File reportsDirectory, final String methodNamePattern )
69          throws TestSetFailedException
70      {
71          TestNG testng = new TestNG( true );
72  
73          Configurator configurator = getConfigurator( (String) options.get( "testng.configurator" ) );
74          System.out.println( "Configuring TestNG with: " + configurator.getClass().getSimpleName() );
75  
76          XmlMethodSelector groupMatchingSelector = getGroupMatchingSelector( options );
77          XmlMethodSelector methodNameFilteringSelector = getMethodNameFilteringSelector( methodNamePattern );
78  
79          Map<String, SuiteAndNamedTests> suitesNames = new HashMap<String, SuiteAndNamedTests>();
80  
81          List<XmlSuite> xmlSuites = new ArrayList<XmlSuite>();
82          for ( Class testClass : testClasses )
83          {
84              TestMetadata metadata = findTestMetadata( testClass );
85  
86              SuiteAndNamedTests suiteAndNamedTests = suitesNames.get( metadata.suiteName );
87              if ( suiteAndNamedTests == null )
88              {
89                  suiteAndNamedTests = new SuiteAndNamedTests();
90                  suiteAndNamedTests.xmlSuite.setName( metadata.suiteName );
91                  configurator.configure( suiteAndNamedTests.xmlSuite, options );
92                  xmlSuites.add( suiteAndNamedTests.xmlSuite );
93  
94                  suitesNames.put( metadata.suiteName, suiteAndNamedTests );
95              }
96  
97              XmlTest xmlTest = suiteAndNamedTests.testNameToTest.get( metadata.testName );
98              if ( xmlTest == null )
99              {
100                 xmlTest = new XmlTest( suiteAndNamedTests.xmlSuite );
101                 xmlTest.setName( metadata.testName );
102                 addSelector( xmlTest, groupMatchingSelector );
103                 addSelector( xmlTest, methodNameFilteringSelector );
104                 xmlTest.setXmlClasses( new ArrayList<XmlClass>() );
105 
106                 suiteAndNamedTests.testNameToTest.put( metadata.testName, xmlTest );
107             }
108 
109             xmlTest.getXmlClasses().add( new XmlClass( testClass.getName() ) );
110         }
111 
112         testng.setXmlSuites( xmlSuites );
113         configurator.configure( testng, options );
114         postConfigure( testng, testSourceDirectory, reportManager, suite, reportsDirectory );
115         testng.run();
116     }
117 
118     private static TestMetadata findTestMetadata( Class testClass )
119     {
120         TestMetadata result = new TestMetadata();
121         if ( HAS_TEST_ANNOTATION_ON_CLASSPATH )
122         {
123             Test testAnnotation = findAnnotation( testClass, Test.class );
124             if ( null != testAnnotation )
125             {
126                 if ( !StringUtils.isBlank( testAnnotation.suiteName() ) )
127                 {
128                     result.suiteName = testAnnotation.suiteName();
129                 }
130 
131                 if ( !StringUtils.isBlank( testAnnotation.testName() ) )
132                 {
133                     result.testName = testAnnotation.testName();
134                 }
135             }
136         }
137         return result;
138     }
139 
140     private static <T extends Annotation> T findAnnotation( Class<?> clazz, Class<T> annotationType )
141     {
142         if ( clazz == null )
143         {
144             return null;
145         }
146 
147         T result = clazz.getAnnotation( annotationType );
148         if ( result != null )
149         {
150             return result;
151         }
152 
153         return findAnnotation( clazz.getSuperclass(), annotationType );
154     }
155 
156     private static class TestMetadata
157     {
158         private String testName = DEFAULT_SUREFIRE_TEST_NAME;
159 
160         private String suiteName = DEFAULT_SUREFIRE_SUITE_NAME;
161     }
162 
163     private static class SuiteAndNamedTests
164     {
165         private XmlSuite xmlSuite = new XmlSuite();
166 
167         private Map<String, XmlTest> testNameToTest = new HashMap<String, XmlTest>();
168     }
169 
170     private static void addSelector( XmlTest xmlTest, XmlMethodSelector selector )
171     {
172         if ( selector != null )
173         {
174             xmlTest.getMethodSelectors().add( selector );
175         }
176     }
177 
178     @SuppressWarnings( "checkstyle:magicnumber" )
179     private static XmlMethodSelector getMethodNameFilteringSelector( String methodNamePattern )
180         throws TestSetFailedException
181     {
182         if ( StringUtils.isBlank( methodNamePattern ) )
183         {
184             return null;
185         }
186 
187         // the class is available in the testClassPath
188         String clazzName = "org.apache.maven.surefire.testng.utils.MethodSelector";
189         try
190         {
191             Class clazz = Class.forName( clazzName );
192 
193             Method method = clazz.getMethod( "setMethodName", new Class[] { String.class } );
194             method.invoke( null, methodNamePattern );
195         }
196         catch ( ClassNotFoundException e )
197         {
198             throw new TestSetFailedException( e.getMessage(), e );
199         }
200         catch ( SecurityException e )
201         {
202             throw new TestSetFailedException( e.getMessage(), e );
203         }
204         catch ( NoSuchMethodException e )
205         {
206             throw new TestSetFailedException( e.getMessage(), e );
207         }
208         catch ( IllegalArgumentException e )
209         {
210             throw new TestSetFailedException( e.getMessage(), e );
211         }
212         catch ( IllegalAccessException e )
213         {
214             throw new TestSetFailedException( e.getMessage(), e );
215         }
216         catch ( InvocationTargetException e )
217         {
218             throw new TestSetFailedException( e.getMessage(), e );
219         }
220 
221         XmlMethodSelector xms = new XmlMethodSelector();
222 
223         xms.setName( clazzName );
224         // looks to need a high value
225         xms.setPriority( 10000 );
226 
227         return xms;
228     }
229 
230     @SuppressWarnings( "checkstyle:magicnumber" )
231     private static XmlMethodSelector getGroupMatchingSelector( Map options )
232         throws TestSetFailedException
233     {
234         String groups = (String) options.get( ProviderParameterNames.TESTNG_GROUPS_PROP );
235         String excludedGroups = (String) options.get( ProviderParameterNames.TESTNG_EXCLUDEDGROUPS_PROP );
236 
237         if ( groups == null && excludedGroups == null )
238         {
239             return null;
240         }
241 
242         // the class is available in the testClassPath
243         String clazzName = "org.apache.maven.surefire.testng.utils.GroupMatcherMethodSelector";
244         try
245         {
246             Class clazz = Class.forName( clazzName );
247 
248             // HORRIBLE hack, but TNG doesn't allow us to setup a method selector instance directly.
249             Method method = clazz.getMethod( "setGroups", new Class[] { String.class, String.class } );
250             method.invoke( null, groups, excludedGroups );
251         }
252         catch ( ClassNotFoundException e )
253         {
254             throw new TestSetFailedException( e.getMessage(), e );
255         }
256         catch ( SecurityException e )
257         {
258             throw new TestSetFailedException( e.getMessage(), e );
259         }
260         catch ( NoSuchMethodException e )
261         {
262             throw new TestSetFailedException( e.getMessage(), e );
263         }
264         catch ( IllegalArgumentException e )
265         {
266             throw new TestSetFailedException( e.getMessage(), e );
267         }
268         catch ( IllegalAccessException e )
269         {
270             throw new TestSetFailedException( e.getMessage(), e );
271         }
272         catch ( InvocationTargetException e )
273         {
274             throw new TestSetFailedException( e.getMessage(), e );
275         }
276 
277         XmlMethodSelector xms = new XmlMethodSelector();
278 
279         xms.setName( clazzName );
280         // looks to need a high value
281         xms.setPriority( 9999 );
282 
283         return xms;
284     }
285 
286     public static void run( List<String> suiteFiles, String testSourceDirectory, Map options,
287                             RunListener reportManager, TestNgTestSuite suite, File reportsDirectory )
288         throws TestSetFailedException
289     {
290         TestNG testng = new TestNG( true );
291         Configurator configurator = getConfigurator( (String) options.get( "testng.configurator" ) );
292         configurator.configure( testng, options );
293         postConfigure( testng, testSourceDirectory, reportManager, suite, reportsDirectory );
294         testng.setTestSuites( suiteFiles );
295         testng.run();
296     }
297 
298     private static Configurator getConfigurator( String className )
299     {
300         try
301         {
302             return (Configurator) Class.forName( className ).newInstance();
303         }
304         catch ( InstantiationException e )
305         {
306             throw new RuntimeException( e );
307         }
308         catch ( IllegalAccessException e )
309         {
310             throw new RuntimeException( e );
311         }
312         catch ( ClassNotFoundException e )
313         {
314             throw new RuntimeException( e );
315         }
316     }
317 
318     private static void postConfigure( TestNG testNG, String sourcePath, RunListener reportManager,
319                                        TestNgTestSuite suite, File reportsDirectory )
320         throws TestSetFailedException
321     {
322         // turn off all TestNG output
323         testNG.setVerbose( 0 );
324 
325         TestNGReporter reporter = createTestNGReporter( reportManager, suite );
326         testNG.addListener( (Object) reporter );
327 
328         // FIXME: use classifier to decide if we need to pass along the source dir (onyl for JDK14)
329         if ( sourcePath != null )
330         {
331             testNG.setSourcePath( sourcePath );
332         }
333 
334         testNG.setOutputDirectory( reportsDirectory.getAbsolutePath() );
335     }
336 
337     // If we have access to IResultListener, return a ConfigurationAwareTestNGReporter
338     // But don't cause NoClassDefFoundErrors if it isn't available; just return a regular TestNGReporter instead
339     private static TestNGReporter createTestNGReporter( RunListener reportManager, TestNgTestSuite suite )
340     {
341         try
342         {
343             Class.forName( "org.testng.internal.IResultListener" );
344             Class c = Class.forName( "org.apache.maven.surefire.testng.ConfigurationAwareTestNGReporter" );
345             try
346             {
347                 Constructor ctor = c.getConstructor( new Class[] { RunListener.class, TestNgTestSuite.class } );
348                 return (TestNGReporter) ctor.newInstance( reportManager, suite );
349             }
350             catch ( Exception e )
351             {
352                 throw new RuntimeException( "Bug in ConfigurationAwareTestNGReporter", e );
353             }
354         }
355         catch ( ClassNotFoundException e )
356         {
357             return new TestNGReporter( reportManager );
358         }
359     }
360 
361 }