7
7
import io .cucumber .datatable .DataTableFormatter ;
8
8
import io .cucumber .docstring .DocString ;
9
9
import io .cucumber .docstring .DocStringFormatter ;
10
+ import io .cucumber .messages .types .Envelope ;
11
+ import io .cucumber .messages .types .Exception ;
12
+ import io .cucumber .messages .types .Group ;
13
+ import io .cucumber .messages .types .Location ;
14
+ import io .cucumber .messages .types .Pickle ;
15
+ import io .cucumber .messages .types .PickleStep ;
16
+ import io .cucumber .messages .types .PickleTag ;
17
+ import io .cucumber .messages .types .Scenario ;
18
+ import io .cucumber .messages .types .Step ;
19
+ import io .cucumber .messages .types .StepDefinition ;
20
+ import io .cucumber .messages .types .StepMatchArgument ;
21
+ import io .cucumber .messages .types .StepMatchArgumentsList ;
22
+ import io .cucumber .messages .types .TestCaseStarted ;
10
23
import io .cucumber .plugin .ColorAware ;
11
24
import io .cucumber .plugin .ConcurrentEventListener ;
12
- import io .cucumber .plugin .event .Argument ;
13
25
import io .cucumber .plugin .event .EmbedEvent ;
14
26
import io .cucumber .plugin .event .EventPublisher ;
15
27
import io .cucumber .plugin .event .PickleStepTestStep ;
16
- import io .cucumber .plugin .event .Result ;
17
28
import io .cucumber .plugin .event .StepArgument ;
18
- import io .cucumber .plugin .event .TestCase ;
19
- import io .cucumber .plugin .event .TestCaseStarted ;
20
29
import io .cucumber .plugin .event .TestRunFinished ;
21
30
import io .cucumber .plugin .event .TestStepFinished ;
22
31
import io .cucumber .plugin .event .WriteEvent ;
32
+ import io .cucumber .query .Lineage ;
33
+ import io .cucumber .query .Query ;
23
34
24
35
import java .io .BufferedReader ;
25
36
import java .io .File ;
28
39
import java .io .StringReader ;
29
40
import java .net .URI ;
30
41
import java .net .URISyntaxException ;
31
- import java .util .Comparator ;
42
+ import java .util .Collection ;
43
+ import java .util .Collections ;
32
44
import java .util .HashMap ;
33
45
import java .util .List ;
34
46
import java .util .Map ;
35
- import java .util .UUID ;
47
+ import java .util .Optional ;
48
+ import java .util .stream .Collectors ;
36
49
37
- import static io .cucumber .core .exception .ExceptionUtils .printStackTrace ;
38
50
import static io .cucumber .core .plugin .Formats .ansi ;
39
51
import static io .cucumber .core .plugin .Formats .monochrome ;
40
52
import static java .lang .Math .max ;
41
53
import static java .util .Locale .ROOT ;
54
+ import static java .util .stream .Collectors .joining ;
42
55
43
56
/**
44
57
* Prints a pretty report of the scenario execution as it happens.
@@ -53,33 +66,40 @@ public final class PrettyFormatter implements ConcurrentEventListener, ColorAwar
53
66
private static final String STEP_SCENARIO_INDENT = STEP_INDENT + " " ;
54
67
private static final String STACK_TRACE_INDENT = STEP_SCENARIO_INDENT + " " ;
55
68
56
- private final Map <UUID , Integer > commentStartIndex = new HashMap <>();
69
+ private final Map <String , Integer > commentStartIndexByTestCaseId = new HashMap <>();
57
70
58
71
private final UTF8PrintWriter out ;
59
72
private Formats formats = ansi ();
73
+ private final Query query = new Query ();
74
+ private final Map <String , StepDefinition > stepDefinitionsById = new HashMap <>();
60
75
61
76
public PrettyFormatter (OutputStream out ) {
62
77
this .out = new UTF8PrintWriter (out );
63
78
}
64
79
65
80
@ Override
66
81
public void setEventPublisher (EventPublisher publisher ) {
67
- publisher .registerHandlerFor (TestCaseStarted .class , this ::handleTestCaseStarted );
68
- publisher .registerHandlerFor (TestStepFinished .class , this ::handleTestStepFinished );
82
+ publisher .registerHandlerFor (Envelope .class , event -> {
83
+ query .update (event );
84
+ event .getStepDefinition ().ifPresent (stepDefinition -> stepDefinitionsById .put (stepDefinition .getId (), stepDefinition ));
85
+ event .getTestCaseStarted ().ifPresent (this ::handleTestCaseStarted );
86
+ event .getTestStepFinished ().ifPresent (this ::handleTestStepFinished );
87
+ });
88
+
69
89
publisher .registerHandlerFor (WriteEvent .class , this ::handleWrite );
70
90
publisher .registerHandlerFor (EmbedEvent .class , this ::handleEmbed );
71
91
publisher .registerHandlerFor (TestRunFinished .class , this ::handleTestRunFinished );
72
92
}
73
93
74
- private void handleTestCaseStarted (TestCaseStarted event ) {
94
+ private void handleTestCaseStarted (io . cucumber . messages . types . TestCaseStarted event ) {
75
95
out .println ();
76
96
preCalculateLocationIndent (event );
77
97
printTags (event );
78
98
printScenarioDefinition (event );
79
99
out .flush ();
80
100
}
81
101
82
- private void handleTestStepFinished (TestStepFinished event ) {
102
+ private void handleTestStepFinished (io . cucumber . messages . types . TestStepFinished event ) {
83
103
printStep (event );
84
104
printError (event );
85
105
out .flush ();
@@ -99,49 +119,104 @@ private void handleEmbed(EmbedEvent event) {
99
119
out .flush ();
100
120
}
101
121
102
- private void handleTestRunFinished (TestRunFinished event ) {
122
+ private void handleTestRunFinished (io . cucumber . messages . types . TestRunFinished event ) {
103
123
printError (event );
104
124
out .close ();
105
125
}
106
126
107
- private void preCalculateLocationIndent (TestCaseStarted event ) {
108
- TestCase testCase = event .getTestCase ();
109
- Integer longestStep = testCase .getTestSteps ().stream ()
110
- .filter (PickleStepTestStep .class ::isInstance )
111
- .map (PickleStepTestStep .class ::cast )
112
- .map (PickleStepTestStep ::getStep )
113
- .map (step -> formatPlainStep (step .getKeyword (), step .getText ()).length ())
114
- .max (Comparator .naturalOrder ())
115
- .orElse (0 );
116
-
117
- int scenarioLength = formatScenarioDefinition (testCase ).length ();
118
- commentStartIndex .put (testCase .getId (), max (longestStep , scenarioLength ) + 1 );
127
+ private void preCalculateLocationIndent (io .cucumber .messages .types .TestCaseStarted event ) {
128
+ query .findLineageBy (event )
129
+ .flatMap (Lineage ::scenario )
130
+ .ifPresent (scenario -> {
131
+ query .findPickleBy (event ).ifPresent (pickle -> {
132
+ int longestLine = calculateScenarioLineLength (pickle , scenario );
133
+
134
+ List <Step > steps = scenario .getSteps ();
135
+ List <PickleStep > pickleSteps = pickle .getSteps ();
136
+
137
+ int stepSize = pickleSteps .size ();
138
+ for (int i = 0 ; i < stepSize ; i ++) {
139
+ Step step = steps .get (i );
140
+ PickleStep pickleStep = pickleSteps .get (i );
141
+ longestLine = Math .max (longestLine , calculateStepLineLength (step , pickleStep ));
142
+ }
143
+
144
+ commentStartIndexByTestCaseId .put (event .getTestCaseId (), longestLine + 1 );
145
+ });
146
+ });
119
147
}
120
148
121
- private void printTags (TestCaseStarted event ) {
122
- List <String > tags = event .getTestCase ().getTags ();
123
- if (!tags .isEmpty ()) {
124
- out .println (SCENARIO_INDENT + String .join (" " , tags ));
125
- }
149
+ private static int calculateStepLineLength (Step step , PickleStep pickleStep ) {
150
+ String keyword = step .getKeyword ();
151
+ String text = pickleStep .getText ();
152
+ // The ": " add 2
153
+ return STEP_INDENT .length () + keyword .length () + text .length () + 2 ;
154
+ }
155
+
156
+ private static int calculateScenarioLineLength (Pickle pickle , Scenario scenario ) {
157
+ String pickleName = pickle .getName ();
158
+ String pickleKeyword = scenario .getKeyword ();
159
+ // The ": " add 2
160
+ return SCENARIO_INDENT .length () + pickleName .length () + pickleKeyword .length () + 2 ;
161
+ }
162
+
163
+ private void printTags (io .cucumber .messages .types .TestCaseStarted event ) {
164
+ query .findPickleBy (event )
165
+ .map (Pickle ::getTags )
166
+ .filter (pickleTags -> !pickleTags .isEmpty ())
167
+ .map (pickleTags -> pickleTags .stream ()
168
+ .map (PickleTag ::getName )
169
+ .collect (joining (" " )))
170
+ .ifPresent (tags -> out .println (SCENARIO_INDENT + tags ));
126
171
}
127
172
128
173
private void printScenarioDefinition (TestCaseStarted event ) {
129
- TestCase testCase = event .getTestCase ();
130
- String definitionText = formatScenarioDefinition (testCase );
131
- String path = relativize (testCase .getUri ()).getSchemeSpecificPart ();
132
- String locationIndent = calculateLocationIndent (event .getTestCase (), SCENARIO_INDENT + definitionText );
133
- out .println (SCENARIO_INDENT + definitionText + locationIndent
134
- + formatLocation (path + ":" + testCase .getLocation ().getLine ()));
174
+ query .findLineageBy (event )
175
+ .flatMap (Lineage ::scenario )
176
+ .ifPresent (scenario -> {
177
+ query .findPickleBy (event )
178
+ .ifPresent (pickle -> {
179
+ String definitionText = formatScenarioDefinition (scenario , pickle );
180
+ String path = relativize (pickle .getUri ()).getSchemeSpecificPart ();
181
+ String locationIndent = calculateLocationIndent (event .getTestCaseId (), definitionText );
182
+ String pathWithLine = query .findLocationOf (pickle ).map (Location ::getLine ).map (line -> path + ":" + line ).orElse (path );
183
+ out .println (definitionText + locationIndent + formatLocation (path + ":" + pathWithLine ));
184
+ });
185
+ });
186
+ }
187
+
188
+ private static String formatScenarioDefinition (Scenario scenario , Pickle pickle ) {
189
+ return SCENARIO_INDENT + scenario .getKeyword () + ": " + pickle .getName ();
135
190
}
136
191
137
- private void printStep (TestStepFinished event ) {
192
+ private void printStep (io .cucumber .messages .types .TestStepFinished event ) {
193
+ query .findTestStepBy (event )
194
+ .ifPresent (testStep -> {
195
+ query .findPickleStepBy (testStep ).ifPresent (pickleStep -> {
196
+ query .findStepBy (pickleStep ).ifPresent (step -> {
197
+ String keyword = step .getKeyword ();
198
+ String stepText = pickleStep .getText ();
199
+ // TODO: Use proper enum map.
200
+ String status = event .getTestStepResult ().getStatus ().toString ().toLowerCase (ROOT );
201
+ List <StepMatchArgument > stepMatchArgumentsLists = testStep .getStepMatchArgumentsLists ()
202
+ .map (stepMatchArgumentsLists1 -> stepMatchArgumentsLists1 .stream ().map (StepMatchArgumentsList ::getStepMatchArguments ).flatMap (Collection ::stream ).collect (Collectors .toList ()))
203
+ .orElseGet (Collections ::emptyList );// TODO: Create separate _arg map
204
+
205
+ String formattedStepText = STEP_INDENT + formatStepText (keyword , stepText , formats .get (status ), formats .get (status + "_arg" ), stepMatchArgumentsLists );
206
+ String locationComment = formatLocationComment (event , testStep , keyword , stepText );
207
+ out .println (formattedStepText + locationComment );
208
+ });
209
+ });
210
+ })
211
+
212
+
138
213
if (event .getTestStep () instanceof PickleStepTestStep ) {
139
214
PickleStepTestStep testStep = (PickleStepTestStep ) event .getTestStep ();
140
215
String keyword = testStep .getStep ().getKeyword ();
141
216
String stepText = testStep .getStep ().getText ();
142
217
String status = event .getResult ().getStatus ().name ().toLowerCase (ROOT );
143
218
String formattedStepText = formatStepText (keyword , stepText , formats .get (status ),
144
- formats .get (status + "_arg" ), testStep .getDefinitionArgument ());
219
+ formats .get (status + "_arg" ), testStep .getDefinitionArgument ());
145
220
String locationComment = formatLocationComment (event , testStep , keyword , stepText );
146
221
out .println (STEP_INDENT + formattedStepText + locationComment );
147
222
StepArgument stepArgument = testStep .getStep ().getArgument ();
@@ -165,7 +240,7 @@ private void printStep(TestStepFinished event) {
165
240
.build ();
166
241
DocStringArgument docStringArgument = (DocStringArgument ) stepArgument ;
167
242
DocString docString = DocString .create (docStringArgument .getContent (),
168
- docStringArgument .getContentType ());
243
+ docStringArgument .getContentType ());
169
244
try {
170
245
docStringFormatter .formatTo (docString , out );
171
246
} catch (IOException e ) {
@@ -186,26 +261,25 @@ private String formatLocationComment(
186
261
return locationIndent + formatLocation (codeLocation );
187
262
}
188
263
189
- private void printError (TestStepFinished event ) {
190
- Result result = event .getResult ();
191
- printError (STACK_TRACE_INDENT , result );
264
+ private void printError (io .cucumber .messages .types .TestStepFinished event ) {
265
+ event .getTestStepResult ()
266
+ .getException ()
267
+ .ifPresent (exception -> {
268
+ String name = event .getTestStepResult ().getStatus ().name ().toLowerCase (ROOT );
269
+ printError (STACK_TRACE_INDENT , exception , formats .get (name ));
270
+ });
192
271
}
193
272
194
- private void printError (TestRunFinished event ) {
195
- Result result = event .getResult ();
196
- printError (SCENARIO_INDENT , result );
273
+ private void printError (io . cucumber . messages . types . TestRunFinished event ) {
274
+ event .getException ()
275
+ . ifPresent ( exception -> printError (SCENARIO_INDENT , exception , formats . get ( "failed" )) );
197
276
}
198
277
199
- private void printError (String prefix , Result result ) {
200
- Throwable error = result .getError ();
201
- if (error != null ) {
202
- String name = result .getStatus ().name ().toLowerCase (ROOT );
203
- Format format = formats .get (name );
204
- String text = printStackTrace (error );
205
- // TODO: Java 12+ use String.indent
206
- String indented = text .replaceAll ("(\r \n |\r |\n )" , "$1" + prefix ).trim ();
207
- out .println (prefix + format .text (indented ));
208
- }
278
+ private void printError (String scenarioIndent , Exception exception , Format format ) {
279
+ String text = exception .getStackTrace ().orElse ("" );
280
+ // TODO: Java 12+ use String.indent
281
+ String indented = text .replaceAll ("(\r \n |\r |\n )" , "$1" + scenarioIndent ).trim ();
282
+ out .println (scenarioIndent + format .text (indented ));
209
283
}
210
284
211
285
private void printText (WriteEvent event ) {
@@ -235,10 +309,14 @@ private String formatPlainStep(String keyword, String stepText) {
235
309
return STEP_INDENT + keyword + stepText ;
236
310
}
237
311
238
- private String formatScenarioDefinition (TestCase testCase ) {
312
+ private String formatScenarioDefinition (io . cucumber . messages . types . TestCase testCase ) {
239
313
return testCase .getKeyword () + ": " + testCase .getName ();
240
314
}
241
315
316
+ static URI relativize (String uri ) {
317
+ return relativize (URI .create (uri ));
318
+ }
319
+
242
320
static URI relativize (URI uri ) {
243
321
if (!"file" .equals (uri .getScheme ())) {
244
322
return uri ;
@@ -257,8 +335,8 @@ static URI relativize(URI uri) {
257
335
}
258
336
}
259
337
260
- private String calculateLocationIndent (TestCase testStep , String prefix ) {
261
- Integer commentStartAt = commentStartIndex .getOrDefault (testStep . getId () , 0 );
338
+ private String calculateLocationIndent (String testCaseId , String prefix ) {
339
+ Integer commentStartAt = commentStartIndexByTestCaseId .getOrDefault (testCaseId , 0 );
262
340
int padding = commentStartAt - prefix .length ();
263
341
264
342
if (padding < 0 ) {
@@ -276,29 +354,27 @@ private String formatLocation(String location) {
276
354
}
277
355
278
356
String formatStepText (
279
- String keyword , String stepText , Format textFormat , Format argFormat , List <Argument > arguments
357
+ String keyword , String stepText , Format textFormat , Format argFormat , List <StepMatchArgument > arguments
280
358
) {
281
359
int beginIndex = 0 ;
282
360
StringBuilder result = new StringBuilder (textFormat .text (keyword ));
283
- for (Argument argument : arguments ) {
361
+ for (StepMatchArgument argument : arguments ) {
284
362
// can be null if the argument is missing.
285
- if (argument .getValue () != null ) {
286
- int argumentOffset = argument .getStart ();
363
+ Group group = argument .getGroup ();
364
+ Optional <String > value = group .getValue ();
365
+ if (value .isPresent ()) {
366
+ // TODO: Messages are silly
367
+ int argumentOffset = (int ) (long ) group .getStart ().orElse (-1L );
287
368
// a nested argument starts before the enclosing argument ends;
288
369
// ignore it when formatting
289
370
if (argumentOffset < beginIndex ) {
290
371
continue ;
291
372
}
292
373
String text = stepText .substring (beginIndex , argumentOffset );
293
374
result .append (textFormat .text (text ));
294
- }
295
- // val can be null if the argument isn't there, for example
296
- // @And("(it )?has something")
297
- if (argument .getValue () != null ) {
298
- String text = stepText .substring (argument .getStart (), argument .getEnd ());
299
- result .append (argFormat .text (text ));
300
- // set beginIndex to end of argument
301
- beginIndex = argument .getEnd ();
375
+ int argumentEndIndex = argumentOffset + value .get ().length ();
376
+ result .append (argFormat .text (stepText .substring (argumentOffset , argumentEndIndex )));
377
+ beginIndex = argumentEndIndex ;
302
378
}
303
379
}
304
380
if (beginIndex != stepText .length ()) {
0 commit comments