1 package org.apache.maven.plugin.surefire.report;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.maven.plugin.surefire.StartupReportConfiguration;
23 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
24 import org.apache.maven.plugin.surefire.log.api.Level;
25 import org.apache.maven.plugin.surefire.runorder.StatisticsReporter;
26 import org.apache.maven.surefire.shared.utils.logging.MessageBuilder;
27 import org.apache.maven.surefire.extensions.ConsoleOutputReportEventListener;
28 import org.apache.maven.surefire.extensions.StatelessReportEventListener;
29 import org.apache.maven.surefire.extensions.StatelessTestsetInfoConsoleReportEventListener;
30 import org.apache.maven.surefire.extensions.StatelessTestsetInfoFileReportEventListener;
31 import org.apache.maven.surefire.api.report.ReporterFactory;
32 import org.apache.maven.surefire.api.report.RunListener;
33 import org.apache.maven.surefire.report.RunStatistics;
34 import org.apache.maven.surefire.api.report.StackTraceWriter;
35 import org.apache.maven.surefire.api.suite.RunResult;
36
37 import java.io.File;
38 import java.util.ArrayList;
39 import java.util.Collection;
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.TreeMap;
44 import java.util.concurrent.ConcurrentLinkedQueue;
45
46 import static org.apache.maven.plugin.surefire.log.api.Level.resolveLevel;
47 import static org.apache.maven.plugin.surefire.report.ConsoleReporter.PLAIN;
48 import static org.apache.maven.plugin.surefire.report.DefaultReporterFactory.TestResultType.error;
49 import static org.apache.maven.plugin.surefire.report.DefaultReporterFactory.TestResultType.failure;
50 import static org.apache.maven.plugin.surefire.report.DefaultReporterFactory.TestResultType.flake;
51 import static org.apache.maven.plugin.surefire.report.DefaultReporterFactory.TestResultType.skipped;
52 import static org.apache.maven.plugin.surefire.report.DefaultReporterFactory.TestResultType.success;
53 import static org.apache.maven.plugin.surefire.report.DefaultReporterFactory.TestResultType.unknown;
54 import static org.apache.maven.plugin.surefire.report.ReportEntryType.ERROR;
55 import static org.apache.maven.plugin.surefire.report.ReportEntryType.FAILURE;
56 import static org.apache.maven.plugin.surefire.report.ReportEntryType.SUCCESS;
57 import static org.apache.maven.surefire.shared.utils.logging.MessageUtils.buffer;
58 import static org.apache.maven.surefire.api.util.internal.ObjectUtils.useNonNull;
59
60
61
62
63
64
65
66
67 public class DefaultReporterFactory
68 implements ReporterFactory
69 {
70 private final Collection<TestSetRunListener> listeners = new ConcurrentLinkedQueue<>();
71 private final StartupReportConfiguration reportConfiguration;
72 private final ConsoleLogger consoleLogger;
73 private final Integer forkNumber;
74
75 private RunStatistics globalStats = new RunStatistics();
76
77
78 private Map<String, List<TestMethodStats>> flakyTests;
79
80
81 private Map<String, List<TestMethodStats>> failedTests;
82
83
84 private Map<String, List<TestMethodStats>> errorTests;
85
86 public DefaultReporterFactory( StartupReportConfiguration reportConfiguration, ConsoleLogger consoleLogger )
87 {
88 this( reportConfiguration, consoleLogger, null );
89 }
90
91 public DefaultReporterFactory( StartupReportConfiguration reportConfiguration, ConsoleLogger consoleLogger,
92 Integer forkNumber )
93 {
94 this.reportConfiguration = reportConfiguration;
95 this.consoleLogger = consoleLogger;
96 this.forkNumber = forkNumber;
97 }
98
99 @Override
100 public RunListener createReporter()
101 {
102 TestSetRunListener testSetRunListener =
103 new TestSetRunListener( createConsoleReporter(),
104 createFileReporter(),
105 createSimpleXMLReporter(),
106 createConsoleOutputReceiver(),
107 createStatisticsReporter(),
108 reportConfiguration.isTrimStackTrace(),
109 PLAIN.equals( reportConfiguration.getReportFormat() ),
110 reportConfiguration.isBriefOrPlainFormat() );
111 addListener( testSetRunListener );
112 return testSetRunListener;
113 }
114
115 public File getReportsDirectory()
116 {
117 return reportConfiguration.getReportsDirectory();
118 }
119
120 private StatelessTestsetInfoConsoleReportEventListener<WrappedReportEntry, TestSetStats> createConsoleReporter()
121 {
122 StatelessTestsetInfoConsoleReportEventListener<WrappedReportEntry, TestSetStats> consoleReporter =
123 reportConfiguration.instantiateConsoleReporter( consoleLogger );
124 return useNonNull( consoleReporter, NullConsoleReporter.INSTANCE );
125 }
126
127 private StatelessTestsetInfoFileReportEventListener<WrappedReportEntry, TestSetStats> createFileReporter()
128 {
129 StatelessTestsetInfoFileReportEventListener<WrappedReportEntry, TestSetStats> fileReporter =
130 reportConfiguration.instantiateFileReporter( forkNumber );
131 return useNonNull( fileReporter, NullFileReporter.INSTANCE );
132 }
133
134 private StatelessReportEventListener<WrappedReportEntry, TestSetStats> createSimpleXMLReporter()
135 {
136 StatelessReportEventListener<WrappedReportEntry, TestSetStats> xmlReporter =
137 reportConfiguration.instantiateStatelessXmlReporter( forkNumber );
138 return useNonNull( xmlReporter, NullStatelessXmlReporter.INSTANCE );
139 }
140
141 private ConsoleOutputReportEventListener createConsoleOutputReceiver()
142 {
143 ConsoleOutputReportEventListener outputReporter =
144 reportConfiguration.instantiateConsoleOutputFileReporter( forkNumber );
145 return useNonNull( outputReporter, NullConsoleOutputReceiver.INSTANCE );
146 }
147
148 private StatisticsReporter createStatisticsReporter()
149 {
150 StatisticsReporter statisticsReporter = reportConfiguration.getStatisticsReporter();
151 return useNonNull( statisticsReporter, NullStatisticsReporter.INSTANCE );
152 }
153
154 public void mergeFromOtherFactories( Collection<DefaultReporterFactory> factories )
155 {
156 for ( DefaultReporterFactory factory : factories )
157 {
158 listeners.addAll( factory.listeners );
159 }
160 }
161
162 final void addListener( TestSetRunListener listener )
163 {
164 listeners.add( listener );
165 }
166
167 @Override
168 public RunResult close()
169 {
170 mergeTestHistoryResult();
171 runCompleted();
172 for ( TestSetRunListener listener : listeners )
173 {
174 listener.close();
175 }
176 return globalStats.getRunResult();
177 }
178
179 public void runStarting()
180 {
181 if ( reportConfiguration.isPrintSummary() )
182 {
183 log( "" );
184 log( "-------------------------------------------------------" );
185 log( " T E S T S" );
186 log( "-------------------------------------------------------" );
187 }
188 }
189
190 private void runCompleted()
191 {
192 if ( reportConfiguration.isPrintSummary() )
193 {
194 log( "" );
195 log( "Results:" );
196 log( "" );
197 }
198 boolean printedFailures = printTestFailures( failure );
199 boolean printedErrors = printTestFailures( error );
200 boolean printedFlakes = printTestFailures( flake );
201 if ( reportConfiguration.isPrintSummary() )
202 {
203 if ( printedFailures | printedErrors | printedFlakes )
204 {
205 log( "" );
206 }
207 boolean hasSuccessful = globalStats.getCompletedCount() > 0;
208 boolean hasSkipped = globalStats.getSkipped() > 0;
209 log( globalStats.getSummary(), hasSuccessful, printedFailures, printedErrors, hasSkipped, printedFlakes );
210 log( "" );
211 }
212 }
213
214 public RunStatistics getGlobalRunStatistics()
215 {
216 mergeTestHistoryResult();
217 return globalStats;
218 }
219
220
221
222
223
224
225
226
227
228
229 static TestResultType getTestResultType( List<ReportEntryType> reportEntries, int rerunFailingTestsCount )
230 {
231 if ( reportEntries == null || reportEntries.isEmpty() )
232 {
233 return unknown;
234 }
235
236 boolean seenSuccess = false, seenFailure = false, seenError = false;
237 for ( ReportEntryType resultType : reportEntries )
238 {
239 if ( resultType == SUCCESS )
240 {
241 seenSuccess = true;
242 }
243 else if ( resultType == FAILURE )
244 {
245 seenFailure = true;
246 }
247 else if ( resultType == ERROR )
248 {
249 seenError = true;
250 }
251 }
252
253 if ( seenFailure || seenError )
254 {
255 if ( seenSuccess && rerunFailingTestsCount > 0 )
256 {
257 return flake;
258 }
259 else
260 {
261 return seenError ? error : failure;
262 }
263 }
264 else if ( seenSuccess )
265 {
266 return success;
267 }
268 else
269 {
270 return skipped;
271 }
272 }
273
274
275
276
277
278 private void mergeTestHistoryResult()
279 {
280 globalStats = new RunStatistics();
281 flakyTests = new TreeMap<>();
282 failedTests = new TreeMap<>();
283 errorTests = new TreeMap<>();
284
285 Map<String, List<TestMethodStats>> mergedTestHistoryResult = new HashMap<>();
286
287 for ( TestSetRunListener listener : listeners )
288 {
289 for ( TestMethodStats methodStats : listener.getTestMethodStats() )
290 {
291 List<TestMethodStats> currentMethodStats =
292 mergedTestHistoryResult.get( methodStats.getTestClassMethodName() );
293 if ( currentMethodStats == null )
294 {
295 currentMethodStats = new ArrayList<>();
296 currentMethodStats.add( methodStats );
297 mergedTestHistoryResult.put( methodStats.getTestClassMethodName(), currentMethodStats );
298 }
299 else
300 {
301 currentMethodStats.add( methodStats );
302 }
303 }
304 }
305
306
307 int completedCount = 0, skipped = 0;
308
309 for ( Map.Entry<String, List<TestMethodStats>> entry : mergedTestHistoryResult.entrySet() )
310 {
311 List<TestMethodStats> testMethodStats = entry.getValue();
312 String testClassMethodName = entry.getKey();
313 completedCount++;
314
315 List<ReportEntryType> resultTypes = new ArrayList<>();
316 for ( TestMethodStats methodStats : testMethodStats )
317 {
318 resultTypes.add( methodStats.getResultType() );
319 }
320
321 switch ( getTestResultType( resultTypes, reportConfiguration.getRerunFailingTestsCount() ) )
322 {
323 case success:
324
325 int successCount = 0;
326 for ( ReportEntryType type : resultTypes )
327 {
328 if ( type == SUCCESS )
329 {
330 successCount++;
331 }
332 }
333 completedCount += successCount - 1;
334 break;
335 case skipped:
336 skipped++;
337 break;
338 case flake:
339 flakyTests.put( testClassMethodName, testMethodStats );
340 break;
341 case failure:
342 failedTests.put( testClassMethodName, testMethodStats );
343 break;
344 case error:
345 errorTests.put( testClassMethodName, testMethodStats );
346 break;
347 default:
348 throw new IllegalStateException( "Get unknown test result type" );
349 }
350 }
351
352 globalStats.set( completedCount, errorTests.size(), failedTests.size(), skipped, flakyTests.size() );
353 }
354
355
356
357
358
359
360
361
362
363 boolean printTestFailures( TestResultType type )
364 {
365 final Map<String, List<TestMethodStats>> testStats;
366 final Level level;
367 switch ( type )
368 {
369 case failure:
370 testStats = failedTests;
371 level = Level.FAILURE;
372 break;
373 case error:
374 testStats = errorTests;
375 level = Level.FAILURE;
376 break;
377 case flake:
378 testStats = flakyTests;
379 level = Level.UNSTABLE;
380 break;
381 default:
382 return false;
383 }
384
385 boolean printed = false;
386 if ( !testStats.isEmpty() )
387 {
388 log( type.getLogPrefix(), level );
389 printed = true;
390 }
391
392 for ( Map.Entry<String, List<TestMethodStats>> entry : testStats.entrySet() )
393 {
394 List<TestMethodStats> testMethodStats = entry.getValue();
395 if ( testMethodStats.size() == 1 )
396 {
397
398 failure( " " + testMethodStats.get( 0 ).getStackTraceWriter().smartTrimmedStackTrace() );
399 }
400 else
401 {
402 log( entry.getKey(), level );
403 for ( int i = 0; i < testMethodStats.size(); i++ )
404 {
405 StackTraceWriter failureStackTrace = testMethodStats.get( i ).getStackTraceWriter();
406 if ( failureStackTrace == null )
407 {
408 success( " Run " + ( i + 1 ) + ": PASS" );
409 }
410 else
411 {
412 failure( " Run " + ( i + 1 ) + ": " + failureStackTrace.smartTrimmedStackTrace() );
413 }
414 }
415 log( "" );
416 }
417 }
418 return printed;
419 }
420
421
422 enum TestResultType
423 {
424
425 error( "Errors: " ),
426 failure( "Failures: " ),
427 flake( "Flakes: " ),
428 success( "Success: " ),
429 skipped( "Skipped: " ),
430 unknown( "Unknown: " );
431
432 private final String logPrefix;
433
434 TestResultType( String logPrefix )
435 {
436 this.logPrefix = logPrefix;
437 }
438
439 public String getLogPrefix()
440 {
441 return logPrefix;
442 }
443 }
444
445 private void log( String s, boolean success, boolean failures, boolean errors, boolean skipped, boolean flakes )
446 {
447 Level level = resolveLevel( success, failures, errors, skipped, flakes );
448 log( s, level );
449 }
450
451 private void log( String s, Level level )
452 {
453 switch ( level )
454 {
455 case FAILURE:
456 failure( s );
457 break;
458 case UNSTABLE:
459 warning( s );
460 break;
461 case SUCCESS:
462 success( s );
463 break;
464 default:
465 info( s );
466 }
467 }
468
469 private void log( String s )
470 {
471 consoleLogger.info( s );
472 }
473
474 private void info( String s )
475 {
476 MessageBuilder builder = buffer();
477 consoleLogger.info( builder.a( s ).toString() );
478 }
479
480 private void warning( String s )
481 {
482 MessageBuilder builder = buffer();
483 consoleLogger.warning( builder.warning( s ).toString() );
484 }
485
486 private void success( String s )
487 {
488 MessageBuilder builder = buffer();
489 consoleLogger.info( builder.success( s ).toString() );
490 }
491
492 private void failure( String s )
493 {
494 MessageBuilder builder = buffer();
495 consoleLogger.error( builder.failure( s ).toString() );
496 }
497 }