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