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 org.apache.maven.surefire.booter.Command;
23  import org.apache.maven.surefire.booter.CommandListener;
24  import org.apache.maven.surefire.booter.CommandReader;
25  import org.apache.maven.surefire.common.junit4.ClassMethod;
26  import org.apache.maven.surefire.common.junit4.JUnit4RunListener;
27  import org.apache.maven.surefire.common.junit4.JUnit4TestChecker;
28  import org.apache.maven.surefire.common.junit4.JUnitTestFailureListener;
29  import org.apache.maven.surefire.common.junit4.Notifier;
30  import org.apache.maven.surefire.providerapi.AbstractProvider;
31  import org.apache.maven.surefire.providerapi.ProviderParameters;
32  import org.apache.maven.surefire.report.ConsoleOutputReceiver;
33  import org.apache.maven.surefire.report.PojoStackTraceWriter;
34  import org.apache.maven.surefire.report.ReporterFactory;
35  import org.apache.maven.surefire.report.RunListener;
36  import org.apache.maven.surefire.report.SimpleReportEntry;
37  import org.apache.maven.surefire.suite.RunResult;
38  import org.apache.maven.surefire.testset.TestListResolver;
39  import org.apache.maven.surefire.testset.TestRequest;
40  import org.apache.maven.surefire.testset.TestSetFailedException;
41  import org.apache.maven.surefire.util.RunOrderCalculator;
42  import org.apache.maven.surefire.util.ScanResult;
43  import org.apache.maven.surefire.util.TestsToRun;
44  import org.junit.runner.Description;
45  import org.junit.runner.Request;
46  import org.junit.runner.Result;
47  import org.junit.runner.Runner;
48  import org.junit.runner.manipulation.Filter;
49  import org.junit.runner.notification.RunNotifier;
50  import org.junit.runner.notification.StoppedByUserException;
51  
52  import java.util.Collection;
53  import java.util.Set;
54  
55  import static java.lang.reflect.Modifier.isAbstract;
56  import static java.lang.reflect.Modifier.isInterface;
57  import static org.apache.maven.surefire.booter.CommandReader.getReader;
58  import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.generateFailingTests;
59  import static org.apache.maven.surefire.common.junit4.JUnit4Reflector.createDescription;
60  import static org.apache.maven.surefire.common.junit4.JUnit4Reflector.createIgnored;
61  import static org.apache.maven.surefire.common.junit4.JUnit4RunListener.rethrowAnyTestMechanismFailures;
62  import static org.apache.maven.surefire.common.junit4.JUnit4RunListenerFactory.createCustomListeners;
63  import static org.apache.maven.surefire.common.junit4.Notifier.pureNotifier;
64  import static org.apache.maven.surefire.report.ConsoleOutputCapture.startCapture;
65  import static org.apache.maven.surefire.report.SimpleReportEntry.withException;
66  import static org.apache.maven.surefire.testset.TestListResolver.optionallyWildcardFilter;
67  import static org.apache.maven.surefire.util.TestsToRun.fromClass;
68  import static org.apache.maven.surefire.util.internal.ObjectUtils.systemProps;
69  import static org.junit.runner.Request.aClass;
70  import static org.junit.runner.Request.method;
71  
72  /**
73   * @author Kristian Rosenvold
74   */
75  public class JUnit4Provider
76      extends AbstractProvider
77  {
78      private static final String UNDETERMINED_TESTS_DESCRIPTION = "cannot determine test in forked JVM with surefire";
79  
80      private final ClassLoader testClassLoader;
81  
82      private final String customRunListeners;
83  
84      private final JUnit4TestChecker jUnit4TestChecker;
85  
86      private final TestListResolver testResolver;
87  
88      private final ProviderParameters providerParameters;
89  
90      private final RunOrderCalculator runOrderCalculator;
91  
92      private final ScanResult scanResult;
93  
94      private final int rerunFailingTestsCount;
95  
96      private final CommandReader commandsReader;
97  
98      private TestsToRun testsToRun;
99  
100     public JUnit4Provider( ProviderParameters bootParams )
101     {
102         // don't start a thread in CommandReader while we are in in-plugin process
103         commandsReader = bootParams.isInsideFork() ? getReader().setShutdown( bootParams.getShutdown() ) : null;
104         providerParameters = bootParams;
105         testClassLoader = bootParams.getTestClassLoader();
106         scanResult = bootParams.getScanResult();
107         runOrderCalculator = bootParams.getRunOrderCalculator();
108         customRunListeners = bootParams.getProviderProperties().get( "listener" );
109         jUnit4TestChecker = new JUnit4TestChecker( testClassLoader );
110         TestRequest testRequest = bootParams.getTestRequest();
111         testResolver = testRequest.getTestListResolver();
112         rerunFailingTestsCount = testRequest.getRerunFailingTestsCount();
113     }
114 
115     @Override
116     public RunResult invoke( Object forkTestSet )
117         throws TestSetFailedException
118     {
119         upgradeCheck();
120 
121         ReporterFactory reporterFactory = providerParameters.getReporterFactory();
122 
123         RunResult runResult;
124         try
125         {
126             RunListener reporter = reporterFactory.createReporter();
127 
128             startCapture( (ConsoleOutputReceiver) reporter );
129             // startCapture() called in prior to setTestsToRun()
130 
131             if ( testsToRun == null )
132             {
133                 setTestsToRun( forkTestSet );
134             }
135 
136             Notifier notifier = new Notifier( new JUnit4RunListener( reporter ), getSkipAfterFailureCount() );
137             Result result = new Result();
138             notifier.addListeners( createCustomListeners( customRunListeners ) )
139                 .addListener( result.createListener() );
140 
141             if ( isFailFast() && commandsReader != null )
142             {
143                 registerPleaseStopJUnitListener( notifier );
144             }
145 
146             try
147             {
148                 notifier.fireTestRunStarted( testsToRun.allowEagerReading()
149                                                  ? createTestsDescription( testsToRun )
150                                                  : createDescription( UNDETERMINED_TESTS_DESCRIPTION ) );
151 
152                 if ( commandsReader != null )
153                 {
154                     registerShutdownListener( testsToRun );
155                     commandsReader.awaitStarted();
156                 }
157 
158                 for ( Class<?> testToRun : testsToRun )
159                 {
160                     executeTestSet( testToRun, reporter, notifier );
161                 }
162             }
163             finally
164             {
165                 notifier.fireTestRunFinished( result );
166                 notifier.removeListeners();
167             }
168             rethrowAnyTestMechanismFailures( result );
169         }
170         finally
171         {
172             runResult = reporterFactory.close();
173         }
174         return runResult;
175     }
176 
177     private void setTestsToRun( Object forkTestSet )
178         throws TestSetFailedException
179     {
180         if ( forkTestSet instanceof TestsToRun )
181         {
182             testsToRun = (TestsToRun) forkTestSet;
183         }
184         else if ( forkTestSet instanceof Class )
185         {
186             testsToRun = fromClass( (Class<?>) forkTestSet );
187         }
188         else
189         {
190             testsToRun = scanClassPath();
191         }
192     }
193 
194     private boolean isRerunFailingTests()
195     {
196         return rerunFailingTestsCount > 0;
197     }
198 
199     private boolean isFailFast()
200     {
201         return providerParameters.getSkipAfterFailureCount() > 0;
202     }
203 
204     private int getSkipAfterFailureCount()
205     {
206         return isFailFast() ? providerParameters.getSkipAfterFailureCount() : 0;
207     }
208 
209     private void registerShutdownListener( final TestsToRun testsToRun )
210     {
211         commandsReader.addShutdownListener( new CommandListener()
212         {
213             @Override
214             public void update( Command command )
215             {
216                 testsToRun.markTestSetFinished();
217             }
218         } );
219     }
220 
221     private void registerPleaseStopJUnitListener( final Notifier notifier )
222     {
223         commandsReader.addSkipNextTestsListener( new CommandListener()
224         {
225             @Override
226             public void update( Command command )
227             {
228                 notifier.pleaseStop();
229             }
230         } );
231     }
232 
233     private void executeTestSet( Class<?> clazz, RunListener reporter, Notifier notifier )
234     {
235         final SimpleReportEntry report = new SimpleReportEntry( getClass().getName(), clazz.getName(), systemProps() );
236         reporter.testSetStarting( report );
237         try
238         {
239             executeWithRerun( clazz, notifier );
240         }
241         catch ( Throwable e )
242         {
243             if ( isFailFast() && e instanceof StoppedByUserException )
244             {
245                 String reason = e.getClass().getName();
246                 Description skippedTest = createDescription( clazz.getName(), createIgnored( reason ) );
247                 notifier.fireTestIgnored( skippedTest );
248             }
249             else
250             {
251                 String reportName = report.getName();
252                 String reportSourceName = report.getSourceName();
253                 PojoStackTraceWriter stackWriter = new PojoStackTraceWriter( reportSourceName, reportName, e );
254                 reporter.testError( withException( reportSourceName, reportName, stackWriter ) );
255             }
256         }
257         finally
258         {
259             reporter.testSetCompleted( report );
260         }
261     }
262 
263     private void executeWithRerun( Class<?> clazz, Notifier notifier )
264         throws TestSetFailedException
265     {
266         JUnitTestFailureListener failureListener = new JUnitTestFailureListener();
267         notifier.addListener( failureListener );
268         boolean hasMethodFilter = testResolver != null && testResolver.hasMethodPatterns();
269 
270         try
271         {
272             try
273             {
274                 notifier.asFailFast( isFailFast() );
275                 execute( clazz, notifier, hasMethodFilter ? createMethodFilter() : null );
276             }
277             finally
278             {
279                 notifier.asFailFast( false );
280             }
281 
282             // Rerun failing tests if rerunFailingTestsCount is larger than 0
283             if ( isRerunFailingTests() )
284             {
285                 Notifier rerunNotifier = pureNotifier();
286                 notifier.copyListenersTo( rerunNotifier );
287                 for ( int i = 0; i < rerunFailingTestsCount && !failureListener.getAllFailures().isEmpty(); i++ )
288                 {
289                     Set<ClassMethod> failedTests = generateFailingTests( failureListener.getAllFailures() );
290                     failureListener.reset();
291                     if ( !failedTests.isEmpty() )
292                     {
293                         executeFailedMethod( rerunNotifier, failedTests );
294                     }
295                 }
296             }
297         }
298         finally
299         {
300             notifier.removeListener( failureListener );
301         }
302     }
303 
304     @Override
305     public Iterable<Class<?>> getSuites()
306     {
307         testsToRun = scanClassPath();
308         return testsToRun;
309     }
310 
311     private TestsToRun scanClassPath()
312     {
313         final TestsToRun scannedClasses = scanResult.applyFilter( jUnit4TestChecker, testClassLoader );
314         return runOrderCalculator.orderTestClasses( scannedClasses );
315     }
316 
317     private void upgradeCheck()
318         throws TestSetFailedException
319     {
320         if ( isJUnit4UpgradeCheck() )
321         {
322             Collection<Class<?>> classesSkippedByValidation =
323                 scanResult.getClassesSkippedByValidation( jUnit4TestChecker, testClassLoader );
324             if ( !classesSkippedByValidation.isEmpty() )
325             {
326                 StringBuilder reason = new StringBuilder();
327                 reason.append( "Updated check failed\n" );
328                 reason.append( "There are tests that would be run with junit4 / surefire 2.6 but not with [2.7,):\n" );
329                 for ( Class testClass : classesSkippedByValidation )
330                 {
331                     reason.append( "   " );
332                     reason.append( testClass.getName() );
333                     reason.append( "\n" );
334                 }
335                 throw new TestSetFailedException( reason.toString() );
336             }
337         }
338     }
339 
340     static Description createTestsDescription( Iterable<Class<?>> classes )
341     {
342         // "null" string rather than null; otherwise NPE in junit:4.0
343         Description description = createDescription( "null" );
344         for ( Class<?> clazz : classes )
345         {
346             description.addChild( createDescription( clazz.getName() ) );
347         }
348         return description;
349     }
350 
351     private static boolean isJUnit4UpgradeCheck()
352     {
353         return System.getProperty( "surefire.junit4.upgradecheck" ) != null;
354     }
355 
356     private static void execute( Class<?> testClass, Notifier notifier, Filter filter )
357     {
358         final int classModifiers = testClass.getModifiers();
359         if ( !isAbstract( classModifiers ) && !isInterface( classModifiers ) )
360         {
361             Request request = aClass( testClass );
362             if ( filter != null )
363             {
364                 request = request.filterWith( filter );
365             }
366             Runner runner = request.getRunner();
367             if ( countTestsInRunner( runner.getDescription() ) != 0 )
368             {
369                 runner.run( notifier );
370             }
371         }
372     }
373 
374     private void executeFailedMethod( RunNotifier notifier, Set<ClassMethod> failedMethods )
375         throws TestSetFailedException
376     {
377         for ( ClassMethod failedMethod : failedMethods )
378         {
379             try
380             {
381                 Class<?> methodClass = Class.forName( failedMethod.getClazz(), true, testClassLoader );
382                 String methodName = failedMethod.getMethod();
383                 method( methodClass, methodName ).getRunner().run( notifier );
384             }
385             catch ( ClassNotFoundException e )
386             {
387                 throw new TestSetFailedException( "Unable to create test class '" + failedMethod.getClazz() + "'", e );
388             }
389         }
390     }
391 
392     /**
393      * JUnit error: test count includes one test-class as a suite which has filtered out all children.
394      * Then the child test has a description "initializationError0(org.junit.runner.manipulation.Filter)"
395      * for JUnit 4.0 or "initializationError(org.junit.runner.manipulation.Filter)" for JUnit 4.12
396      * and Description#isTest() returns true, but this description is not a real test
397      * and therefore it should not be included in the entire test count.
398      */
399     private static int countTestsInRunner( Description description )
400     {
401         if ( description.isSuite() )
402         {
403             int count = 0;
404             for ( Description child : description.getChildren() )
405             {
406                 if ( !hasFilteredOutAllChildren( child ) )
407                 {
408                     count += countTestsInRunner( child );
409                 }
410             }
411             return count;
412         }
413         else if ( description.isTest() )
414         {
415             return hasFilteredOutAllChildren( description ) ? 0 : 1;
416         }
417         else
418         {
419             return 0;
420         }
421     }
422 
423     private static boolean hasFilteredOutAllChildren( Description description )
424     {
425         String name = description.getDisplayName();
426         // JUnit 4.0: initializationError0; JUnit 4.12: initializationError.
427         if ( name == null )
428         {
429             return true;
430         }
431         else
432         {
433             name = name.trim();
434             return name.startsWith( "initializationError0(org.junit.runner.manipulation.Filter)" )
435                            || name.startsWith( "initializationError(org.junit.runner.manipulation.Filter)" );
436         }
437     }
438 
439     private Filter createMethodFilter()
440     {
441         TestListResolver methodFilter = optionallyWildcardFilter( testResolver );
442         return methodFilter.isEmpty() || methodFilter.isWildcard() ? null : new TestResolverFilter( methodFilter );
443     }
444 }