cucumber 3.2.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +166 -19
  3. data/CONTRIBUTING.md +2 -18
  4. data/README.md +4 -5
  5. data/bin/cucumber +1 -1
  6. data/lib/autotest/cucumber_mixin.rb +34 -39
  7. data/lib/cucumber/cli/configuration.rb +5 -5
  8. data/lib/cucumber/cli/main.rb +12 -12
  9. data/lib/cucumber/cli/options.rb +62 -71
  10. data/lib/cucumber/cli/profile_loader.rb +49 -26
  11. data/lib/cucumber/configuration.rb +31 -23
  12. data/lib/cucumber/constantize.rb +2 -5
  13. data/lib/cucumber/deprecate.rb +31 -7
  14. data/lib/cucumber/errors.rb +5 -7
  15. data/lib/cucumber/events/envelope.rb +9 -0
  16. data/lib/cucumber/events/gherkin_source_parsed.rb +11 -0
  17. data/lib/cucumber/events/hook_test_step_created.rb +13 -0
  18. data/lib/cucumber/events/step_activated.rb +2 -1
  19. data/lib/cucumber/events/test_case_created.rb +13 -0
  20. data/lib/cucumber/events/test_case_ready.rb +12 -0
  21. data/lib/cucumber/events/test_step_created.rb +13 -0
  22. data/lib/cucumber/events/undefined_parameter_type.rb +10 -0
  23. data/lib/cucumber/events.rb +13 -6
  24. data/lib/cucumber/file_specs.rb +6 -6
  25. data/lib/cucumber/filters/activate_steps.rb +5 -3
  26. data/lib/cucumber/filters/broadcast_test_case_ready_event.rb +12 -0
  27. data/lib/cucumber/filters/prepare_world.rb +5 -9
  28. data/lib/cucumber/filters/quit.rb +1 -3
  29. data/lib/cucumber/filters/tag_limits/verifier.rb +2 -4
  30. data/lib/cucumber/filters.rb +1 -0
  31. data/lib/cucumber/formatter/ansicolor.rb +40 -45
  32. data/lib/cucumber/formatter/ast_lookup.rb +165 -0
  33. data/lib/cucumber/formatter/backtrace_filter.rb +9 -8
  34. data/lib/cucumber/formatter/console.rb +58 -66
  35. data/lib/cucumber/formatter/console_counts.rb +4 -9
  36. data/lib/cucumber/formatter/console_issues.rb +6 -3
  37. data/lib/cucumber/formatter/duration.rb +1 -1
  38. data/lib/cucumber/formatter/duration_extractor.rb +3 -1
  39. data/lib/cucumber/formatter/errors.rb +6 -0
  40. data/lib/cucumber/formatter/fanout.rb +2 -0
  41. data/lib/cucumber/formatter/html.rb +11 -598
  42. data/lib/cucumber/formatter/http_io.rb +1 -1
  43. data/lib/cucumber/formatter/ignore_missing_messages.rb +1 -1
  44. data/lib/cucumber/formatter/interceptor.rb +8 -28
  45. data/lib/cucumber/formatter/io.rb +1 -1
  46. data/lib/cucumber/formatter/json.rb +101 -115
  47. data/lib/cucumber/formatter/junit.rb +56 -56
  48. data/lib/cucumber/formatter/message.rb +22 -0
  49. data/lib/cucumber/formatter/message_builder.rb +255 -0
  50. data/lib/cucumber/formatter/pretty.rb +359 -153
  51. data/lib/cucumber/formatter/progress.rb +30 -32
  52. data/lib/cucumber/formatter/query/hook_by_test_step.rb +31 -0
  53. data/lib/cucumber/formatter/query/pickle_by_test.rb +26 -0
  54. data/lib/cucumber/formatter/query/pickle_step_by_test_step.rb +26 -0
  55. data/lib/cucumber/formatter/query/step_definitions_by_test_step.rb +40 -0
  56. data/lib/cucumber/formatter/query/test_case_started_by_test_case.rb +40 -0
  57. data/lib/cucumber/formatter/rerun.rb +22 -4
  58. data/lib/cucumber/formatter/stepdefs.rb +1 -2
  59. data/lib/cucumber/formatter/steps.rb +2 -3
  60. data/lib/cucumber/formatter/summary.rb +16 -8
  61. data/lib/cucumber/formatter/unicode.rb +15 -17
  62. data/lib/cucumber/formatter/usage.rb +11 -10
  63. data/lib/cucumber/gherkin/data_table_parser.rb +17 -6
  64. data/lib/cucumber/gherkin/formatter/ansi_escapes.rb +13 -17
  65. data/lib/cucumber/gherkin/formatter/escaping.rb +2 -2
  66. data/lib/cucumber/gherkin/steps_parser.rb +17 -8
  67. data/lib/cucumber/glue/dsl.rb +1 -1
  68. data/lib/cucumber/glue/hook.rb +34 -11
  69. data/lib/cucumber/glue/invoke_in_world.rb +13 -18
  70. data/lib/cucumber/glue/proto_world.rb +42 -33
  71. data/lib/cucumber/glue/registry_and_more.rb +42 -12
  72. data/lib/cucumber/glue/snippet.rb +23 -22
  73. data/lib/cucumber/glue/step_definition.rb +42 -19
  74. data/lib/cucumber/glue/world_factory.rb +1 -1
  75. data/lib/cucumber/hooks.rb +11 -11
  76. data/lib/cucumber/multiline_argument/data_table/diff_matrices.rb +1 -1
  77. data/lib/cucumber/multiline_argument/data_table.rb +97 -64
  78. data/lib/cucumber/multiline_argument/doc_string.rb +1 -1
  79. data/lib/cucumber/multiline_argument.rb +4 -6
  80. data/lib/cucumber/platform.rb +3 -3
  81. data/lib/cucumber/rake/task.rb +16 -16
  82. data/lib/cucumber/rspec/disable_option_parser.rb +9 -8
  83. data/lib/cucumber/running_test_case.rb +2 -53
  84. data/lib/cucumber/runtime/after_hooks.rb +8 -4
  85. data/lib/cucumber/runtime/before_hooks.rb +8 -4
  86. data/lib/cucumber/runtime/for_programming_languages.rb +4 -2
  87. data/lib/cucumber/runtime/step_hooks.rb +3 -2
  88. data/lib/cucumber/runtime/support_code.rb +13 -15
  89. data/lib/cucumber/runtime/user_interface.rb +6 -16
  90. data/lib/cucumber/runtime.rb +54 -58
  91. data/lib/cucumber/step_definition_light.rb +4 -3
  92. data/lib/cucumber/step_definitions.rb +2 -2
  93. data/lib/cucumber/step_match.rb +12 -11
  94. data/lib/cucumber/step_match_search.rb +2 -1
  95. data/lib/cucumber/term/ansicolor.rb +9 -9
  96. data/lib/cucumber/version +1 -1
  97. data/lib/cucumber.rb +1 -1
  98. metadata +221 -77
  99. data/lib/cucumber/formatter/cucumber.css +0 -286
  100. data/lib/cucumber/formatter/cucumber.sass +0 -247
  101. data/lib/cucumber/formatter/hook_query_visitor.rb +0 -42
  102. data/lib/cucumber/formatter/html_builder.rb +0 -121
  103. data/lib/cucumber/formatter/inline-js.js +0 -30
  104. data/lib/cucumber/formatter/jquery-min.js +0 -154
  105. data/lib/cucumber/formatter/json_pretty.rb +0 -11
  106. data/lib/cucumber/formatter/legacy_api/adapter.rb +0 -1028
  107. data/lib/cucumber/formatter/legacy_api/ast.rb +0 -394
  108. data/lib/cucumber/formatter/legacy_api/results.rb +0 -50
  109. data/lib/cucumber/formatter/legacy_api/runtime_facade.rb +0 -32
  110. data/lib/cucumber/step_argument.rb +0 -25
@@ -1,1028 +0,0 @@
1
- # frozen_string_literal: true
2
- require 'forwardable'
3
- require 'delegate'
4
- require 'cucumber/errors'
5
- require 'cucumber/multiline_argument'
6
- require 'cucumber/formatter/backtrace_filter'
7
- require 'cucumber/formatter/legacy_api/ast'
8
-
9
- module Cucumber
10
- module Formatter
11
- module LegacyApi
12
-
13
- Adapter = Struct.new(:formatter, :results, :config) do
14
- extend Forwardable
15
-
16
- def initialize(*)
17
- super
18
- emit_deprecation_warning
19
-
20
- @matches = collect_matches
21
- config.on_event(:test_case_started) do |event|
22
- formatter.before_test_case(event.test_case)
23
- printer.before_test_case(event.test_case)
24
- end
25
- config.on_event(:test_step_started) do |event|
26
- formatter.before_test_step(event.test_step)
27
- printer.before_test_step(event.test_step)
28
- end
29
- config.on_event(:test_step_finished) do |event|
30
- test_step, result = *event.attributes
31
- printer.after_test_step(test_step, result)
32
- formatter.after_test_step(test_step, result.with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter))
33
- end
34
- config.on_event(:test_case_finished) do |event|
35
- test_case, result = *event.attributes
36
- record_test_case_result(test_case, result)
37
- printer.after_test_case(test_case, result)
38
- formatter.after_test_case(test_case, result.with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter))
39
- end
40
- config.on_event(:test_run_finished) do
41
- printer.after
42
- formatter.done
43
- end
44
- end
45
-
46
- def_delegators :formatter, :ask
47
- def_delegators :printer, :embed
48
-
49
- def puts(*messages)
50
- printer.puts(messages)
51
- end
52
-
53
- private
54
-
55
- def emit_deprecation_warning
56
- parent_name = formatter_class_name =~ /::[^:]+\Z/ ? $`.freeze : nil
57
- return if parent_name == 'Cucumber::Formatter'
58
- return if !config.out_stream # some unit tests don't set it
59
- config.out_stream.puts "WARNING: The formatter #{formatter.class.name} is using the deprecated formatter API which will be removed in v4.0 of Cucumber."
60
- config.out_stream.puts
61
- end
62
-
63
- def formatter_class_name
64
- formatter.class.name
65
- rescue NoMethodError # when we use the Fanout, things get gnarly
66
- formatter.class[0].class.name
67
- end
68
-
69
- def printer
70
- @printer ||= FeaturesPrinter.new(formatter, results, config, @matches).before
71
- end
72
-
73
- def record_test_case_result(test_case, result)
74
- scenario = LegacyResultBuilder.new(result).scenario("#{test_case.keyword}: #{test_case.name}", test_case.location)
75
- results.scenario_visited(scenario)
76
- end
77
-
78
- def collect_matches
79
- result = {}
80
- config.on_event(:step_activated) do |event|
81
- test_step, step_match = *event.attributes
82
- result[test_step.source.last] = step_match
83
- end
84
- result
85
- end
86
-
87
- require 'cucumber/core/test/timer'
88
- FeaturesPrinter = Struct.new(:formatter, :results, :config, :matches) do
89
- extend Forwardable
90
-
91
- def before
92
- timer.start
93
- formatter.before_features(nil)
94
- self
95
- end
96
-
97
- def before_test_case(test_case)
98
- test_case.describe_source_to(self)
99
- @child.before_test_case(test_case)
100
- end
101
-
102
- def before_test_step(*args)
103
- @child.before_test_step(*args)
104
- end
105
-
106
- def after_test_step(test_step, result)
107
- @child.after_test_step(test_step, result)
108
- end
109
-
110
- def after_test_case(*args)
111
- @child.after_test_case(*args)
112
- end
113
-
114
- def feature(node, *)
115
- if node != @current_feature
116
- @child.after if @child
117
- @child = FeaturePrinter.new(formatter, results, matches, config, node).before
118
- @current_feature = node
119
- end
120
- end
121
-
122
- def scenario(node, *)
123
- end
124
-
125
- def scenario_outline(node, *)
126
- end
127
-
128
- def examples_table(node, *)
129
- end
130
-
131
- def examples_table_row(node, *)
132
- end
133
-
134
- def after
135
- @child.after if @child
136
- formatter.after_features Ast::Features.new(timer.sec)
137
- self
138
- end
139
-
140
- def puts(messages)
141
- @child.puts(messages)
142
- end
143
-
144
- def embed(src, mime_type, label)
145
- @child.embed(src, mime_type, label)
146
- end
147
-
148
- private
149
-
150
- def timer
151
- @timer ||= Cucumber::Core::Test::Timer.new
152
- end
153
-
154
- end
155
-
156
- module TestCaseSource
157
- def self.for(test_case, result)
158
- collector = Collector.new
159
- test_case.describe_source_to collector, result
160
- collector.result.freeze
161
- end
162
-
163
- class Collector
164
- attr_reader :result
165
-
166
- def initialize
167
- @result = CaseSource.new
168
- end
169
-
170
- def method_missing(name, node, _test_case_result, *_args)
171
- result.send "#{name}=", node
172
- end
173
- end
174
-
175
- require 'ostruct'
176
- class CaseSource < OpenStruct
177
- end
178
- end
179
-
180
- module TestStepSource
181
- def self.for(test_step, result)
182
- collector = Collector.new
183
- test_step.describe_source_to collector, result
184
- collector.result.freeze
185
- end
186
-
187
- class Collector
188
- attr_reader :result
189
-
190
- def initialize
191
- @result = StepSource.new
192
- end
193
-
194
- def method_missing(name, node, step_result, *_args)
195
- result.send "#{name}=", node
196
- result.send "#{name}_result=", LegacyResultBuilder.new(step_result)
197
- end
198
- end
199
-
200
- require 'ostruct'
201
- class StepSource < OpenStruct
202
- def build_step_invocation(indent, matches, config, messages, embeddings)
203
- step_result.step_invocation(
204
- matches.fetch(step) { NoStepMatch.new(step, step.text) },
205
- step,
206
- indent,
207
- background,
208
- config,
209
- messages,
210
- embeddings
211
- )
212
- end
213
- end
214
-
215
- end
216
-
217
- Embedding = Struct.new(:src, :mime_type, :label) do
218
-
219
- def send_to_formatter(formatter)
220
- formatter.embed(src, mime_type, label)
221
- end
222
- end
223
-
224
- FeaturePrinter = Struct.new(:formatter, :results, :matches, :config, :node) do
225
-
226
- def before
227
- formatter.before_feature(node)
228
- language_comment = node.language.iso_code != 'en' ? ["# language: #{node.language.iso_code}"] : []
229
- Ast::Comments.new(language_comment + node.comments).accept(formatter)
230
- Ast::Tags.new(node.tags).accept(formatter)
231
- formatter.feature_name node.keyword, node.legacy_conflated_name_and_description
232
- @delayed_messages = []
233
- @delayed_embeddings = []
234
- self
235
- end
236
-
237
- attr_reader :current_test_step_source
238
-
239
- def before_test_case(_test_case)
240
- @before_hook_results = Ast::HookResultCollection.new
241
- @test_step_results = []
242
- end
243
-
244
- def before_test_step(test_step)
245
- end
246
-
247
- def after_test_step(test_step, result)
248
- @current_test_step_source = TestStepSource.for(test_step, result)
249
- # TODO: stop calling self, and describe source to another object
250
- test_step.describe_source_to(self, result)
251
- print_step
252
- @test_step_results << result
253
- end
254
-
255
- def after_test_case(test_case, test_case_result)
256
- if current_test_step_source && current_test_step_source.step_result.nil?
257
- switch_step_container
258
- end
259
-
260
- if test_case_result.failed? && !any_test_steps_failed?
261
- # around hook must have failed. Print the error.
262
- switch_step_container(TestCaseSource.for(test_case, test_case_result))
263
- LegacyResultBuilder.new(test_case_result).describe_exception_to formatter
264
- end
265
-
266
- # messages and embedding should already have been handled, but just in case...
267
- @delayed_messages.each { |message| formatter.puts(message) }
268
- @delayed_embeddings.each { |embedding| embedding.send_to_formatter(formatter) }
269
- @delayed_messages = []
270
- @delayed_embeddings = []
271
-
272
- @child.after_test_case(test_case, test_case_result) if @child
273
- @previous_test_case_background = @current_test_case_background
274
- @previous_test_case_scenario_outline = current_test_step_source && current_test_step_source.scenario_outline
275
- end
276
-
277
- def before_hook(_location, result)
278
- @before_hook_results << Ast::HookResult.new(LegacyResultBuilder.new(result), @delayed_messages, @delayed_embeddings)
279
- @delayed_messages = []
280
- @delayed_embeddings = []
281
- end
282
-
283
- def after_hook(_location, result)
284
- # if the scenario has no steps, we can hit this before we've created the scenario printer
285
- # ideally we should call switch_step_container in before_step_step
286
- switch_step_container if !@child
287
- @child.after_hook Ast::HookResult.new(LegacyResultBuilder.new(result), @delayed_messages, @delayed_embeddings)
288
- @delayed_messages = []
289
- @delayed_embeddings = []
290
- end
291
-
292
- def after_step_hook(_hook, result)
293
- p current_test_step_source if current_test_step_source.step.nil?
294
- line = current_test_step_source.step.backtrace_line
295
- @child.after_step_hook Ast::HookResult.new(LegacyResultBuilder.new(result).
296
- append_to_exception_backtrace(line), @delayed_messages, @delayed_embeddings)
297
- @delayed_messages = []
298
- @delayed_embeddings = []
299
- end
300
-
301
- def background(node, *)
302
- @current_test_case_background = node
303
- end
304
-
305
- def puts(messages)
306
- @delayed_messages.push *messages
307
- end
308
-
309
- def embed(src, mime_type, label)
310
- @delayed_embeddings.push Embedding.new(src, mime_type, label)
311
- end
312
-
313
- def step(*);end
314
- def scenario(*);end
315
- def scenario_outline(*);end
316
- def examples_table(*);end
317
- def examples_table_row(*);end
318
- def feature(*);end
319
-
320
- def after
321
- @child.after if @child
322
- formatter.after_feature(node)
323
- self
324
- end
325
-
326
- private
327
-
328
- attr_reader :before_hook_results
329
- private :before_hook_results
330
-
331
- def any_test_steps_failed?
332
- @test_step_results.any? &:failed?
333
- end
334
-
335
- def switch_step_container(source = current_test_step_source)
336
- switch_to_child select_step_container(source), source
337
- end
338
-
339
- def select_step_container(source)
340
- if source.background
341
- if same_background_as_previous_test_case?(source)
342
- HiddenBackgroundPrinter.new(formatter, source.background)
343
- else
344
- BackgroundPrinter.new(formatter, node, source.background, before_hook_results)
345
- end
346
- elsif source.scenario
347
- ScenarioPrinter.new(formatter, source.scenario, before_hook_results)
348
- elsif source.scenario_outline
349
- if same_scenario_outline_as_previous_test_case?(source) && @previous_outline_child
350
- @previous_outline_child
351
- else
352
- ScenarioOutlinePrinter.new(formatter, config, source.scenario_outline)
353
- end
354
- else
355
- raise 'unknown step container'
356
- end
357
- end
358
-
359
- def same_background_as_previous_test_case?(source)
360
- source.background == @previous_test_case_background
361
- end
362
-
363
- def same_scenario_outline_as_previous_test_case?(source)
364
- source.scenario_outline == @previous_test_case_scenario_outline
365
- end
366
-
367
- def print_step
368
- return unless current_test_step_source.step_result
369
- switch_step_container
370
-
371
- if current_test_step_source.scenario_outline
372
- @child.examples_table(current_test_step_source.examples_table)
373
- @child.examples_table_row(current_test_step_source.examples_table_row, before_hook_results)
374
- end
375
-
376
- if @failed_hidden_background_step
377
- indent = Indent.new(@child.node)
378
- step_invocation = @failed_hidden_background_step.build_step_invocation(indent, matches, config, [], [])
379
- @child.step_invocation(step_invocation, @failed_hidden_background_step)
380
- @failed_hidden_background_step = nil
381
- end
382
-
383
- unless @last_step == current_test_step_source.step
384
- indent ||= Indent.new(@child.node)
385
- step_invocation = current_test_step_source.build_step_invocation(indent, matches, config, @delayed_messages, @delayed_embeddings)
386
- results.step_visited step_invocation
387
- @child.step_invocation(step_invocation, current_test_step_source)
388
- @last_step = current_test_step_source.step
389
- end
390
- @delayed_messages = []
391
- @delayed_embeddings = []
392
- end
393
-
394
- def switch_to_child(child, source)
395
- return if @child == child
396
- if @child
397
- if from_first_background(@child)
398
- @first_background_failed = @child.failed?
399
- elsif from_hidden_background(@child)
400
- if not @first_background_failed
401
- @failed_hidden_background_step = @child.get_failed_step_source
402
- end
403
- if @previous_outline_child
404
- @previous_outline_child.after unless same_scenario_outline_as_previous_test_case?(source)
405
- end
406
- end
407
- if from_scenario_outline_to_hidden_background(@child, child)
408
- @previous_outline_child = @child
409
- else
410
- @child.after
411
- @previous_outline_child = nil
412
- end
413
- end
414
- child.before unless to_scenario_outline(child) && same_scenario_outline_as_previous_test_case?(source)
415
- @child = child
416
- end
417
-
418
- def from_scenario_outline_to_hidden_background(from, to)
419
- from.class.name == ScenarioOutlinePrinter.name &&
420
- to.class.name == HiddenBackgroundPrinter.name
421
- end
422
-
423
- def from_first_background(from)
424
- from.class.name == BackgroundPrinter.name
425
- end
426
-
427
- def from_hidden_background(from)
428
- from.class.name == HiddenBackgroundPrinter.name
429
- end
430
-
431
- def to_scenario_outline(to)
432
- to.class.name == ScenarioOutlinePrinter.name
433
- end
434
-
435
- end
436
-
437
- module PrintsAfterHooks
438
- def after_hook_results
439
- @after_hook_results ||= Ast::HookResultCollection.new
440
- end
441
-
442
- def after_hook(result)
443
- after_hook_results << result
444
- end
445
- end
446
-
447
- # Basic printer used by default
448
- class AfterHookPrinter
449
- attr_reader :formatter
450
-
451
- def initialize(formatter)
452
- @formatter = formatter
453
- end
454
-
455
- include PrintsAfterHooks
456
-
457
- def after
458
- after_hook_results.accept(formatter)
459
- end
460
- end
461
-
462
- BackgroundPrinter = Struct.new(:formatter, :feature, :node, :before_hook_results) do
463
-
464
- def after_test_case(*)
465
- end
466
-
467
- def after_hook(*)
468
- end
469
-
470
- def before
471
- formatter.before_background Ast::Background.new(feature, node)
472
- Ast::Comments.new(node.comments).accept(formatter)
473
- formatter.background_name node.keyword, node.legacy_conflated_name_and_description, node.location.to_s, indent.of(node)
474
- before_hook_results.accept(formatter)
475
- self
476
- end
477
-
478
- def after_step_hook(result)
479
- result.accept formatter
480
- end
481
-
482
- def step_invocation(step_invocation, source)
483
- @child ||= StepsPrinter.new(formatter).before
484
- @child.step_invocation step_invocation
485
- if source.step_result.status == :failed
486
- @failed = true
487
- end
488
- end
489
-
490
- def after
491
- @child.after if @child
492
- formatter.after_background(Ast::Background.new(feature, node))
493
- self
494
- end
495
-
496
- def failed?
497
- @failed
498
- end
499
-
500
- private
501
-
502
- def indent
503
- @indent ||= Indent.new(node)
504
- end
505
- end
506
-
507
- # Printer to handle background steps for anything but the first scenario in a
508
- # feature. These steps should not be printed.
509
- class HiddenBackgroundPrinter < Struct.new(:formatter, :node)
510
- def get_failed_step_source
511
- return @source_of_failed_step
512
- end
513
-
514
- def step_invocation(_step_invocation, source)
515
- if source.step_result.status == :failed
516
- @source_of_failed_step = source
517
- end
518
- end
519
-
520
- def before;self;end
521
- def after;self;end
522
- def before_hook(*);end
523
- def after_hook(*);end
524
- def after_step_hook(*);end
525
- def examples_table(*);end
526
- def after_test_case(*);end
527
- end
528
-
529
- ScenarioPrinter = Struct.new(:formatter, :node, :before_hook_results) do
530
- include PrintsAfterHooks
531
-
532
- def before
533
- formatter.before_feature_element(node)
534
- Ast::Comments.new(node.comments).accept(formatter)
535
- Ast::Tags.new(node.tags).accept(formatter)
536
- formatter.scenario_name node.keyword, node.legacy_conflated_name_and_description, node.location.to_s, indent.of(node)
537
- before_hook_results.accept(formatter)
538
- self
539
- end
540
-
541
- def step_invocation(step_invocation, _source)
542
- @child ||= StepsPrinter.new(formatter).before
543
- @child.step_invocation step_invocation
544
- end
545
-
546
- def after_step_hook(result)
547
- result.accept formatter
548
- end
549
-
550
- def after_test_case(_test_case, result)
551
- @test_case_result = result
552
- after
553
- end
554
-
555
- def after
556
- return if @done
557
- @child.after if @child
558
- scenario = LegacyResultBuilder.new(@test_case_result).scenario(node.name, node.location)
559
- after_hook_results.accept(formatter)
560
- formatter.after_feature_element(scenario)
561
- @done = true
562
- self
563
- end
564
-
565
- private
566
-
567
- def indent
568
- @indent ||= Indent.new(node)
569
- end
570
- end
571
-
572
- StepsPrinter = Struct.new(:formatter) do
573
- def before
574
- formatter.before_steps(nil)
575
- self
576
- end
577
-
578
- def step_invocation(step_invocation)
579
- steps << step_invocation
580
- step_invocation.accept(formatter)
581
- self
582
- end
583
-
584
- def after
585
- formatter.after_steps(steps)
586
- self
587
- end
588
-
589
- private
590
-
591
- def steps
592
- @steps ||= Ast::StepInvocations.new
593
- end
594
-
595
- end
596
-
597
- ScenarioOutlinePrinter = Struct.new(:formatter, :configuration, :node) do
598
- extend Forwardable
599
- def_delegators :@child, :after_hook, :after_step_hook
600
-
601
- def before
602
- formatter.before_feature_element(node)
603
- Ast::Comments.new(node.comments).accept(formatter)
604
- Ast::Tags.new(node.tags).accept(formatter)
605
- formatter.scenario_name node.keyword, node.legacy_conflated_name_and_description, node.location.to_s, indent.of(node)
606
- OutlineStepsPrinter.new(formatter, configuration, indent).print(node)
607
- self
608
- end
609
-
610
- def step_invocation(step_invocation, source)
611
- _node, result = source.step, source.step_result
612
- @last_step_result = result
613
- @child.step_invocation(step_invocation, source)
614
- end
615
-
616
- def examples_table(examples_table)
617
- @child ||= ExamplesArrayPrinter.new(formatter, configuration).before
618
- @child.examples_table(examples_table)
619
- end
620
-
621
- def examples_table_row(node, before_hook_results)
622
- @child.examples_table_row(node, before_hook_results)
623
- end
624
-
625
- def after_test_case(test_case, result)
626
- @child.after_test_case(test_case, result)
627
- end
628
-
629
- def after
630
- @child.after if @child
631
- # TODO: the last step result might not accurately reflect the
632
- # overall scenario result.
633
- scenario_outline = last_step_result.scenario_outline(node.name, node.location)
634
- formatter.after_feature_element(scenario_outline)
635
- self
636
- end
637
-
638
- private
639
-
640
- def last_step_result
641
- @last_step_result || Core::Test::Result::Unknown.new
642
- end
643
-
644
- def indent
645
- @indent ||= Indent.new(node)
646
- end
647
- end
648
-
649
- OutlineStepsPrinter = Struct.new(:formatter, :configuration, :indent, :outline) do
650
- def print(node)
651
- node.describe_to self
652
- steps_printer.after
653
- end
654
-
655
- def scenario_outline(_node, &descend)
656
- descend.call(self)
657
- end
658
-
659
- def outline_step(step)
660
- step_match = NoStepMatch.new(step, step.text)
661
- step_invocation = LegacyResultBuilder.new(Core::Test::Result::Skipped.new).
662
- step_invocation(step_match, step, indent, nil, configuration, [], [])
663
- steps_printer.step_invocation step_invocation
664
- end
665
-
666
- def examples_table(*);end
667
-
668
- private
669
-
670
- def steps_printer
671
- @steps_printer ||= StepsPrinter.new(formatter).before
672
- end
673
- end
674
-
675
- ExamplesArrayPrinter = Struct.new(:formatter, :configuration) do
676
- extend Forwardable
677
- def_delegators :@child, :step_invocation, :after_hook, :after_step_hook, :after_test_case, :examples_table_row
678
-
679
- def before
680
- formatter.before_examples_array(:examples_array)
681
- self
682
- end
683
-
684
- def examples_table(examples_table)
685
- return if examples_table == @current
686
- @child.after if @child
687
- @child = ExamplesTablePrinter.new(formatter, configuration, examples_table).before
688
- @current = examples_table
689
- end
690
-
691
- def after
692
- @child.after if @child
693
- formatter.after_examples_array
694
- self
695
- end
696
- end
697
-
698
- ExamplesTablePrinter = Struct.new(:formatter, :configuration, :node) do
699
- extend Forwardable
700
- def_delegators :@child, :step_invocation, :after_hook, :after_step_hook, :after_test_case
701
-
702
- def before
703
- formatter.before_examples(node)
704
- Ast::Comments.new(node.comments).accept(formatter)
705
- Ast::Tags.new(node.tags).accept(formatter)
706
- formatter.examples_name(node.keyword, node.legacy_conflated_name_and_description)
707
- formatter.before_outline_table(legacy_table)
708
- if !configuration.expand?
709
- HeaderTableRowPrinter.new(formatter, ExampleTableRow.new(node.header), Ast::Node.new).before.after
710
- end
711
- self
712
- end
713
-
714
- def examples_table_row(examples_table_row, before_hook_results)
715
- return if examples_table_row == @current
716
- @child.after if @child
717
- row = ExampleTableRow.new(examples_table_row)
718
- @child = if !configuration.expand?
719
- TableRowPrinter.new(formatter, row, before_hook_results).before
720
- else
721
- ExpandTableRowPrinter.new(formatter, row, before_hook_results).before
722
- end
723
- @current = examples_table_row
724
- end
725
-
726
- def after_test_case(*args)
727
- @child.after_test_case(*args)
728
- end
729
-
730
- def after
731
- @child.after if @child
732
- formatter.after_outline_table(node)
733
- formatter.after_examples(node)
734
- self
735
- end
736
-
737
- private
738
-
739
- def legacy_table
740
- LegacyTable.new(node)
741
- end
742
-
743
- class ExampleTableRow < SimpleDelegator
744
- def dom_id
745
- file_colon_line.gsub(/[\/\.:]/, '_')
746
- end
747
- end
748
-
749
- LegacyTable = Struct.new(:node) do
750
- def col_width(index)
751
- max_width = FindMaxWidth.new(index)
752
- node.describe_to max_width
753
- max_width.result
754
- end
755
-
756
- require 'cucumber/gherkin/formatter/escaping'
757
- FindMaxWidth = Struct.new(:index) do
758
- include ::Cucumber::Gherkin::Formatter::Escaping
759
-
760
- def examples_table(table, &descend)
761
- @result = char_length_of(table.header.values[index])
762
- descend.call(self)
763
- end
764
-
765
- def examples_table_row(row, &_descend)
766
- width = char_length_of(row.values[index])
767
- @result = width if width > result
768
- end
769
-
770
- def result
771
- @result ||= 0
772
- end
773
-
774
- private
775
- def char_length_of(cell)
776
- escape_cell(cell).unpack('U*').length
777
- end
778
- end
779
- end
780
- end
781
-
782
- class TableRowPrinterBase < Struct.new(:formatter, :node, :before_hook_results)
783
- include PrintsAfterHooks
784
-
785
- def after_step_hook(result)
786
- @after_step_hook_result ||= Ast::HookResultCollection.new
787
- @after_step_hook_result << result
788
- end
789
-
790
- def after_test_case(*_args)
791
- after
792
- end
793
-
794
- private
795
-
796
- def indent
797
- :not_needed
798
- end
799
-
800
- def legacy_table_row
801
- Ast::ExampleTableRow.new(exception, @status, node.values, node.location, node.language)
802
- end
803
-
804
- def exception
805
- return nil unless @failed_step
806
- @failed_step.exception
807
- end
808
- end
809
-
810
- class HeaderTableRowPrinter < TableRowPrinterBase
811
- def legacy_table_row
812
- Ast::ExampleTableRow.new(exception, @status, node.values, node.location, Ast::NullLanguage.new)
813
- end
814
-
815
- def before
816
- Ast::Comments.new(node.comments).accept(formatter)
817
- formatter.before_table_row(node)
818
- self
819
- end
820
-
821
- def after
822
- node.values.each do |value|
823
- formatter.before_table_cell(value)
824
- formatter.table_cell_value(value, :skipped_param)
825
- formatter.after_table_cell(value)
826
- end
827
- formatter.after_table_row(legacy_table_row)
828
- self
829
- end
830
- end
831
-
832
-
833
- class TableRowPrinter < TableRowPrinterBase
834
- def before
835
- before_hook_results.accept(formatter)
836
- Ast::Comments.new(node.comments).accept(formatter)
837
- formatter.before_table_row(node)
838
- self
839
- end
840
-
841
- def step_invocation(step_invocation, source)
842
- result = source.step_result
843
- step_invocation.messages.each { |message| formatter.puts(message) }
844
- step_invocation.embeddings.each { |embedding| embedding.send_to_formatter(formatter) }
845
- @failed_step = step_invocation if result.status == :failed
846
- @status = step_invocation.status unless already_failed?
847
- end
848
-
849
- def after
850
- return if @done
851
- @child.after if @child
852
- node.values.each do |value|
853
- formatter.before_table_cell(value)
854
- formatter.table_cell_value(value, @status || :skipped)
855
- formatter.after_table_cell(value)
856
- end
857
- @after_step_hook_result.send_output_to(formatter) if @after_step_hook_result
858
- after_hook_results.send_output_to(formatter)
859
- formatter.after_table_row(legacy_table_row)
860
- @after_step_hook_result.describe_exception_to(formatter) if @after_step_hook_result
861
- after_hook_results.describe_exception_to(formatter)
862
- @done = true
863
- self
864
- end
865
-
866
- private
867
-
868
- def already_failed?
869
- @status == :failed || @status == :undefined || @status == :pending
870
- end
871
- end
872
-
873
- class ExpandTableRowPrinter < TableRowPrinterBase
874
- def before
875
- before_hook_results.accept(formatter)
876
- self
877
- end
878
-
879
- def step_invocation(step_invocation, source)
880
- result = source.step_result
881
- @table_row ||= legacy_table_row
882
- step_invocation.indent.record_width_of(@table_row)
883
- if !@scenario_name_printed
884
- print_scenario_name(step_invocation, @table_row)
885
- @scenario_name_printed = true
886
- end
887
- step_invocation.accept(formatter)
888
- @failed_step = step_invocation if result.status == :failed
889
- @status = step_invocation.status unless @status == :failed
890
- end
891
-
892
- def after
893
- return if @done
894
- @child.after if @child
895
- @after_step_hook_result.accept(formatter) if @after_step_hook_result
896
- after_hook_results.accept(formatter)
897
- @done = true
898
- self
899
- end
900
-
901
- private
902
-
903
- def print_scenario_name(step_invocation, table_row)
904
- formatter.scenario_name table_row.keyword, table_row.name, node.location.to_s, step_invocation.indent.of(table_row)
905
- end
906
- end
907
-
908
- class Indent
909
- def initialize(node)
910
- @widths = []
911
- node.describe_to(self)
912
- end
913
-
914
- [:background, :scenario, :scenario_outline].each do |node_name|
915
- define_method(node_name) do |node, &descend|
916
- record_width_of node
917
- descend.call(self)
918
- end
919
- end
920
-
921
- [:step, :outline_step].each do |node_name|
922
- define_method(node_name) do |node|
923
- record_width_of node
924
- end
925
- end
926
-
927
- def examples_table(*); end
928
- def examples_table_row(*); end
929
-
930
- def of(node)
931
- # The length of the instantiated steps in --expand mode are currently
932
- # not included in the calculation of max => make sure to return >= 1
933
- [1, max - node.to_s.length - node.keyword.length].max
934
- end
935
-
936
- def record_width_of(node)
937
- @widths << node.keyword.length + node.to_s.length + 1
938
- end
939
-
940
- private
941
-
942
- def max
943
- @widths.max
944
- end
945
- end
946
-
947
- class LegacyResultBuilder
948
- attr_reader :status
949
- def initialize(result)
950
- @result = result
951
- @result.describe_to(self)
952
- end
953
-
954
- def passed
955
- @status = :passed
956
- end
957
-
958
- def failed
959
- @status = :failed
960
- end
961
-
962
- def undefined
963
- @status = :undefined
964
- end
965
-
966
- def skipped
967
- @status = :skipped
968
- end
969
-
970
- def pending(exception, *)
971
- @exception = exception
972
- @status = :pending
973
- end
974
-
975
- def exception(exception, *)
976
- @exception = exception
977
- end
978
-
979
- def append_to_exception_backtrace(line)
980
- @exception.set_backtrace(@exception.backtrace + [line.to_s]) if @exception
981
- return self
982
- end
983
-
984
- def duration(duration, *)
985
- @duration = duration
986
- end
987
-
988
- def step_invocation(step_match, step, indent, background, configuration, messages, embeddings)
989
- Ast::StepInvocation.new(step_match, @status, @duration, step_exception(step, configuration), indent, background, step, messages, embeddings)
990
- end
991
-
992
- def scenario(name, location)
993
- Ast::Scenario.new(@status, name, location)
994
- end
995
-
996
- def scenario_outline(name, location)
997
- Ast::ScenarioOutline.new(@status, name, location)
998
- end
999
-
1000
- def describe_exception_to(formatter)
1001
- formatter.exception(filtered_exception, @status) if @exception
1002
- end
1003
-
1004
- private
1005
-
1006
- def step_exception(step, configuration)
1007
- return filtered_step_exception(step) if @exception
1008
- return nil unless @status == :undefined && configuration.strict.strict?(:undefined)
1009
- @exception = Cucumber::Undefined.from(@result, step.text)
1010
- @exception.backtrace << step.backtrace_line
1011
- filtered_step_exception(step)
1012
- end
1013
-
1014
- def filtered_exception
1015
- Cucumber::Formatter::BacktraceFilter.new(@exception.dup).exception
1016
- end
1017
-
1018
- def filtered_step_exception(_step)
1019
- exception = filtered_exception
1020
- return Cucumber::Formatter::BacktraceFilter.new(exception).exception
1021
- end
1022
- end
1023
-
1024
- end
1025
-
1026
- end
1027
- end
1028
- end