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 java.io.File;
23  import java.lang.annotation.Annotation;
24  import java.lang.reflect.Method;
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.SortedMap;
31  import java.util.TreeMap;
32  
33  import org.apache.maven.surefire.NonAbstractClassFilter;
34  import org.apache.maven.surefire.cli.CommandLineOption;
35  import org.apache.maven.surefire.report.ConsoleOutputCapture;
36  import org.apache.maven.surefire.report.ConsoleOutputReceiver;
37  import org.apache.maven.surefire.report.ReportEntry;
38  import org.apache.maven.surefire.report.ReporterException;
39  import org.apache.maven.surefire.report.ReporterFactory;
40  import org.apache.maven.surefire.report.RunListener;
41  import org.apache.maven.surefire.report.SimpleReportEntry;
42  import org.apache.maven.surefire.testset.TestListResolver;
43  import org.apache.maven.surefire.testset.TestSetFailedException;
44  import org.apache.maven.surefire.util.RunOrderCalculator;
45  import org.apache.maven.surefire.util.ScanResult;
46  import org.apache.maven.surefire.util.TestsToRun;
47  
48  /**
49   * Test suite for TestNG based on a directory of Java test classes. Can also execute JUnit tests.
50   *
51   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
52   * @author <a href='mailto:the[dot]mindstorm[at]gmail[dot]com'>Alex Popescu</a>
53   */
54  public class TestNGDirectoryTestSuite
55      implements TestNgTestSuite
56  {
57  
58      private final Map<String, String> options;
59  
60      private final Map<String, String> junitOptions;
61  
62      private final String testSourceDirectory;
63  
64      private final File reportsDirectory;
65  
66      private SortedMap<String, TestNGTestSet> testSets;
67  
68      private final ScanResult scanResult;
69  
70      private final TestListResolver methodFilter;
71  
72      private final RunOrderCalculator runOrderCalculator;
73  
74      private final Class<?> junitTestClass;
75  
76      private final Class<? extends Annotation> junitRunWithAnnotation;
77  
78      private final Class<? extends Annotation> junitTestAnnotation;
79  
80      private final List<CommandLineOption> mainCliOptions;
81  
82      private final int skipAfterFailureCount;
83  
84      public TestNGDirectoryTestSuite( String testSourceDirectory, Map<String, String> confOptions, File reportsDirectory,
85                                       TestListResolver methodFilter, RunOrderCalculator runOrderCalculator,
86                                       ScanResult scanResult, List<CommandLineOption> mainCliOptions,
87                                       int skipAfterFailureCount )
88      {
89          this.runOrderCalculator = runOrderCalculator;
90          this.options = confOptions;
91          this.testSourceDirectory = testSourceDirectory;
92          this.reportsDirectory = reportsDirectory;
93          this.scanResult = scanResult;
94          this.methodFilter = methodFilter;
95          this.junitTestClass = findJUnitTestClass();
96          this.junitRunWithAnnotation = findJUnitRunWithAnnotation();
97          this.junitTestAnnotation = findJUnitTestAnnotation();
98          this.junitOptions = createJUnitOptions();
99          this.mainCliOptions = mainCliOptions;
100         this.skipAfterFailureCount = skipAfterFailureCount;
101     }
102 
103     public void execute( TestsToRun testsToRun, ReporterFactory reporterManagerFactory )
104         throws TestSetFailedException
105     {
106 
107         if ( !testsToRun.allowEagerReading() )
108         {
109             executeLazy( testsToRun, reporterManagerFactory );
110         }
111         else if ( testsToRun.containsAtLeast( 2 ) )
112         {
113             executeMulti( testsToRun, reporterManagerFactory );
114         }
115         else if ( testsToRun.containsAtLeast( 1 ) )
116         {
117             Class<?> testClass = testsToRun.iterator().next();
118             executeSingleClass( reporterManagerFactory, testClass );
119         }
120     }
121 
122     private void executeSingleClass( ReporterFactory reporterManagerFactory, Class<?> testClass )
123         throws TestSetFailedException
124     {
125         options.put( "suitename", testClass.getName() );
126 
127         RunListener reporter = reporterManagerFactory.createReporter();
128         ConsoleOutputCapture.startCapture( (ConsoleOutputReceiver) reporter );
129 
130         startTestSuite( reporter, this );
131 
132         final Map<String, String> optionsToUse = isJUnitTest( testClass ) ? junitOptions : options;
133 
134         TestNGExecutor.run( new Class<?>[]{ testClass }, testSourceDirectory, optionsToUse, reporter, this,
135                             reportsDirectory, methodFilter, mainCliOptions, skipAfterFailureCount );
136 
137         finishTestSuite( reporter, this );
138     }
139 
140     public void executeLazy( TestsToRun testsToRun, ReporterFactory reporterFactory )
141         throws TestSetFailedException
142     {
143         for ( Class<?> c : testsToRun )
144         {
145             executeSingleClass( reporterFactory, c );
146         }
147     }
148 
149     private Class<?> findJUnitTestClass()
150     {
151         return lookupClass( "junit.framework.Test" );
152     }
153 
154     private Class<Annotation> findJUnitRunWithAnnotation()
155     {
156         return lookupAnnotation( "org.junit.runner.RunWith" );
157     }
158 
159     private Class<Annotation> findJUnitTestAnnotation()
160     {
161         return lookupAnnotation( "org.junit.Test" );
162     }
163 
164     @SuppressWarnings( "unchecked" )
165     private static Class<Annotation> lookupAnnotation( String className )
166     {
167         Class<Annotation> junitClass;
168         try
169         {
170             junitClass = (Class<Annotation>) Class.forName( className );
171         }
172         catch ( ClassNotFoundException e )
173         {
174             junitClass = null;
175         }
176         return junitClass;
177     }
178 
179     private static Class<?> lookupClass( String className )
180     {
181         Class<?> junitClass;
182         try
183         {
184             junitClass = Class.forName( className );
185         }
186         catch ( ClassNotFoundException e )
187         {
188             junitClass = null;
189         }
190         return junitClass;
191     }
192 
193     public void executeMulti( TestsToRun testsToRun, ReporterFactory reporterFactory )
194         throws TestSetFailedException
195     {
196         List<Class<?>> testNgTestClasses = new ArrayList<Class<?>>();
197         List<Class<?>> junitTestClasses = new ArrayList<Class<?>>();
198         for ( Class<?> c : testsToRun )
199         {
200             if ( isJUnitTest( c ) )
201             {
202                 junitTestClasses.add( c );
203             }
204             else
205             {
206                 testNgTestClasses.add( c );
207             }
208         }
209 
210         File testNgReportsDirectory = reportsDirectory, junitReportsDirectory = reportsDirectory;
211 
212         if ( !junitTestClasses.isEmpty() && !testNgTestClasses.isEmpty() )
213         {
214             testNgReportsDirectory = new File( reportsDirectory, "testng-native-results" );
215             junitReportsDirectory = new File( reportsDirectory, "testng-junit-results" );
216         }
217 
218         RunListener reporterManager = reporterFactory.createReporter();
219         ConsoleOutputCapture.startCapture( (ConsoleOutputReceiver) reporterManager );
220         startTestSuite( reporterManager, this );
221 
222         Class<?>[] testClasses = testNgTestClasses.toArray( new Class<?>[testNgTestClasses.size()] );
223 
224         TestNGExecutor.run( testClasses, testSourceDirectory, options, reporterManager, this,
225                             testNgReportsDirectory, methodFilter, mainCliOptions, skipAfterFailureCount );
226 
227         if ( !junitTestClasses.isEmpty() )
228         {
229             testClasses = junitTestClasses.toArray( new Class[junitTestClasses.size()] );
230 
231             TestNGExecutor.run( testClasses, testSourceDirectory, junitOptions, reporterManager, this,
232                                 junitReportsDirectory, methodFilter, mainCliOptions, skipAfterFailureCount );
233         }
234 
235         finishTestSuite( reporterManager, this );
236     }
237 
238     private boolean isJUnitTest( Class<?> c )
239     {
240         return isJunit3Test( c ) || isJunit4Test( c );
241     }
242 
243     private boolean isJunit4Test( Class<?> c )
244     {
245         return hasJunit4RunWithAnnotation( c ) || hasJunit4TestAnnotation( c );
246     }
247 
248     private boolean hasJunit4RunWithAnnotation( Class<?> c )
249     {
250         return junitRunWithAnnotation != null && c.getAnnotation( junitRunWithAnnotation ) != null;
251     }
252 
253     private boolean hasJunit4TestAnnotation( Class<?> c )
254     {
255         if ( junitTestAnnotation != null )
256         {
257             for ( Method m : c.getMethods() )
258             {
259                 if ( m.getAnnotation( junitTestAnnotation ) != null )
260                 {
261                     return true;
262                 }
263             }
264         }
265 
266         return false;
267     }
268 
269     private boolean isJunit3Test( Class<?> c )
270     {
271         return junitTestClass != null && junitTestClass.isAssignableFrom( c );
272     }
273 
274     private Map<String, String> createJUnitOptions()
275     {
276         Map<String, String> junitOptions = new HashMap<String, String>( this.options );
277         junitOptions.put( "junit", "true" );
278         return junitOptions;
279     }
280 
281     // single class test
282     public void execute( String testSetName, ReporterFactory reporterManagerFactory )
283         throws TestSetFailedException
284     {
285         if ( testSets == null )
286         {
287             throw new IllegalStateException( "You must call locateTestSets before calling execute" );
288         }
289         TestNGTestSet testSet = testSets.get( testSetName );
290 
291         if ( testSet == null )
292         {
293             throw new TestSetFailedException( "Unable to find test set '" + testSetName + "' in suite" );
294         }
295 
296         RunListener reporter = reporterManagerFactory.createReporter();
297         ConsoleOutputCapture.startCapture( (ConsoleOutputReceiver) reporter );
298 
299         startTestSuite( reporter, this );
300 
301         TestNGExecutor.run( new Class<?>[] { testSet.getTestClass() }, testSourceDirectory, options, reporter,
302                             this, reportsDirectory, methodFilter, mainCliOptions, skipAfterFailureCount );
303 
304         finishTestSuite( reporter, this );
305     }
306 
307     public static void startTestSuite( RunListener reporter, Object suite )
308     {
309         ReportEntry report = new SimpleReportEntry( suite.getClass().getName(), getSuiteName( suite ) );
310 
311         try
312         {
313             reporter.testSetStarting( report );
314         }
315         catch ( ReporterException e )
316         {
317             // TODO: remove this exception from the report manager
318         }
319     }
320 
321     public static void finishTestSuite( RunListener reporterManager, Object suite )
322     {
323         ReportEntry report = new SimpleReportEntry( suite.getClass().getName(), getSuiteName( suite ) );
324 
325         reporterManager.testSetCompleted( report );
326     }
327 
328     public String getSuiteName()
329     {
330         String result = options.get( "suitename" );
331         return result == null ? "TestSuite" : result;
332     }
333 
334     private static String getSuiteName( Object suite )
335     {
336         String result = "TestSuite";
337         if ( suite instanceof TestNGDirectoryTestSuite )
338         {
339             result = ( (TestNGDirectoryTestSuite) suite ).getSuiteName();
340         }
341         else if ( suite instanceof TestNGXmlTestSuite )
342         {
343             result = ( (TestNGXmlTestSuite) suite ).getSuiteName();
344         }
345 
346         return result;
347     }
348 
349     public Map locateTestSets( ClassLoader classLoader )
350         throws TestSetFailedException
351     {
352         if ( testSets != null )
353         {
354             throw new IllegalStateException( "You can't call locateTestSets twice" );
355         }
356         testSets = new TreeMap<String, TestNGTestSet>();
357 
358         final TestsToRun scanned = scanResult.applyFilter( new NonAbstractClassFilter(), classLoader );
359 
360         final TestsToRun testsToRun = runOrderCalculator.orderTestClasses( scanned );
361 
362         for ( Class<?> testClass : testsToRun )
363         {
364             TestNGTestSet testSet = new TestNGTestSet( testClass );
365 
366             if ( testSets.containsKey( testSet.getName() ) )
367             {
368                 throw new TestSetFailedException( "Duplicate test set '" + testSet.getName() + "'" );
369             }
370             testSets.put( testSet.getName(), testSet );
371         }
372 
373         return Collections.unmodifiableSortedMap( testSets );
374     }
375 
376 }