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