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