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