11package io .cucumber .core .plugin ;
22
33import io .cucumber .messages .types .Envelope ;
4+ import io .cucumber .messages .types .Exception ;
5+ import io .cucumber .messages .types .Location ;
46import io .cucumber .messages .types .Snippet ;
57import io .cucumber .messages .types .Suggestion ;
8+ import io .cucumber .messages .types .TestCaseFinished ;
9+ import io .cucumber .messages .types .TestStepFinished ;
10+ import io .cucumber .messages .types .TestStepResult ;
11+ import io .cucumber .messages .types .TestStepResultStatus ;
612import io .cucumber .plugin .ColorAware ;
713import io .cucumber .plugin .ConcurrentEventListener ;
814import io .cucumber .plugin .event .EventPublisher ;
1218
1319import java .io .OutputStream ;
1420import java .io .PrintStream ;
21+ import java .text .DecimalFormat ;
22+ import java .text .DecimalFormatSymbols ;
1523import java .util .Collection ;
24+ import java .util .Collections ;
1625import java .util .LinkedHashSet ;
1726import java .util .List ;
1827import java .util .Locale ;
28+ import java .util .Map ;
1929import java .util .Optional ;
2030import java .util .Set ;
31+ import java .util .stream .Collector ;
2132import java .util .stream .Collectors ;
2233
34+ import static io .cucumber .core .plugin .Formats .ansi ;
35+ import static io .cucumber .core .plugin .Formats .monochrome ;
2336import static io .cucumber .query .Repository .RepositoryFeature .INCLUDE_GHERKIN_DOCUMENTS ;
2437import static io .cucumber .query .Repository .RepositoryFeature .INCLUDE_SUGGESTIONS ;
38+ import static java .util .Collections .emptyList ;
39+ import static java .util .Locale .ROOT ;
40+ import static java .util .concurrent .TimeUnit .SECONDS ;
41+ import static java .util .stream .Collectors .counting ;
42+ import static java .util .stream .Collectors .groupingBy ;
2543
2644public final class DefaultSummaryPrinter implements ColorAware , ConcurrentEventListener {
45+ private static final long ONE_SECOND = SECONDS .toNanos (1 );
46+ private static final long ONE_MINUTE = 60 * ONE_SECOND ;
2747
2848 private final Repository repository = Repository .builder ()
2949 .feature (INCLUDE_GHERKIN_DOCUMENTS , true )
3050 .feature (INCLUDE_SUGGESTIONS , true )
3151 .build ();
3252 private final Query query = new Query (repository );
3353
34- private final Stats stats ;
3554 private final PrintStream out ;
55+ private final Locale locale ;
56+ private Formats formats = ansi ();
3657
3758 public DefaultSummaryPrinter () {
3859 this (System .out , Locale .getDefault ());
3960 }
4061
4162 DefaultSummaryPrinter (OutputStream out , Locale locale ) {
4263 this .out = new PrintStream (out );
43- this .stats = new Stats ( query , locale ) ;
64+ this .locale = locale ;
4465 }
4566
4667 @ Override
@@ -58,18 +79,125 @@ private void print() {
5879 }
5980
6081 private void printStats () {
61- stats .printStats (out );
82+ printNonPassingScenarios ();
83+ printScenarioCounts ();
84+ printStepCounts ();
85+ printDuration ();
6286 out .println ();
6387 }
88+
89+ private void printNonPassingScenarios () {
90+ Map <TestStepResultStatus , List <TestCaseFinished >> testCaseFinishedByStatus = query
91+ .findAllTestCaseFinished ()
92+ .stream ()
93+ .collect (groupingBy (this ::getTestStepResultStatusBy ));
94+
95+ printScenarios (testCaseFinishedByStatus , TestStepResultStatus .FAILED );
96+ printScenarios (testCaseFinishedByStatus , TestStepResultStatus .AMBIGUOUS );
97+ printScenarios (testCaseFinishedByStatus , TestStepResultStatus .PENDING );
98+ printScenarios (testCaseFinishedByStatus , TestStepResultStatus .UNDEFINED );
99+ }
100+
101+ private void printScenarios (
102+ Map <TestStepResultStatus , List <TestCaseFinished >> testCaseFinishedByStatus ,
103+ TestStepResultStatus type
104+ ) {
105+ List <TestCaseFinished > scenarios = testCaseFinishedByStatus .getOrDefault (type , emptyList ());
106+ Format format = formats .get (type .name ().toLowerCase (ROOT ));
107+ if (!scenarios .isEmpty ()) {
108+ out .println (format .text (firstLetterCapitalizedName (type ) + " scenarios:" ));
109+ }
110+ for (TestCaseFinished testCaseFinished : scenarios ) {
111+ query .findPickleBy (testCaseFinished ).ifPresent (pickle -> {
112+ String location = pickle .getUri () + query .findLocationOf (pickle ).map (Location ::getLine ).map (line -> ":" + line ).orElse ("" );
113+ out .println (location + " # " + pickle .getName ());
114+ });
115+ }
116+ if (!scenarios .isEmpty ()) {
117+ out .println ();
118+ }
119+ }
120+
121+ private void printScenarioCounts () {
122+ List <TestCaseFinished > allTestCaseFinished = query .findAllTestCaseFinished ();
123+ if (allTestCaseFinished .isEmpty ()) {
124+ out .println ("0 Scenarios" );
125+ return ;
126+ }
127+ Map <TestStepResultStatus , Long > scenarioSubCounts = allTestCaseFinished
128+ .stream ()
129+ .collect (countTestStepResultStatusByTestCaseFinished ());
130+
131+ out .print (allTestCaseFinished .size ());
132+ out .print (" Scenarios (" );
133+ printSubCounts (scenarioSubCounts );
134+ out .println (")" );
135+ }
136+
137+
138+ private void printStepCounts () {
139+ List <TestStepFinished > testStepsFinished = query .findAllTestStepFinished ();
140+ if (testStepsFinished .isEmpty ()) {
141+ out .println ("0 Steps" );
142+ return ;
143+ }
144+
145+ Map <TestStepResultStatus , Long > testStepResultStatus = testStepsFinished .stream ()
146+ .collect (countTestStepResultStatusByTestStepFinished ());
147+
148+ out .print (testStepsFinished .size ());
149+ out .print (" Steps (" );
150+ printSubCounts (testStepResultStatus );
151+ out .println (")" );
152+ }
153+
154+ private void printSubCounts (Map <TestStepResultStatus , Long > subCounts ) {
155+ boolean addComma = false ;
156+ addComma = printSubCount (out , subCounts , TestStepResultStatus .FAILED , addComma );
157+ addComma = printSubCount (out , subCounts , TestStepResultStatus .AMBIGUOUS , addComma );
158+ addComma = printSubCount (out , subCounts , TestStepResultStatus .SKIPPED , addComma );
159+ addComma = printSubCount (out , subCounts , TestStepResultStatus .PENDING , addComma );
160+ addComma = printSubCount (out , subCounts , TestStepResultStatus .UNDEFINED , addComma );
161+ addComma = printSubCount (out , subCounts , TestStepResultStatus .PASSED , addComma );
162+ }
163+
164+ private boolean printSubCount (
165+ PrintStream out , Map <TestStepResultStatus , Long > subCounts , TestStepResultStatus type , boolean addComma
166+ ) {
167+ long count = subCounts .getOrDefault (type , 0L );
168+ if (count != 0 ) {
169+ if (addComma ) {
170+ out .print (", " );
171+ }
172+ Format format = formats .get (type .name ().toLowerCase (ROOT ));
173+ out .print (format .text (count + " " + type .name ().toLowerCase (ROOT )));
174+ addComma = true ;
175+ }
176+ return addComma ;
177+ }
178+
179+ private void printDuration () {
180+ query .findTestRunDuration ().ifPresent (duration -> {
181+ out .printf ("%dm" , (duration .toNanos () / ONE_MINUTE ));
182+ DecimalFormat format = new DecimalFormat ("0.000" , new DecimalFormatSymbols (locale ));
183+ out .println (format .format (((double ) (duration .toNanos () % ONE_MINUTE ) / ONE_SECOND )) + "s" );
184+ });
185+ }
64186
65187 private void printErrors () {
66- List <Throwable > errors = stats .getErrors ();
188+ List <Exception > errors = query .findAllTestStepFinished ()
189+ .stream ()
190+ .map (TestStepFinished ::getTestStepResult )
191+ .map (TestStepResult ::getException )
192+ .filter (Optional ::isPresent )
193+ .map (Optional ::get )
194+ .collect (Collectors .toList ());
67195 if (errors .isEmpty ()) {
68196 return ;
69197 }
70198 out .println ();
71- for (Throwable error : errors ) {
72- error .printStackTrace ( out );
199+ for (Exception error : errors ) {
200+ out . println ( error .getStackTrace () );
73201 out .println ();
74202 }
75203 }
@@ -98,9 +226,33 @@ private void printSnippets() {
98226 }
99227 }
100228
229+ private Collector <TestCaseFinished , ?, Map <TestStepResultStatus , Long >> countTestStepResultStatusByTestCaseFinished () {
230+ return groupingBy (this ::getTestStepResultStatusBy , counting ());
231+ }
232+
233+ private TestStepResultStatus getTestStepResultStatusBy (TestCaseFinished testCaseFinished ) {
234+ return query .findMostSevereTestStepResultBy (testCaseFinished )
235+ .map (TestStepResult ::getStatus )
236+ // By definition
237+ .orElse (TestStepResultStatus .PASSED );
238+ }
239+
240+ private static Collector <TestStepFinished , ?, Map <TestStepResultStatus , Long >> countTestStepResultStatusByTestStepFinished () {
241+ return groupingBy (DefaultSummaryPrinter ::getTestStepResultStatusBy , counting ());
242+ }
243+
244+ private static TestStepResultStatus getTestStepResultStatusBy (TestStepFinished testStepFinished ) {
245+ return testStepFinished .getTestStepResult ().getStatus ();
246+ }
247+
248+ private String firstLetterCapitalizedName (TestStepResultStatus status ) {
249+ String name = status .name ();
250+ return name .charAt (0 ) + name .substring (1 ).toLowerCase (ROOT );
251+ }
252+
101253 @ Override
102254 public void setMonochrome (boolean monochrome ) {
103- stats . setMonochrome ( monochrome );
255+ formats = monochrome ? monochrome () : ansi ( );
104256 }
105257
106258}
0 commit comments