View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.surefire.junit4;
20  
21  import java.util.Collection;
22  import java.util.Set;
23  
24  import org.apache.maven.surefire.api.booter.Command;
25  import org.apache.maven.surefire.api.provider.AbstractProvider;
26  import org.apache.maven.surefire.api.provider.CommandChainReader;
27  import org.apache.maven.surefire.api.provider.CommandListener;
28  import org.apache.maven.surefire.api.provider.ProviderParameters;
29  import org.apache.maven.surefire.api.report.ReporterFactory;
30  import org.apache.maven.surefire.api.report.RunListener;
31  import org.apache.maven.surefire.api.report.SimpleReportEntry;
32  import org.apache.maven.surefire.api.report.TestOutputReportEntry;
33  import org.apache.maven.surefire.api.report.TestReportListener;
34  import org.apache.maven.surefire.api.suite.RunResult;
35  import org.apache.maven.surefire.api.testset.TestListResolver;
36  import org.apache.maven.surefire.api.testset.TestRequest;
37  import org.apache.maven.surefire.api.testset.TestSetFailedException;
38  import org.apache.maven.surefire.api.util.RunOrderCalculator;
39  import org.apache.maven.surefire.api.util.ScanResult;
40  import org.apache.maven.surefire.api.util.TestsToRun;
41  import org.apache.maven.surefire.common.junit4.JUnit4RunListener;
42  import org.apache.maven.surefire.common.junit4.JUnit4TestChecker;
43  import org.apache.maven.surefire.common.junit4.JUnitTestFailureListener;
44  import org.apache.maven.surefire.common.junit4.Notifier;
45  import org.apache.maven.surefire.report.ClassMethodIndexer;
46  import org.apache.maven.surefire.report.PojoStackTraceWriter;
47  import org.apache.maven.surefire.report.RunModeSetter;
48  import org.junit.runner.Description;
49  import org.junit.runner.Request;
50  import org.junit.runner.Result;
51  import org.junit.runner.Runner;
52  import org.junit.runner.manipulation.Filter;
53  import org.junit.runner.notification.StoppedByUserException;
54  
55  import static java.lang.reflect.Modifier.isAbstract;
56  import static java.lang.reflect.Modifier.isInterface;
57  import static org.apache.maven.surefire.api.report.ConsoleOutputCapture.startCapture;
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.api.report.SimpleReportEntry.withException;
61  import static org.apache.maven.surefire.api.testset.TestListResolver.optionallyWildcardFilter;
62  import static org.apache.maven.surefire.api.util.TestsToRun.fromClass;
63  import static org.apache.maven.surefire.api.util.internal.ObjectUtils.systemProps;
64  import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.createMatchAnyDescriptionFilter;
65  import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.generateFailingTestDescriptions;
66  import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.isFailureInsideJUnitItself;
67  import static org.apache.maven.surefire.common.junit4.JUnit4Reflector.createDescription;
68  import static org.apache.maven.surefire.common.junit4.JUnit4Reflector.createIgnored;
69  import static org.apache.maven.surefire.common.junit4.JUnit4RunListener.rethrowAnyTestMechanismFailures;
70  import static org.apache.maven.surefire.common.junit4.JUnit4RunListenerFactory.createCustomListeners;
71  import static org.apache.maven.surefire.common.junit4.Notifier.pureNotifier;
72  import static org.junit.runner.Request.aClass;
73  
74  /**
75   * @author Kristian Rosenvold
76   */
77  public class JUnit4Provider extends AbstractProvider {
78      private static final String UNDETERMINED_TESTS_DESCRIPTION = "cannot determine test in forked JVM with surefire";
79  
80      private final ClassMethodIndexer classMethodIndexer = new ClassMethodIndexer();
81  
82      private final ClassLoader testClassLoader;
83  
84      private final String customRunListeners;
85  
86      private final JUnit4TestChecker jUnit4TestChecker;
87  
88      private final TestListResolver testResolver;
89  
90      private final ProviderParameters providerParameters;
91  
92      private final RunOrderCalculator runOrderCalculator;
93  
94      private final ScanResult scanResult;
95  
96      private final int rerunFailingTestsCount;
97  
98      private final CommandChainReader commandsReader;
99  
100     private TestsToRun testsToRun;
101 
102     public JUnit4Provider(ProviderParameters bootParams) {
103         // don't start a thread in CommandReader while we are in in-plugin process
104         commandsReader = bootParams.isInsideFork() ? bootParams.getCommandReader() : null;
105         providerParameters = bootParams;
106         testClassLoader = bootParams.getTestClassLoader();
107         scanResult = bootParams.getScanResult();
108         runOrderCalculator = bootParams.getRunOrderCalculator();
109         customRunListeners = bootParams.getProviderProperties().get("listener");
110         jUnit4TestChecker = new JUnit4TestChecker(testClassLoader);
111         TestRequest testRequest = bootParams.getTestRequest();
112         testResolver = testRequest.getTestListResolver();
113         rerunFailingTestsCount = testRequest.getRerunFailingTestsCount();
114     }
115 
116     @Override
117     public RunResult invoke(Object forkTestSet) throws TestSetFailedException {
118         upgradeCheck();
119 
120         ReporterFactory reporterFactory = providerParameters.getReporterFactory();
121 
122         RunResult runResult;
123         try {
124             TestReportListener<TestOutputReportEntry> reporter = reporterFactory.createTestReportListener();
125             JUnit4RunListener listener = new JUnit4RunListener(reporter);
126             listener.setRunMode(NORMAL_RUN);
127 
128             startCapture(listener);
129             // startCapture() called in prior to setTestsToRun()
130 
131             if (testsToRun == null) {
132                 setTestsToRun(forkTestSet);
133             }
134 
135             Notifier notifier = new Notifier(listener, getSkipAfterFailureCount());
136             Result result = new Result();
137             notifier.addListeners(createCustomListeners(customRunListeners)).addListener(result.createListener());
138 
139             if (isFailFast() && commandsReader != null) {
140                 registerPleaseStopJUnitListener(notifier);
141             }
142 
143             try {
144                 notifier.fireTestRunStarted(
145                         testsToRun.allowEagerReading()
146                                 ? createTestsDescription(testsToRun)
147                                 : createDescription(UNDETERMINED_TESTS_DESCRIPTION));
148 
149                 if (commandsReader != null) {
150                     registerShutdownListener(testsToRun);
151                     commandsReader.awaitStarted();
152                 }
153 
154                 for (Class<?> testToRun : testsToRun) {
155                     executeTestSet(testToRun, reporter, notifier, listener);
156                 }
157             } finally {
158                 notifier.fireTestRunFinished(result);
159                 notifier.removeListeners();
160             }
161             rethrowAnyTestMechanismFailures(result);
162         } finally {
163             runResult = reporterFactory.close();
164         }
165         return runResult;
166     }
167 
168     private void setTestsToRun(Object forkTestSet) throws TestSetFailedException {
169         if (forkTestSet instanceof TestsToRun) {
170             testsToRun = (TestsToRun) forkTestSet;
171         } else if (forkTestSet instanceof Class) {
172             testsToRun = fromClass((Class<?>) forkTestSet);
173         } else {
174             testsToRun = scanClassPath();
175         }
176     }
177 
178     private boolean isRerunFailingTests() {
179         return rerunFailingTestsCount > 0;
180     }
181 
182     private boolean isFailFast() {
183         return providerParameters.getSkipAfterFailureCount() > 0;
184     }
185 
186     private int getSkipAfterFailureCount() {
187         return isFailFast() ? providerParameters.getSkipAfterFailureCount() : 0;
188     }
189 
190     private void registerShutdownListener(final TestsToRun testsToRun) {
191         commandsReader.addShutdownListener(new CommandListener() {
192             @Override
193             public void update(Command command) {
194                 testsToRun.markTestSetFinished();
195             }
196         });
197     }
198 
199     private void registerPleaseStopJUnitListener(final Notifier notifier) {
200         commandsReader.addSkipNextTestsListener(new CommandListener() {
201             @Override
202             public void update(Command command) {
203                 notifier.pleaseStop();
204             }
205         });
206     }
207 
208     private void executeTestSet(Class<?> clazz, RunListener reporter, Notifier notifier, RunModeSetter runMode) {
209         long testRunId = classMethodIndexer.indexClass(clazz.getName());
210         SimpleReportEntry report =
211                 new SimpleReportEntry(NORMAL_RUN, testRunId, clazz.getName(), null, null, null, systemProps());
212         reporter.testSetStarting(report);
213         try {
214             executeWithRerun(clazz, notifier, runMode);
215         } catch (Throwable e) {
216             if (isFailFast() && e instanceof StoppedByUserException) {
217                 String reason = e.getClass().getName();
218                 Description skippedTest = createDescription(clazz.getName(), createIgnored(reason));
219                 notifier.fireTestIgnored(skippedTest);
220             } else {
221                 String reportName = report.getName();
222                 String reportSourceName = report.getSourceName();
223                 PojoStackTraceWriter stackWriter = new PojoStackTraceWriter(reportSourceName, reportName, e);
224                 reporter.testError(
225                         withException(NORMAL_RUN, testRunId, reportSourceName, null, reportName, null, stackWriter));
226             }
227         } finally {
228             reporter.testSetCompleted(report);
229         }
230     }
231 
232     private void executeWithRerun(Class<?> clazz, Notifier notifier, RunModeSetter runMode) {
233         JUnitTestFailureListener failureListener = new JUnitTestFailureListener();
234         notifier.addListener(failureListener);
235         boolean hasMethodFilter = testResolver != null && testResolver.hasMethodPatterns();
236 
237         try {
238             try {
239                 notifier.asFailFast(isFailFast());
240                 execute(clazz, notifier, hasMethodFilter ? createMethodFilter() : null);
241             } finally {
242                 notifier.asFailFast(false);
243             }
244 
245             // Rerun failing tests if rerunFailingTestsCount is larger than 0
246             if (isRerunFailingTests()) {
247                 runMode.setRunMode(RERUN_TEST_AFTER_FAILURE);
248                 Notifier rerunNotifier = pureNotifier();
249                 notifier.copyListenersTo(rerunNotifier);
250                 for (int i = 0;
251                         i < rerunFailingTestsCount
252                                 && !failureListener.getAllFailures().isEmpty();
253                         i++) {
254                     Set<Description> failures = generateFailingTestDescriptions(failureListener.getAllFailures());
255                     failureListener.reset();
256                     Filter failureDescriptionFilter = createMatchAnyDescriptionFilter(failures);
257                     execute(clazz, rerunNotifier, failureDescriptionFilter);
258                 }
259             }
260         } finally {
261             notifier.removeListener(failureListener);
262         }
263     }
264 
265     @Override
266     public Iterable<Class<?>> getSuites() {
267         testsToRun = scanClassPath();
268         return testsToRun;
269     }
270 
271     private TestsToRun scanClassPath() {
272         final TestsToRun scannedClasses = scanResult.applyFilter(jUnit4TestChecker, testClassLoader);
273         return runOrderCalculator.orderTestClasses(scannedClasses);
274     }
275 
276     private void upgradeCheck() throws TestSetFailedException {
277         if (isJUnit4UpgradeCheck()) {
278             Collection<Class<?>> classesSkippedByValidation =
279                     scanResult.getClassesSkippedByValidation(jUnit4TestChecker, testClassLoader);
280             if (!classesSkippedByValidation.isEmpty()) {
281                 StringBuilder reason = new StringBuilder();
282                 reason.append("Updated check failed\n");
283                 reason.append("There are tests that would be run with junit4 / surefire 2.6 but not with [2.7,):\n");
284                 for (Class testClass : classesSkippedByValidation) {
285                     reason.append("   ");
286                     reason.append(testClass.getName());
287                     reason.append("\n");
288                 }
289                 throw new TestSetFailedException(reason.toString());
290             }
291         }
292     }
293 
294     static Description createTestsDescription(Iterable<Class<?>> classes) {
295         // "null" string rather than null; otherwise NPE in junit:4.0
296         Description description = createDescription("null");
297         for (Class<?> clazz : classes) {
298             description.addChild(createDescription(clazz.getName()));
299         }
300         return description;
301     }
302 
303     private static boolean isJUnit4UpgradeCheck() {
304         return System.getProperty("surefire.junit4.upgradecheck") != null;
305     }
306 
307     private static void execute(Class<?> testClass, Notifier notifier, Filter filter) {
308         final int classModifiers = testClass.getModifiers();
309         if (!isAbstract(classModifiers) && !isInterface(classModifiers)) {
310             Request request = aClass(testClass);
311             if (filter != null) {
312                 request = request.filterWith(filter);
313             }
314             Runner runner = request.getRunner();
315             if (countTestsInRunner(runner.getDescription()) != 0) {
316                 runner.run(notifier);
317             }
318         }
319     }
320 
321     /**
322      * JUnit error: test count includes one test-class as a suite which has filtered out all children.
323      * Then the child test has a description "initializationError0(org.junit.runner.manipulation.Filter)"
324      * for JUnit 4.0 or "initializationError(org.junit.runner.manipulation.Filter)" for JUnit 4.12
325      * and Description#isTest() returns true, but this description is not a real test
326      * and therefore it should not be included in the entire test count.
327      */
328     private static int countTestsInRunner(Description description) {
329         if (description.isSuite()) {
330             int count = 0;
331             for (Description child : description.getChildren()) {
332                 if (!hasFilteredOutAllChildren(child)) {
333                     count += countTestsInRunner(child);
334                 }
335             }
336             return count;
337         } else if (description.isTest()) {
338             return hasFilteredOutAllChildren(description) ? 0 : 1;
339         } else {
340             return 0;
341         }
342     }
343 
344     private static boolean hasFilteredOutAllChildren(Description description) {
345         if (isFailureInsideJUnitItself(description)) {
346             return true;
347         }
348         String name = description.getDisplayName();
349         // JUnit 4.0: initializationError0; JUnit 4.12: initializationError.
350         if (name == null) {
351             return true;
352         } else {
353             name = name.trim();
354             return name.startsWith("initializationError0(org.junit.runner.manipulation.Filter)")
355                     || name.startsWith("initializationError(org.junit.runner.manipulation.Filter)");
356         }
357     }
358 
359     private Filter createMethodFilter() {
360         TestListResolver methodFilter = optionallyWildcardFilter(testResolver);
361         return methodFilter.isEmpty() || methodFilter.isWildcard() ? null : new TestResolverFilter(methodFilter);
362     }
363 }