View Javadoc
1   package org.apache.maven.surefire.junit4;
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.lang.reflect.Method;
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Set;
28  
29  import org.apache.maven.shared.utils.io.SelectorUtils;
30  import org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil;
31  import org.apache.maven.surefire.common.junit4.JUnit4RunListener;
32  import org.apache.maven.surefire.common.junit4.JUnit4RunListenerFactory;
33  import org.apache.maven.surefire.common.junit4.JUnit4TestChecker;
34  import org.apache.maven.surefire.common.junit4.JUnitTestFailureListener;
35  import org.apache.maven.surefire.providerapi.AbstractProvider;
36  import org.apache.maven.surefire.providerapi.ProviderParameters;
37  import org.apache.maven.surefire.report.ConsoleOutputCapture;
38  import org.apache.maven.surefire.report.ConsoleOutputReceiver;
39  import org.apache.maven.surefire.report.PojoStackTraceWriter;
40  import org.apache.maven.surefire.report.ReportEntry;
41  import org.apache.maven.surefire.report.ReporterFactory;
42  import org.apache.maven.surefire.report.RunListener;
43  import org.apache.maven.surefire.report.SimpleReportEntry;
44  import org.apache.maven.surefire.suite.RunResult;
45  import org.apache.maven.surefire.testset.TestSetFailedException;
46  import org.apache.maven.surefire.util.RunOrderCalculator;
47  import org.apache.maven.surefire.util.ScanResult;
48  import org.apache.maven.surefire.util.TestsToRun;
49  import org.apache.maven.surefire.util.internal.StringUtils;
50  import org.junit.runner.Description;
51  import org.junit.runner.Request;
52  import org.junit.runner.Result;
53  import org.junit.runner.notification.RunNotifier;
54  
55  /**
56   * @author Kristian Rosenvold
57   */
58  public class JUnit4Provider
59      extends AbstractProvider
60  {
61      private final ClassLoader testClassLoader;
62  
63      private final List<org.junit.runner.notification.RunListener> customRunListeners;
64  
65      private final JUnit4TestChecker jUnit4TestChecker;
66  
67      private final String requestedTestMethod;
68  
69      private final ProviderParameters providerParameters;
70  
71      private final RunOrderCalculator runOrderCalculator;
72  
73      private final ScanResult scanResult;
74  
75      private final int rerunFailingTestsCount;
76  
77      private TestsToRun testsToRun;
78  
79      public JUnit4Provider( ProviderParameters booterParameters )
80      {
81          providerParameters = booterParameters;
82          testClassLoader = booterParameters.getTestClassLoader();
83          scanResult = booterParameters.getScanResult();
84          runOrderCalculator = booterParameters.getRunOrderCalculator();
85          customRunListeners = JUnit4RunListenerFactory.
86              createCustomListeners( booterParameters.getProviderProperties().getProperty( "listener" ) );
87          jUnit4TestChecker = new JUnit4TestChecker( testClassLoader );
88          requestedTestMethod = booterParameters.getTestRequest().getRequestedTestMethod();
89          rerunFailingTestsCount = booterParameters.getTestRequest().getRerunFailingTestsCount();
90      }
91  
92      public RunResult invoke( Object forkTestSet )
93          throws TestSetFailedException
94      {
95          if ( testsToRun == null )
96          {
97              if ( forkTestSet instanceof TestsToRun )
98              {
99                  testsToRun = (TestsToRun) forkTestSet;
100             }
101             else if ( forkTestSet instanceof Class )
102             {
103                 testsToRun = TestsToRun.fromClass( (Class) forkTestSet );
104             }
105             else
106             {
107                 testsToRun = scanClassPath();
108             }
109         }
110 
111         upgradeCheck();
112 
113         final ReporterFactory reporterFactory = providerParameters.getReporterFactory();
114 
115         RunListener reporter = reporterFactory.createReporter();
116 
117         ConsoleOutputCapture.startCapture( (ConsoleOutputReceiver) reporter );
118 
119         JUnit4RunListener jUnit4TestSetReporter = new JUnit4RunListener( reporter );
120 
121         Result result = new Result();
122         RunNotifier runNotifier = getRunNotifier( jUnit4TestSetReporter, result, customRunListeners );
123 
124         runNotifier.fireTestRunStarted( createTestsDescription() );
125 
126         for ( Class aTestsToRun : testsToRun )
127         {
128             executeTestSet( aTestsToRun, reporter, runNotifier );
129         }
130 
131         runNotifier.fireTestRunFinished( result );
132 
133         JUnit4RunListener.rethrowAnyTestMechanismFailures( result );
134 
135         closeRunNotifier( jUnit4TestSetReporter, customRunListeners );
136         return reporterFactory.close();
137     }
138 
139     private void executeTestSet( Class<?> clazz, RunListener reporter, RunNotifier listeners )
140     {
141         final ReportEntry report = new SimpleReportEntry( getClass().getName(), clazz.getName() );
142         reporter.testSetStarting( report );
143         try
144         {
145             if ( !StringUtils.isBlank( requestedTestMethod ) )
146             {
147                 String actualTestMethod = getMethod( clazz, requestedTestMethod );
148                 String[] testMethods = StringUtils.split( actualTestMethod, "+" );
149                 executeWithRerun( clazz, listeners, testMethods );
150             }
151             else
152             {
153                 executeWithRerun( clazz, listeners, null );
154             }
155         }
156         catch ( Throwable e )
157         {
158             reporter.testError( SimpleReportEntry.withException( report.getSourceName(), report.getName(),
159                                                                  new PojoStackTraceWriter( report.getSourceName(),
160                                                                                            report.getName(), e ) ) );
161         }
162         finally
163         {
164             reporter.testSetCompleted( report );
165         }
166     }
167 
168     private void executeWithRerun( Class<?> clazz, RunNotifier listeners, String[] testMethods )
169     {
170         JUnitTestFailureListener failureListener = new JUnitTestFailureListener();
171         listeners.addListener( failureListener );
172 
173         execute( clazz, listeners, testMethods );
174 
175         // Rerun failing tests if rerunFailingTestsCount is larger than 0
176         if ( rerunFailingTestsCount > 0 )
177         {
178             for ( int i = 0; i < rerunFailingTestsCount && !failureListener.getAllFailures().isEmpty(); i++ )
179             {
180                 Set<String> methodsSet = JUnit4ProviderUtil.generateFailingTests( failureListener.getAllFailures() );
181                 String[] methods = methodsSet.toArray( new String[ methodsSet.size() ] );
182                 failureListener.reset();
183                 execute( clazz, listeners, methods );
184             }
185         }
186     }
187 
188     private RunNotifier getRunNotifier( org.junit.runner.notification.RunListener main, Result result,
189                                         List<org.junit.runner.notification.RunListener> others )
190     {
191         RunNotifier fNotifier = new RunNotifier();
192         fNotifier.addListener( main );
193         fNotifier.addListener( result.createListener() );
194         for ( org.junit.runner.notification.RunListener listener : others )
195         {
196             fNotifier.addListener( listener );
197         }
198         return fNotifier;
199     }
200 
201     // I am not entirely sure as to why we do this explicit freeing, it's one of those
202     // pieces of code that just seem to linger on in here ;)
203     private void closeRunNotifier( org.junit.runner.notification.RunListener main,
204                                    List<org.junit.runner.notification.RunListener> others )
205     {
206         RunNotifier fNotifier = new RunNotifier();
207         fNotifier.removeListener( main );
208         for ( org.junit.runner.notification.RunListener listener : others )
209         {
210             fNotifier.removeListener( listener );
211         }
212     }
213 
214     public Iterator<?> getSuites()
215     {
216         testsToRun = scanClassPath();
217         return testsToRun.iterator();
218     }
219 
220     private TestsToRun scanClassPath()
221     {
222         final TestsToRun scannedClasses = scanResult.applyFilter( jUnit4TestChecker, testClassLoader );
223         return runOrderCalculator.orderTestClasses( scannedClasses );
224     }
225 
226     @SuppressWarnings( "unchecked" )
227     private void upgradeCheck()
228         throws TestSetFailedException
229     {
230         if ( isJUnit4UpgradeCheck() )
231         {
232             List<Class> classesSkippedByValidation =
233                 scanResult.getClassesSkippedByValidation( jUnit4TestChecker, testClassLoader );
234             if ( !classesSkippedByValidation.isEmpty() )
235             {
236                 StringBuilder reason = new StringBuilder();
237                 reason.append( "Updated check failed\n" );
238                 reason.append( "There are tests that would be run with junit4 / surefire 2.6 but not with [2.7,):\n" );
239                 for ( Class testClass : classesSkippedByValidation )
240                 {
241                     reason.append( "   " );
242                     reason.append( testClass.getName() );
243                     reason.append( "\n" );
244                 }
245                 throw new TestSetFailedException( reason.toString() );
246             }
247         }
248     }
249 
250     private Description createTestsDescription()
251     {
252         Collection<Class<?>> classes = new ArrayList<Class<?>>();
253         for ( Class<?> clazz : testsToRun )
254         {
255             classes.add( clazz );
256         }
257         return JUnit4ProviderUtil.createSuiteDescription( classes );
258     }
259 
260     private static boolean isJUnit4UpgradeCheck()
261     {
262         return System.getProperty( "surefire.junit4.upgradecheck" ) != null;
263     }
264 
265     private static void execute( Class<?> testClass, RunNotifier fNotifier, String[] testMethods )
266     {
267         if ( testMethods != null )
268         {
269             for ( final Method method : testClass.getMethods() )
270             {
271                 for ( final String testMethod : testMethods )
272                 {
273                     if ( SelectorUtils.match( testMethod, method.getName() ) )
274                     {
275                         Request.method( testClass, method.getName() ).getRunner().run( fNotifier );
276                     }
277 
278                 }
279             }
280         }
281         else
282         {
283             Request.aClass( testClass ).getRunner().run( fNotifier );
284         }
285     }
286 
287     /**
288      * this method retrive testMethods from String like
289      * "com.xx.ImmutablePairTest#testBasic,com.xx.StopWatchTest#testLang315+testStopWatchSimpleGet" <br>
290      * and we need to think about cases that 2 or more method in 1 class. we should choose the correct method
291      *
292      * @param testClass the testclass
293      * @param testMethodStr the test method string
294      * @return a string ;)
295      */
296     private static String getMethod( Class testClass, String testMethodStr )
297     {
298         final String className = testClass.getName();
299 
300         if ( !testMethodStr.contains( "#" ) && !testMethodStr.contains( "," ) )
301         {
302             return testMethodStr;
303         }
304         testMethodStr += ","; // for the bellow  split code
305         final int beginIndex = testMethodStr.indexOf( className );
306         final int endIndex = testMethodStr.indexOf( ",", beginIndex );
307         final String classMethodStr =
308             testMethodStr.substring( beginIndex, endIndex ); // String like "StopWatchTest#testLang315"
309 
310         final int index = classMethodStr.indexOf( '#' );
311         return index >= 0 ? classMethodStr.substring( index + 1, classMethodStr.length() ) : null;
312     }
313 }