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