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