View Javadoc
1   package org.apache.maven.surefire.junitplatform;
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 static org.apache.maven.surefire.report.SimpleReportEntry.ignored;
23  import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED;
24  import static org.junit.platform.engine.TestExecutionResult.Status.FAILED;
25  
26  import java.util.Optional;
27  import java.util.Set;
28  import java.util.concurrent.ConcurrentHashMap;
29  
30  import org.apache.maven.surefire.report.PojoStackTraceWriter;
31  import org.apache.maven.surefire.report.RunListener;
32  import org.apache.maven.surefire.report.SimpleReportEntry;
33  import org.apache.maven.surefire.report.StackTraceWriter;
34  import org.junit.platform.engine.TestExecutionResult;
35  import org.junit.platform.engine.TestSource;
36  import org.junit.platform.engine.support.descriptor.ClassSource;
37  import org.junit.platform.engine.support.descriptor.MethodSource;
38  import org.junit.platform.launcher.TestExecutionListener;
39  import org.junit.platform.launcher.TestIdentifier;
40  import org.junit.platform.launcher.TestPlan;
41  import org.junit.platform.launcher.listeners.LegacyReportingUtils;
42  
43  /**
44   * @since 2.22.0
45   */
46  final class RunListenerAdapter
47      implements TestExecutionListener
48  {
49  
50      private final RunListener runListener;
51  
52      private TestPlan testPlan;
53  
54      private Set<TestIdentifier> testSetNodes = ConcurrentHashMap.newKeySet();
55  
56      RunListenerAdapter( RunListener runListener )
57      {
58          this.runListener = runListener;
59      }
60  
61      @Override
62      public void testPlanExecutionStarted( TestPlan testPlan )
63      {
64          updateTestPlan( testPlan );
65      }
66  
67      @Override
68      public void testPlanExecutionFinished( TestPlan testPlan )
69      {
70          updateTestPlan( null );
71      }
72  
73      @Override
74      public void executionStarted( TestIdentifier testIdentifier )
75      {
76          if ( testIdentifier.isContainer()
77                          && testIdentifier.getSource().filter( ClassSource.class::isInstance ).isPresent() )
78          {
79              startTestSetIfPossible( testIdentifier );
80          }
81          if ( testIdentifier.isTest() )
82          {
83              ensureTestSetStarted( testIdentifier );
84              runListener.testStarting( createReportEntry( testIdentifier ) );
85          }
86      }
87  
88      @Override
89      public void executionSkipped( TestIdentifier testIdentifier, String reason )
90      {
91          ensureTestSetStarted( testIdentifier );
92          String source = getLegacyReportingClassName( testIdentifier );
93          runListener.testSkipped( ignored( source, getLegacyReportingName( testIdentifier ), reason ) );
94          completeTestSetIfNecessary( testIdentifier );
95      }
96  
97      @Override
98      public void executionFinished(
99                      TestIdentifier testIdentifier, TestExecutionResult testExecutionResult )
100     {
101         if ( testExecutionResult.getStatus() == ABORTED )
102         {
103             runListener.testAssumptionFailure( createReportEntry( testIdentifier, testExecutionResult ) );
104         }
105         else if ( testExecutionResult.getStatus() == FAILED )
106         {
107             reportFailedTest( testIdentifier, testExecutionResult );
108         }
109         else if ( testIdentifier.isTest() )
110         {
111             runListener.testSucceeded( createReportEntry( testIdentifier ) );
112         }
113         completeTestSetIfNecessary( testIdentifier );
114     }
115 
116     private void updateTestPlan( TestPlan testPlan )
117     {
118         this.testPlan = testPlan;
119         testSetNodes.clear();
120     }
121 
122     private void ensureTestSetStarted( TestIdentifier testIdentifier )
123     {
124         if ( isTestSetStarted( testIdentifier ) )
125         {
126             return;
127         }
128         if ( testIdentifier.isTest() )
129         {
130             startTestSet( testPlan.getParent( testIdentifier ).orElse( testIdentifier ) );
131         }
132         else
133         {
134             startTestSet( testIdentifier );
135         }
136     }
137 
138     private boolean isTestSetStarted( TestIdentifier testIdentifier )
139     {
140         return testSetNodes.contains( testIdentifier )
141                         || testPlan.getParent( testIdentifier ).map( this::isTestSetStarted ).orElse( false );
142     }
143 
144     private void startTestSetIfPossible( TestIdentifier testIdentifier )
145     {
146         if ( !isTestSetStarted( testIdentifier ) )
147         {
148             startTestSet( testIdentifier );
149         }
150     }
151 
152     private void completeTestSetIfNecessary( TestIdentifier testIdentifier )
153     {
154         if ( testSetNodes.contains( testIdentifier ) )
155         {
156             completeTestSet( testIdentifier );
157         }
158     }
159 
160     private void startTestSet( TestIdentifier testIdentifier )
161     {
162         runListener.testSetStarting( createTestSetReportEntry( testIdentifier ) );
163         testSetNodes.add( testIdentifier );
164     }
165 
166     private void completeTestSet( TestIdentifier testIdentifier )
167     {
168         runListener.testSetCompleted( createTestSetReportEntry( testIdentifier ) );
169         testSetNodes.remove( testIdentifier );
170     }
171 
172     private void reportFailedTest(
173                     TestIdentifier testIdentifier, TestExecutionResult testExecutionResult )
174     {
175         SimpleReportEntry reportEntry = createReportEntry( testIdentifier, testExecutionResult );
176         if ( testExecutionResult.getThrowable().filter( AssertionError.class::isInstance ).isPresent() )
177         {
178             runListener.testFailed( reportEntry );
179         }
180         else
181         {
182             runListener.testError( reportEntry );
183         }
184     }
185 
186     private SimpleReportEntry createTestSetReportEntry( TestIdentifier testIdentifier )
187     {
188         return new SimpleReportEntry(
189                         JUnitPlatformProvider.class.getName(), testIdentifier.getLegacyReportingName() );
190     }
191 
192     private SimpleReportEntry createReportEntry( TestIdentifier testIdentifier )
193     {
194         return createReportEntry( testIdentifier, (StackTraceWriter) null );
195     }
196 
197     private SimpleReportEntry createReportEntry(
198                     TestIdentifier testIdentifier, TestExecutionResult testExecutionResult )
199     {
200         return createReportEntry(
201                         testIdentifier, getStackTraceWriter( testIdentifier, testExecutionResult ) );
202     }
203 
204     private SimpleReportEntry createReportEntry(
205                     TestIdentifier testIdentifier, StackTraceWriter stackTraceWriter )
206     {
207         String source = getLegacyReportingClassName( testIdentifier );
208         String name = getLegacyReportingName( testIdentifier );
209 
210         return SimpleReportEntry.withException( source, name, stackTraceWriter );
211     }
212 
213     private String getLegacyReportingName( TestIdentifier testIdentifier )
214     {
215         // Surefire cuts off the name at the first '(' character. Thus, we have to pick a different
216         // character to represent parentheses. "()" are removed entirely to maximize compatibility with
217         // existing reporting tools because in the old days test methods used to not have parameters.
218         return testIdentifier
219                         .getLegacyReportingName()
220                         .replace( "()", "" )
221                         .replace( '(', '{' )
222                         .replace( ')', '}' );
223     }
224 
225     private String getLegacyReportingClassName( TestIdentifier testIdentifier )
226     {
227         return LegacyReportingUtils.getClassName( testPlan, testIdentifier );
228     }
229 
230     private StackTraceWriter getStackTraceWriter(
231                     TestIdentifier testIdentifier, TestExecutionResult testExecutionResult )
232     {
233         Optional<Throwable> throwable = testExecutionResult.getThrowable();
234         if ( testExecutionResult.getStatus() == FAILED )
235         {
236             // Failed tests must have a StackTraceWriter, otherwise Surefire will fail
237             return getStackTraceWriter( testIdentifier, throwable.orElse( null ) );
238         }
239         return throwable.map( t -> getStackTraceWriter( testIdentifier, t ) ).orElse( null );
240     }
241 
242     private StackTraceWriter getStackTraceWriter( TestIdentifier testIdentifier, Throwable throwable )
243     {
244         String className = getClassName( testIdentifier );
245         String methodName = getMethodName( testIdentifier ).orElse( "" );
246         return new PojoStackTraceWriter( className, methodName, throwable );
247     }
248 
249     private String getClassName( TestIdentifier testIdentifier )
250     {
251         TestSource testSource = testIdentifier.getSource().orElse( null );
252         if ( testSource instanceof ClassSource )
253         {
254             return ( (ClassSource) testSource ).getJavaClass().getName();
255         }
256         if ( testSource instanceof MethodSource )
257         {
258             return ( (MethodSource) testSource ).getClassName();
259         }
260         return testPlan.getParent( testIdentifier ).map( this::getClassName ).orElse( "" );
261     }
262 
263     private Optional<String> getMethodName( TestIdentifier testIdentifier )
264     {
265         TestSource testSource = testIdentifier.getSource().orElse( null );
266         if ( testSource instanceof MethodSource )
267         {
268             return Optional.of( ( (MethodSource) testSource ).getMethodName() );
269         }
270         return Optional.empty();
271     }
272 }