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.common.junit4;
20  
21  import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
22  import org.apache.maven.surefire.api.report.OutputReportEntry;
23  import org.apache.maven.surefire.api.report.ReportEntry;
24  import org.apache.maven.surefire.api.report.RunMode;
25  import org.apache.maven.surefire.api.report.SimpleReportEntry;
26  import org.apache.maven.surefire.api.report.StackTraceWriter;
27  import org.apache.maven.surefire.api.report.TestOutputReceiver;
28  import org.apache.maven.surefire.api.report.TestOutputReportEntry;
29  import org.apache.maven.surefire.api.report.TestReportListener;
30  import org.apache.maven.surefire.api.testset.TestSetFailedException;
31  import org.apache.maven.surefire.api.util.internal.ClassMethod;
32  import org.apache.maven.surefire.report.ClassMethodIndexer;
33  import org.apache.maven.surefire.report.RunModeSetter;
34  import org.junit.runner.Description;
35  import org.junit.runner.Result;
36  import org.junit.runner.notification.Failure;
37  import org.junit.runner.notification.RunListener;
38  
39  import static org.apache.maven.surefire.api.report.SimpleReportEntry.assumption;
40  import static org.apache.maven.surefire.api.report.SimpleReportEntry.ignored;
41  import static org.apache.maven.surefire.api.report.SimpleReportEntry.withException;
42  import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.isFailureInsideJUnitItself;
43  import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.toClassMethod;
44  import static org.apache.maven.surefire.common.junit4.JUnit4Reflector.getAnnotatedIgnoreValue;
45  
46  /**
47   * RunListener for JUnit4, delegates to our own RunListener
48   *
49   */
50  public class JUnit4RunListener extends RunListener implements TestOutputReceiver<OutputReportEntry>, RunModeSetter {
51      protected final ClassMethodIndexer classMethodIndexer = new ClassMethodIndexer();
52      protected final TestReportListener<TestOutputReportEntry> reporter;
53      private volatile RunMode runMode;
54  
55      /**
56       * This flag is set after a failure has occurred so that a
57       * {@link org.apache.maven.surefire.api.report.RunListener#testSucceeded} event is not fired.
58       * This is necessary because JUnit4 always fires a
59       * {@link org.junit.runner.notification.RunListener#testRunFinished(Result)}
60       * event-- even if there was a failure.
61       */
62      private final ThreadLocal<Boolean> failureFlag = new InheritableThreadLocal<>();
63  
64      /**
65       * Constructor.
66       *
67       * @param reporter the reporter to log testing events to
68       */
69      public JUnit4RunListener(TestReportListener<TestOutputReportEntry> reporter) {
70          this.reporter = reporter;
71      }
72  
73      public final ConsoleLogger getConsoleLogger() {
74          return reporter;
75      }
76  
77      @Override
78      public void setRunMode(RunMode runMode) {
79          this.runMode = runMode;
80      }
81  
82      protected final RunMode getRunMode() {
83          return runMode;
84      }
85  
86      // Testrun methods are not invoked when using the runner
87  
88      /**
89       * Called when a specific test has been skipped (for whatever reason).
90       *
91       * @see org.junit.runner.notification.RunListener#testIgnored(org.junit.runner.Description)
92       */
93      @Override
94      public void testIgnored(Description description) throws Exception {
95          String reason = getAnnotatedIgnoreValue(description);
96          ClassMethod classMethod = toClassMethod(description);
97          long testRunId = classMethodIndexer.indexClassMethod(classMethod.getClazz(), classMethod.getMethod());
98          reporter.testSkipped(
99                  ignored(runMode, testRunId, classMethod.getClazz(), null, classMethod.getMethod(), null, reason));
100     }
101 
102     /**
103      * Called when a specific test has started.
104      *
105      * @see org.junit.runner.notification.RunListener#testStarted(org.junit.runner.Description)
106      */
107     @Override
108     public void testStarted(Description description) throws Exception {
109         try {
110             reporter.testStarting(createReportEntry(description));
111         } finally {
112             failureFlag.remove();
113         }
114     }
115 
116     /**
117      * Called when a specific test has failed.
118      *
119      * @see org.junit.runner.notification.RunListener#testFailure(org.junit.runner.notification.Failure)
120      */
121     @Override
122     @SuppressWarnings({"ThrowableResultOfMethodCallIgnored"})
123     public void testFailure(Failure failure) throws Exception {
124         try {
125             StackTraceWriter stackTrace = createStackTraceWriter(failure);
126             ClassMethod classMethod = toClassMethod(failure.getDescription());
127             long testRunId = classMethodIndexer.indexClassMethod(classMethod.getClazz(), classMethod.getMethod());
128             ReportEntry report = withException(
129                     runMode, testRunId, classMethod.getClazz(), null, classMethod.getMethod(), null, stackTrace);
130 
131             if (failure.getException() instanceof AssertionError) {
132                 reporter.testFailed(report);
133             } else {
134                 reporter.testError(report);
135             }
136         } finally {
137             failureFlag.set(true);
138         }
139     }
140 
141     public void testAssumptionFailure(Failure failure) {
142         try {
143             Description desc = failure.getDescription();
144             ClassMethod classMethod = toClassMethod(desc);
145             long testRunId = classMethodIndexer.indexClassMethod(classMethod.getClazz(), classMethod.getMethod());
146             ReportEntry report = assumption(
147                     runMode,
148                     testRunId,
149                     classMethod.getClazz(),
150                     null,
151                     classMethod.getMethod(),
152                     null,
153                     failure.getMessage());
154             reporter.testAssumptionFailure(report);
155         } finally {
156             failureFlag.set(true);
157         }
158     }
159 
160     /**
161      * Called after a specific test has finished.
162      *
163      * @see org.junit.runner.notification.RunListener#testFinished(org.junit.runner.Description)
164      */
165     @Override
166     public void testFinished(Description description) throws Exception {
167         Boolean failure = failureFlag.get();
168         if (failure == null) {
169             reporter.testSucceeded(createReportEntry(description));
170         }
171     }
172 
173     /**
174      * Delegates to {@link org.apache.maven.surefire.api.report.RunListener#testExecutionSkippedByUser()}.
175      */
176     public void testExecutionSkippedByUser() {
177         reporter.testExecutionSkippedByUser();
178     }
179 
180     protected StackTraceWriter createStackTraceWriter(Failure failure) {
181         return new JUnit4StackTraceWriter(failure);
182     }
183 
184     protected SimpleReportEntry createReportEntry(Description description) {
185         ClassMethod classMethod = toClassMethod(description);
186         long testRunId = classMethodIndexer.indexClassMethod(classMethod.getClazz(), classMethod.getMethod());
187         return new SimpleReportEntry(runMode, testRunId, classMethod.getClazz(), null, classMethod.getMethod(), null);
188     }
189 
190     public static void rethrowAnyTestMechanismFailures(Result run) throws TestSetFailedException {
191         for (Failure failure : run.getFailures()) {
192             if (isFailureInsideJUnitItself(failure.getDescription())) {
193                 throw new TestSetFailedException(
194                         failure.getTestHeader() + " :: " + failure.getMessage(), failure.getException());
195             }
196         }
197     }
198 
199     @Override
200     public void writeTestOutput(OutputReportEntry reportEntry) {
201         Long testRunId = classMethodIndexer.getLocalIndex();
202         reporter.writeTestOutput(new TestOutputReportEntry(reportEntry, runMode, testRunId));
203     }
204 }