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