1 package org.apache.maven.surefire.junitplatform;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import static java.util.Collections.emptyMap;
23 import static java.util.stream.Collectors.joining;
24 import static org.apache.maven.surefire.api.util.internal.ObjectUtils.systemProps;
25 import static org.apache.maven.surefire.shared.lang3.StringUtils.isNotBlank;
26 import static org.junit.platform.engine.TestExecutionResult.Status.FAILED;
27
28 import java.util.Map;
29 import java.util.Objects;
30 import java.util.Optional;
31 import java.util.concurrent.ConcurrentHashMap;
32 import java.util.concurrent.ConcurrentMap;
33 import java.util.regex.Pattern;
34
35 import org.apache.maven.surefire.report.PojoStackTraceWriter;
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.report.StackTraceWriter;
39 import org.junit.platform.engine.TestExecutionResult;
40 import org.junit.platform.engine.TestSource;
41 import org.junit.platform.engine.support.descriptor.ClassSource;
42 import org.junit.platform.engine.support.descriptor.MethodSource;
43 import org.junit.platform.launcher.TestExecutionListener;
44 import org.junit.platform.launcher.TestIdentifier;
45 import org.junit.platform.launcher.TestPlan;
46
47
48
49
50 final class RunListenerAdapter
51 implements TestExecutionListener
52 {
53 private static final Pattern COMMA_PATTERN = Pattern.compile( "," );
54
55 private final ConcurrentMap<TestIdentifier, Long> testStartTime = new ConcurrentHashMap<>();
56 private final ConcurrentMap<TestIdentifier, TestExecutionResult> failures = new ConcurrentHashMap<>();
57 private final RunListener runListener;
58 private volatile TestPlan testPlan;
59
60 RunListenerAdapter( RunListener runListener )
61 {
62 this.runListener = runListener;
63 }
64
65 @Override
66 public void testPlanExecutionStarted( TestPlan testPlan )
67 {
68 this.testPlan = testPlan;
69 }
70
71 @Override
72 public void testPlanExecutionFinished( TestPlan testPlan )
73 {
74 this.testPlan = null;
75 testStartTime.clear();
76 }
77
78 @Override
79 public void executionStarted( TestIdentifier testIdentifier )
80 {
81 if ( testIdentifier.isContainer()
82 && testIdentifier.getSource().filter( ClassSource.class::isInstance ).isPresent() )
83 {
84 testStartTime.put( testIdentifier, System.currentTimeMillis() );
85 runListener.testSetStarting( createReportEntry( testIdentifier ) );
86 }
87 else if ( testIdentifier.isTest() )
88 {
89 testStartTime.put( testIdentifier, System.currentTimeMillis() );
90 runListener.testStarting( createReportEntry( testIdentifier ) );
91 }
92 }
93
94 @Override
95 public void executionFinished( TestIdentifier testIdentifier, TestExecutionResult testExecutionResult )
96 {
97 boolean isClass = testIdentifier.isContainer()
98 && testIdentifier.getSource().filter( ClassSource.class::isInstance ).isPresent();
99
100 boolean isTest = testIdentifier.isTest();
101
102 boolean failed = testExecutionResult.getStatus() == FAILED;
103
104 boolean isAssertionError = testExecutionResult.getThrowable()
105 .filter( AssertionError.class::isInstance ).isPresent();
106
107 boolean isRootContainer = testIdentifier.isContainer() && !testIdentifier.getParentId().isPresent();
108
109 if ( failed || isClass || isTest )
110 {
111 Integer elapsed = computeElapsedTime( testIdentifier );
112 switch ( testExecutionResult.getStatus() )
113 {
114 case ABORTED:
115 if ( isTest )
116 {
117 runListener.testAssumptionFailure(
118 createReportEntry( testIdentifier, testExecutionResult, elapsed ) );
119 }
120 else
121 {
122 runListener.testSetCompleted( createReportEntry( testIdentifier, testExecutionResult,
123 systemProps(), null, elapsed ) );
124 }
125 break;
126 case FAILED:
127 if ( isAssertionError )
128 {
129 runListener.testFailed( createReportEntry( testIdentifier, testExecutionResult, elapsed ) );
130 }
131 else
132 {
133 runListener.testError( createReportEntry( testIdentifier, testExecutionResult, elapsed ) );
134 }
135 if ( isClass || isRootContainer )
136 {
137 runListener.testSetCompleted( createReportEntry( testIdentifier, null,
138 systemProps(), null, elapsed ) );
139 }
140 failures.put( testIdentifier, testExecutionResult );
141 break;
142 default:
143 if ( isTest )
144 {
145 runListener.testSucceeded( createReportEntry( testIdentifier, null, elapsed ) );
146 }
147 else
148 {
149 runListener.testSetCompleted(
150 createReportEntry( testIdentifier, null, systemProps(), null, elapsed ) );
151 }
152 }
153 }
154 }
155
156 private Integer computeElapsedTime( TestIdentifier testIdentifier )
157 {
158 Long startTime = testStartTime.remove( testIdentifier );
159 long endTime = System.currentTimeMillis();
160 return startTime == null ? null : (int) ( endTime - startTime );
161 }
162
163 @Override
164 public void executionSkipped( TestIdentifier testIdentifier, String reason )
165 {
166 testStartTime.remove( testIdentifier );
167 runListener.testSkipped( createReportEntry( testIdentifier, null, emptyMap(), reason, null ) );
168 }
169
170 private SimpleReportEntry createReportEntry( TestIdentifier testIdentifier,
171 TestExecutionResult testExecutionResult,
172 Map<String, String> systemProperties,
173 String reason,
174 Integer elapsedTime )
175 {
176 String[] classMethodName = toClassMethodName( testIdentifier );
177 String className = classMethodName[0];
178 String classText = classMethodName[1];
179 if ( Objects.equals( className, classText ) )
180 {
181 classText = null;
182 }
183 boolean failed = testExecutionResult != null && testExecutionResult.getStatus() == FAILED;
184 String methodName = failed || testIdentifier.isTest() ? classMethodName[2] : null;
185 String methodText = failed || testIdentifier.isTest() ? classMethodName[3] : null;
186 if ( Objects.equals( methodName, methodText ) )
187 {
188 methodText = null;
189 }
190 StackTraceWriter stw =
191 testExecutionResult == null ? null : toStackTraceWriter( className, methodName, testExecutionResult );
192 return new SimpleReportEntry( className, classText, methodName, methodText,
193 stw, elapsedTime, reason, systemProperties );
194 }
195
196 private SimpleReportEntry createReportEntry( TestIdentifier testIdentifier )
197 {
198 return createReportEntry( testIdentifier, null, null );
199 }
200
201 private SimpleReportEntry createReportEntry( TestIdentifier testIdentifier,
202 TestExecutionResult testExecutionResult, Integer elapsedTime )
203 {
204 return createReportEntry( testIdentifier, testExecutionResult, emptyMap(), null, elapsedTime );
205 }
206
207 private StackTraceWriter toStackTraceWriter( String realClassName, String realMethodName,
208 TestExecutionResult testExecutionResult )
209 {
210 switch ( testExecutionResult.getStatus() )
211 {
212 case ABORTED:
213 case FAILED:
214
215 Throwable exception = testExecutionResult.getThrowable().orElse( null );
216 return toStackTraceWriter( realClassName, realMethodName, exception );
217 default:
218 return testExecutionResult.getThrowable()
219 .map( t -> toStackTraceWriter( realClassName, realMethodName, t ) )
220 .orElse( null );
221 }
222 }
223
224 private StackTraceWriter toStackTraceWriter( String realClassName, String realMethodName, Throwable throwable )
225 {
226 return new PojoStackTraceWriter( realClassName, realMethodName, throwable );
227 }
228
229
230
231
232
233
234
235
236
237
238
239
240 private String[] toClassMethodName( TestIdentifier testIdentifier )
241 {
242 Optional<TestSource> testSource = testIdentifier.getSource();
243 String display = testIdentifier.getDisplayName();
244
245 if ( testSource.filter( MethodSource.class::isInstance ).isPresent() )
246 {
247 MethodSource methodSource = testSource.map( MethodSource.class::cast ).get();
248 String realClassName = methodSource.getClassName();
249
250 String[] source = testPlan.getParent( testIdentifier )
251 .map( this::toClassMethodName )
252 .map( s -> new String[] { s[0], s[1] } )
253 .orElse( new String[] { realClassName, realClassName } );
254
255 String simpleClassNames = COMMA_PATTERN.splitAsStream( methodSource.getMethodParameterTypes() )
256 .map( s -> s.substring( 1 + s.lastIndexOf( '.' ) ) )
257 .collect( joining( "," ) );
258
259 boolean hasParams = isNotBlank( methodSource.getMethodParameterTypes() );
260 String methodName = methodSource.getMethodName();
261 String description = testIdentifier.getLegacyReportingName();
262 String methodSign = hasParams ? methodName + '(' + simpleClassNames + ')' : methodName;
263 boolean equalDescriptions = display.equals( description );
264 boolean hasLegacyDescription = description.startsWith( methodName + '(' );
265 boolean hasDisplayName = !equalDescriptions || !hasLegacyDescription;
266 String methodDesc = equalDescriptions || !hasParams ? methodSign : description;
267 String methodDisp = hasDisplayName ? display : methodDesc;
268
269
270
271
272
273
274
275
276 return new String[] {source[0], source[1], methodDesc, methodDisp};
277 }
278 else if ( testSource.filter( ClassSource.class::isInstance ).isPresent() )
279 {
280 ClassSource classSource = testSource.map( ClassSource.class::cast ).get();
281 String className = classSource.getClassName();
282 String simpleClassName = className.substring( 1 + className.lastIndexOf( '.' ) );
283 String source = display.equals( simpleClassName ) ? className : display;
284 return new String[] {className, source, null, null};
285 }
286 else
287 {
288 String source = testPlan.getParent( testIdentifier )
289 .map( TestIdentifier::getDisplayName ).orElse( display );
290 return new String[] {source, source, display, display};
291 }
292 }
293
294
295
296
297 Map<TestIdentifier, TestExecutionResult> getFailures()
298 {
299 return failures;
300 }
301
302 boolean hasFailingTests()
303 {
304 return !getFailures().isEmpty();
305 }
306
307 void reset()
308 {
309 getFailures().clear();
310 testPlan = null;
311 }
312 }