cucumber 11.0.0 → 11.1.1

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +15 -6
  3. data/VERSION +1 -1
  4. data/lib/cucumber/cli/main.rb +22 -0
  5. data/lib/cucumber/configuration.rb +4 -0
  6. data/lib/cucumber/events/attach_called.rb +19 -0
  7. data/lib/cucumber/events/base.rb +25 -0
  8. data/lib/cucumber/events/envelope.rb +11 -2
  9. data/lib/cucumber/events/gherkin_source_parsed.rb +12 -3
  10. data/lib/cucumber/events/gherkin_source_read.rb +11 -3
  11. data/lib/cucumber/events/hook_test_step_created.rb +12 -2
  12. data/lib/cucumber/events/step_activated.rb +13 -7
  13. data/lib/cucumber/events/step_definition_registered.rb +12 -4
  14. data/lib/cucumber/events/test_case_created.rb +11 -3
  15. data/lib/cucumber/events/test_case_finished.rb +12 -4
  16. data/lib/cucumber/events/test_case_ready.rb +10 -4
  17. data/lib/cucumber/events/test_case_started.rb +11 -2
  18. data/lib/cucumber/events/test_run_finished.rb +10 -3
  19. data/lib/cucumber/events/test_run_hook_finished.rb +11 -3
  20. data/lib/cucumber/events/test_run_hook_started.rb +10 -1
  21. data/lib/cucumber/events/test_run_started.rb +11 -2
  22. data/lib/cucumber/events/test_step_created.rb +12 -2
  23. data/lib/cucumber/events/test_step_finished.rb +12 -2
  24. data/lib/cucumber/events/test_step_started.rb +11 -2
  25. data/lib/cucumber/events/undefined_parameter_type.rb +11 -3
  26. data/lib/cucumber/events.rb +1 -0
  27. data/lib/cucumber/filters/fire_before_all_hooks.rb +36 -0
  28. data/lib/cucumber/formatter/console.rb +19 -11
  29. data/lib/cucumber/formatter/console_issues.rb +5 -5
  30. data/lib/cucumber/formatter/duration_extractor.rb +0 -2
  31. data/lib/cucumber/formatter/fail_fast.rb +3 -4
  32. data/lib/cucumber/formatter/global_hooks_summary.rb +36 -0
  33. data/lib/cucumber/formatter/html.rb +6 -4
  34. data/lib/cucumber/formatter/json.rb +14 -10
  35. data/lib/cucumber/formatter/junit.rb +4 -6
  36. data/lib/cucumber/formatter/message.rb +5 -6
  37. data/lib/cucumber/formatter/message_builder.rb +213 -205
  38. data/lib/cucumber/formatter/message_handlers.rb +13 -0
  39. data/lib/cucumber/formatter/pretty.rb +7 -9
  40. data/lib/cucumber/formatter/progress.rb +1 -2
  41. data/lib/cucumber/formatter/rerun.rb +12 -5
  42. data/lib/cucumber/formatter/summary.rb +15 -1
  43. data/lib/cucumber/formatter/usage.rb +1 -2
  44. data/lib/cucumber/glue/proto_world.rb +3 -2
  45. data/lib/cucumber/glue/registry_and_more.rb +98 -9
  46. data/lib/cucumber/runtime/user_interface.rb +4 -2
  47. data/lib/cucumber/runtime.rb +38 -17
  48. metadata +11 -15
  49. data/lib/cucumber/formatter/errors.rb +0 -9
  50. data/lib/cucumber/formatter/fanout.rb +0 -28
  51. data/lib/cucumber/formatter/query/hook_by_test_step.rb +0 -34
  52. data/lib/cucumber/formatter/query/pickle_by_test.rb +0 -28
  53. data/lib/cucumber/formatter/query/step_definitions_by_test_step.rb +0 -42
  54. data/lib/cucumber/formatter/query/test_case_started_by_test_case.rb +0 -45
@@ -1,48 +1,45 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'base64'
4
- require 'cucumber/formatter/backtrace_filter'
5
- require 'cucumber/formatter/query/hook_by_test_step'
6
- require 'cucumber/formatter/query/pickle_by_test'
7
- require 'cucumber/formatter/query/step_definitions_by_test_step'
8
- require 'cucumber/formatter/query/test_case_started_by_test_case'
4
+ require 'json'
9
5
 
6
+ require 'cucumber/formatter/backtrace_filter'
10
7
  require 'cucumber/query'
11
8
 
9
+ require_relative 'message_handlers'
10
+
12
11
  module Cucumber
13
12
  module Formatter
14
13
  class MessageBuilder
15
14
  include Cucumber::Messages::Helpers::TimeConversion
16
15
  include Io
16
+ include MessageHandlers
17
+ include Console
17
18
 
18
19
  def initialize(config)
19
20
  @config = config
20
-
21
- @hook_by_test_step = Query::HookByTestStep.new(config)
22
- @pickle_by_test = Query::PickleByTest.new(config)
23
- @step_definitions_by_test_step = Query::StepDefinitionsByTestStep.new(config)
24
- @test_case_started_by_test_case = Query::TestCaseStartedByTestCase.new(config)
25
-
21
+ @ast_lookup = AstLookup.new(config)
26
22
  @repository = Cucumber::Repository.new
27
- @query = Cucumber::Query.new(@repository)
28
23
 
29
- @test_run_started_id = config.id_generator.new_id
24
+ @test_run_started_id = config.test_run_started_id
25
+
26
+ # Fake Query objects
30
27
  @test_case_by_step_id = {}
28
+ @pickle_id_by_test_case_id = {}
31
29
  @pickle_id_step_by_test_step_id = {}
30
+ @hook_id_by_test_step_id = {}
31
+ @step_definition_ids_by_test_step_id = {}
32
+ @step_match_arguments_by_test_step_id = {}
32
33
 
33
34
  # Ensure all handlers for events occur after all ivars are instantiated
34
35
 
35
- config.on_event :envelope, &method(:on_envelope)
36
-
37
36
  config.on_event :gherkin_source_parsed, &method(:on_gherkin_source_parsed)
38
- config.on_event :gherkin_source_read, &method(:on_gherkin_source_read)
39
37
 
40
38
  config.on_event :hook_test_step_created, &method(:on_hook_test_step_created)
41
39
 
42
40
  config.on_event :step_activated, &method(:on_step_activated)
43
- config.on_event :step_definition_registered, &method(:on_step_definition_registered)
44
41
 
45
- # TODO: Handle TestCaseCreated
42
+ config.on_event :test_case_created, &method(:on_test_case_created)
46
43
  config.on_event :test_case_ready, &method(:on_test_case_ready)
47
44
  config.on_event :test_case_started, &method(:on_test_case_started)
48
45
  config.on_event :test_case_finished, &method(:on_test_case_finished)
@@ -50,69 +47,75 @@ module Cucumber
50
47
  config.on_event :test_run_started, &method(:on_test_run_started)
51
48
  config.on_event :test_run_finished, &method(:on_test_run_finished)
52
49
 
53
- config.on_event :test_run_hook_started, &method(:on_test_run_hook_started)
54
- config.on_event :test_run_hook_finished, &method(:on_test_run_hook_finished)
55
-
56
50
  config.on_event :test_step_created, &method(:on_test_step_created)
57
51
  config.on_event :test_step_started, &method(:on_test_step_started)
58
52
  config.on_event :test_step_finished, &method(:on_test_step_finished)
59
53
 
60
- config.on_event :undefined_parameter_type, &method(:on_undefined_parameter_type)
54
+ config.on_event :attach_called, &method(:on_attach_called)
55
+ config.on_event :envelope, &method(:on_envelope)
61
56
  end
62
57
 
63
- def attach(src, media_type, filename)
64
- attachment_data = {
65
- test_step_id: @current_test_step_id,
66
- test_case_started_id: @current_test_case_started_id,
67
- media_type: media_type,
68
- file_name: filename,
69
- timestamp: time_to_timestamp(Time.now)
70
- }
58
+ def on_envelope(event)
59
+ store_current_test_run_hook_started_id(event)
60
+ end
71
61
 
72
- if media_type&.start_with?('text/')
73
- attachment_data[:content_encoding] = Cucumber::Messages::AttachmentContentEncoding::IDENTITY
74
- attachment_data[:body] = src
75
- else
76
- body = src.respond_to?(:read) ? src.read : src
62
+ def on_attach_called(event)
63
+ attachment_data =
64
+ if @current_test_run_hook_started_id.nil?
65
+ {
66
+ test_step_id: @current_test_step_id,
67
+ test_case_started_id: @current_test_case_started_id,
68
+ media_type: event.media_type,
69
+ file_name: event.filename,
70
+ timestamp: time_to_timestamp(Time.now)
71
+ }
72
+ else
73
+ {
74
+ test_run_hook_started_id: @current_test_run_hook_started_id,
75
+ media_type: event.media_type,
76
+ file_name: event.filename,
77
+ timestamp: time_to_timestamp(Time.now)
78
+ }
79
+ end
80
+
81
+ streamed_file = event.src.encoding == Encoding::BINARY
82
+
83
+ if streamed_file
77
84
  attachment_data[:content_encoding] = Cucumber::Messages::AttachmentContentEncoding::BASE64
78
- attachment_data[:body] = Base64.strict_encode64(body)
85
+ attachment_data[:body] = Base64.strict_encode64(event.src)
86
+ else
87
+ attachment_data[:content_encoding] = Cucumber::Messages::AttachmentContentEncoding::IDENTITY
88
+ attachment_data[:body] = event.src.is_a?(Hash) ? event.src.to_json : event.src
79
89
  end
80
90
 
81
91
  message = Cucumber::Messages::Envelope.new(attachment: Cucumber::Messages::Attachment.new(**attachment_data))
82
- output_envelope(message)
92
+ @config.event_bus.envelope(message)
83
93
  end
84
94
 
85
95
  private
86
96
 
87
- def on_envelope(event)
88
- output_envelope(event.envelope)
89
- end
90
-
91
97
  def on_gherkin_source_parsed(_event)
92
98
  # TODO: Handle GherkinSourceParsed
93
99
  end
94
100
 
95
- def on_gherkin_source_read(event)
96
- message = Cucumber::Messages::Envelope.new(
97
- source: Cucumber::Messages::Source.new(
98
- uri: event.path,
99
- data: event.body,
100
- media_type: 'text/x.cucumber.gherkin+plain'
101
- )
102
- )
101
+ def on_hook_test_step_created(event)
102
+ @hook_id_by_test_step_id[event.test_step.id] = event.hook.id
103
+ end
103
104
 
104
- output_envelope(message)
105
+ def on_step_activated(event)
106
+ @step_definition_ids_by_test_step_id[event.test_step.id] << event.step_match.step_definition.id
107
+ @step_match_arguments_by_test_step_id[event.test_step.id] = event.step_match.step_arguments
105
108
  end
106
109
 
107
- def on_hook_test_step_created(_event)
108
- # TODO: Handle HookTestStepCreated
110
+ def on_test_case_created(event)
111
+ @pickle_id_by_test_case_id[event.test_case.id] = event.pickle.id
109
112
  end
110
113
 
111
114
  def on_test_case_ready(event)
112
115
  message = Cucumber::Messages::Envelope.new(
113
116
  test_case: Cucumber::Messages::TestCase.new(
114
117
  id: event.test_case.id,
115
- pickle_id: @pickle_by_test.pickle_id(event.test_case),
118
+ pickle_id: fake_query_pickle_id(event.test_case),
116
119
  test_steps: event.test_case.test_steps.map { |step| test_step_to_message(step) },
117
120
  test_run_started_id: @test_run_started_id
118
121
  )
@@ -121,63 +124,56 @@ module Cucumber
121
124
  # TODO: This may be a redundant update. But for now we're leaving this in whilst we're in the transitory phase
122
125
  @repository.update(message)
123
126
 
124
- # TODO: Switch this over to using the Repo Query object -> `test_step_by_id`
125
- # TODO: The finder in query is `find_test_step_by` (Using +TestStepStarted+ message)
126
- event.test_case.test_steps.each do |step|
127
- @test_case_by_step_id[step.id] = event.test_case
128
- end
129
-
130
- output_envelope(message)
127
+ @config.event_bus.envelope(message)
131
128
  end
132
129
 
133
- def test_step_to_message(step)
134
- return hook_step_to_message(step) if step.hook?
130
+ def on_test_case_started(event)
131
+ # For any new test_case_started events, we must ALWAYS generate a new id for a new run
132
+ @current_test_case_started_id = @config.id_generator.new_id
133
+ @current_test_run_hook_started_id = nil
135
134
 
136
- Cucumber::Messages::TestStep.new(
137
- id: step.id,
138
- # TODO: This "fake query" is only used once. It can likely be replace by `find_pickle_step_by` which
139
- # takes a +TestStep+ message from the repo directly.
140
- pickle_step_id: @pickle_id_step_by_test_step_id[step.id],
141
- step_definition_ids: @step_definitions_by_test_step.step_definition_ids(step),
142
- step_match_arguments_lists: step_match_arguments_lists(step)
143
- )
144
- end
135
+ find_all_test_case_started_by_test_case_id =
136
+ @repository.test_case_started_by_id
137
+ .values
138
+ .select { |test_case_started| test_case_started.test_case_id == event.test_case.id }
145
139
 
146
- def hook_step_to_message(step)
147
- Cucumber::Messages::TestStep.new(
148
- id: step.id,
149
- hook_id: @hook_by_test_step.hook_id(step)
140
+ # If no TestCaseStarted messages exist. We must be on attempt 1 (Hence the .to_i casting for a `nil` value)
141
+ attempts_previously_made = find_all_test_case_started_by_test_case_id.map(&:attempt).max.to_i
142
+
143
+ message = Cucumber::Messages::Envelope.new(
144
+ test_case_started: Cucumber::Messages::TestCaseStarted.new(
145
+ id: @current_test_case_started_id,
146
+ test_case_id: event.test_case.id,
147
+ timestamp: time_to_timestamp(Time.now),
148
+ attempt: attempts_previously_made + 1
149
+ )
150
150
  )
151
- end
152
151
 
153
- def step_match_arguments_lists(step)
154
- match_arguments = step_match_arguments(step)
155
- [Cucumber::Messages::StepMatchArgumentsList.new(
156
- step_match_arguments: match_arguments
157
- )]
158
- rescue Cucumber::Formatter::TestStepUnknownError
159
- []
152
+ @config.event_bus.envelope(message)
153
+ @repository.update(message)
160
154
  end
161
155
 
162
- def step_match_arguments(step)
163
- @step_definitions_by_test_step.step_match_arguments(step).map do |argument|
164
- Cucumber::Messages::StepMatchArgument.new(
165
- group: argument_group_to_message(argument.group),
166
- parameter_type_name: parameter_type_name(argument)
167
- )
168
- end
169
- end
156
+ def on_test_case_finished(event)
157
+ test_case_started_id =
158
+ @repository.test_case_started_by_id
159
+ .values
160
+ .detect { |test_case_started_message| test_case_started_message.test_case_id == event.test_case.id }
161
+ .id
170
162
 
171
- def argument_group_to_message(group)
172
- Cucumber::Messages::Group.new(
173
- start: group.start,
174
- value: group.value,
175
- children: group.children&.map { |child| argument_group_to_message(child) }
163
+ test_case_started_message = @repository.test_case_started_by_id[test_case_started_id]
164
+ max_attempts = @config.retry_attempts
165
+ retries_attempted = test_case_started_message.attempt - 1
166
+ will_be_retried = event.result.failed? && (retries_attempted < max_attempts)
167
+
168
+ message = Cucumber::Messages::Envelope.new(
169
+ test_case_finished: Cucumber::Messages::TestCaseFinished.new(
170
+ test_case_started_id: test_case_started_id,
171
+ timestamp: time_to_timestamp(Time.now),
172
+ will_be_retried: will_be_retried
173
+ )
176
174
  )
177
- end
178
175
 
179
- def parameter_type_name(step_match_argument)
180
- step_match_argument.parameter_type&.name if step_match_argument.respond_to?(:parameter_type)
176
+ @config.event_bus.envelope(message)
181
177
  end
182
178
 
183
179
  def on_test_run_started(*)
@@ -188,63 +184,63 @@ module Cucumber
188
184
  )
189
185
  )
190
186
 
191
- output_envelope(message)
187
+ @config.event_bus.envelope(message)
192
188
  end
193
189
 
194
- def on_test_case_started(event)
195
- @current_test_case_started_id = test_case_started_id(event.test_case)
196
-
190
+ def on_test_run_finished(event)
197
191
  message = Cucumber::Messages::Envelope.new(
198
- test_case_started: Cucumber::Messages::TestCaseStarted.new(
199
- id: test_case_started_id(event.test_case),
200
- test_case_id: event.test_case.id,
192
+ test_run_finished: Cucumber::Messages::TestRunFinished.new(
201
193
  timestamp: time_to_timestamp(Time.now),
202
- attempt: @test_case_started_by_test_case.attempt_by_test_case(event.test_case)
194
+ success: event.success,
195
+ test_run_started_id: @test_run_started_id
203
196
  )
204
197
  )
205
198
 
206
- output_envelope(message)
199
+ @config.event_bus.envelope(message)
207
200
  end
208
201
 
209
202
  def on_test_step_created(event)
210
203
  @pickle_id_step_by_test_step_id[event.test_step.id] = event.pickle_step.id
211
- test_step_to_message(event.test_step)
212
- # TODO: We need to determine what message to output here (Unsure - Placeholder added)
213
- # message = Cucumber::Messages::Envelope.new(
214
- # pickle: {
215
- # id: '',
216
- # uri: '',
217
- # location: nil,
218
- # name: '',
219
- # language: '',
220
- # steps: test_step_to_message(event.test_step),
221
- # tags: [],
222
- # ast_node_ids: []
223
- # }
224
- # )
225
- #
226
- # output_envelope(message)
204
+ @step_definition_ids_by_test_step_id[event.test_step.id] = []
227
205
  end
228
206
 
229
207
  def on_test_step_started(event)
230
208
  @current_test_step_id = event.test_step.id
231
- test_case = @test_case_by_step_id[event.test_step.id]
209
+ find_test_case_by_step_id =
210
+ @repository.test_case_by_id
211
+ .values
212
+ .detect { |test_case_message| test_case_message.test_steps.any? { |step_message| step_message.id == event.test_step.id } }
213
+
214
+ find_test_case_started_by_test_case =
215
+ @repository.test_case_started_by_id
216
+ .values
217
+ .select { |test_case_started_message| test_case_started_message.test_case_id == find_test_case_by_step_id.id }
218
+ .max_by(&:attempt)
232
219
 
233
220
  message = Cucumber::Messages::Envelope.new(
234
221
  test_step_started: Cucumber::Messages::TestStepStarted.new(
235
222
  test_step_id: event.test_step.id,
236
- test_case_started_id: test_case_started_id(test_case),
223
+ test_case_started_id: find_test_case_started_by_test_case.id,
237
224
  timestamp: time_to_timestamp(Time.now)
238
225
  )
239
226
  )
240
227
 
241
- output_envelope(message)
228
+ @config.event_bus.envelope(message)
242
229
  end
243
230
 
244
231
  def on_test_step_finished(event)
245
- test_case = @test_case_by_step_id[event.test_step.id]
246
- result = event.result.with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter)
232
+ find_test_case_by_step_id =
233
+ @repository.test_case_by_id
234
+ .values
235
+ .detect { |test_case_message| test_case_message.test_steps.any? { |step_message| step_message.id == event.test_step.id } }
236
+
237
+ find_test_case_started_by_test_case =
238
+ @repository.test_case_started_by_id
239
+ .values
240
+ .select { |test_case_started_message| test_case_started_message.test_case_id == find_test_case_by_step_id.id }
241
+ .max_by(&:attempt)
247
242
 
243
+ result = event.result.with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter)
248
244
  result_message = result.to_message
249
245
  if result.failed? || result.pending?
250
246
  message_element = result.failed? ? result.exception : result
@@ -257,126 +253,138 @@ module Cucumber
257
253
  )
258
254
  end
259
255
 
256
+ output_snippet_envelope(event)
257
+
260
258
  message = Cucumber::Messages::Envelope.new(
261
259
  test_step_finished: Cucumber::Messages::TestStepFinished.new(
262
260
  test_step_id: event.test_step.id,
263
- test_case_started_id: test_case_started_id(test_case),
261
+ test_case_started_id: find_test_case_started_by_test_case.id,
264
262
  test_step_result: result_message,
265
263
  timestamp: time_to_timestamp(Time.now)
266
264
  )
267
265
  )
268
266
 
269
- output_envelope(message)
267
+ @config.event_bus.envelope(message)
270
268
  end
271
269
 
272
- def create_error_message(message_element)
273
- <<~ERROR_MESSAGE
274
- #{message_element.message} (#{message_element.class})
275
- #{message_element.backtrace}
276
- ERROR_MESSAGE
277
- end
270
+ def output_snippet_envelope(event)
271
+ return unless event.result.undefined?
278
272
 
279
- def create_exception_object(result, message_element)
280
- return unless result.failed?
273
+ collect_snippet_data(event.test_step, @ast_lookup)
274
+ snippet_text_proc = lambda do |step_keyword, step_name, multiline_arg|
275
+ snippet_text(step_keyword, step_name, multiline_arg)
276
+ end
281
277
 
282
- Cucumber::Messages::Exception.new(
283
- type: message_element.class,
284
- message: message_element.message,
285
- stack_trace: message_element.backtrace.join("\n")
286
- )
278
+ message = generate_snippet_envelope(snippet_text_proc, event)
279
+ @config.event_bus.envelope(message)
280
+ # To ensure we don't redistribute the "same" snippets over and over again
281
+ snippets_input.clear
287
282
  end
288
283
 
289
- def on_test_case_finished(event)
290
- test_case_started_id = test_case_started_id(event.test_case)
291
- test_case_started_message = @repository.test_case_started_by_id[test_case_started_id]
292
- max_attempts = @config.retry_attempts
293
- # See "fake query" for reason this is index shifted
294
- retries_attempted = test_case_started_message.attempt - 1
295
- will_be_retried = event.result.failed? && (retries_attempted < max_attempts)
284
+ def generate_snippet_envelope(snippet_text_proc, event)
285
+ snippets_array = snippets_input.map do |data|
286
+ snippet_text_proc.call(data.actual_keyword, data.step.text, data.step.multiline_arg)
287
+ end.uniq
296
288
 
297
- message = Cucumber::Messages::Envelope.new(
298
- test_case_finished: Cucumber::Messages::TestCaseFinished.new(
299
- test_case_started_id: test_case_started_id,
300
- timestamp: time_to_timestamp(Time.now),
301
- will_be_retried: will_be_retried
289
+ Cucumber::Messages::Envelope.new(
290
+ suggestion: Cucumber::Messages::Suggestion.new(
291
+ id: @config.id_generator.new_id,
292
+ pickle_step_id: @repository.test_step_by_id[event.test_step.id].pickle_step_id,
293
+ snippets: snippets_array.map { |code_snippet| Cucumber::Messages::Snippet.new(language: 'ruby', code: code_snippet) }
302
294
  )
303
295
  )
304
-
305
- output_envelope(message)
306
296
  end
307
297
 
308
- def on_test_run_finished(event)
298
+ def on_undefined_parameter_type(event)
309
299
  message = Cucumber::Messages::Envelope.new(
310
- test_run_finished: Cucumber::Messages::TestRunFinished.new(
311
- timestamp: time_to_timestamp(Time.now),
312
- success: event.success,
313
- test_run_started_id: @test_run_started_id
300
+ undefined_parameter_type: Cucumber::Messages::UndefinedParameterType.new(
301
+ name: event.type_name,
302
+ expression: event.expression
314
303
  )
315
304
  )
316
305
 
317
- output_envelope(message)
306
+ @config.event_bus.envelope(message)
318
307
  end
319
308
 
320
- def on_step_activated(event)
321
- # TODO: Handle StepActivated
309
+ def test_step_to_message(step)
310
+ return hook_step_to_message(step) if step.hook?
311
+
312
+ Cucumber::Messages::TestStep.new(
313
+ id: step.id,
314
+ pickle_step_id: @pickle_id_step_by_test_step_id[step.id],
315
+ step_definition_ids: fake_query_step_definition_ids(step),
316
+ step_match_arguments_lists: step_match_arguments_lists(step)
317
+ )
322
318
  end
323
319
 
324
- def on_step_definition_registered(event)
325
- output_envelope(event.step_definition.to_envelope)
320
+ def hook_step_to_message(step)
321
+ Cucumber::Messages::TestStep.new(
322
+ id: step.id,
323
+ hook_id: @hook_id_by_test_step_id[step.id]
324
+ )
326
325
  end
327
326
 
328
- def on_test_run_hook_started(event)
329
- @current_test_run_hook_started_id = @config.id_generator.new_id
327
+ def step_match_arguments_lists(step)
328
+ match_arguments = step_match_arguments(step)
329
+ if match_arguments.nil?
330
+ []
331
+ else
332
+ [Cucumber::Messages::StepMatchArgumentsList.new(step_match_arguments: match_arguments)]
333
+ end
334
+ end
330
335
 
331
- message = Cucumber::Messages::Envelope.new(
332
- test_run_hook_started: Cucumber::Messages::TestRunHookStarted.new(
333
- id: @current_test_run_hook_started_id,
334
- hook_id: event.hook.id,
335
- test_run_started_id: @test_run_started_id,
336
- timestamp: time_to_timestamp(Time.now)
336
+ def step_match_arguments(step)
337
+ fake_query_step_match_arguments(step)&.map do |argument|
338
+ Cucumber::Messages::StepMatchArgument.new(
339
+ group: argument_group_to_message(argument.group),
340
+ parameter_type_name: parameter_type_name(argument)
337
341
  )
342
+ end
343
+ end
344
+
345
+ def argument_group_to_message(group)
346
+ Cucumber::Messages::Group.new(
347
+ start: group.start,
348
+ value: group.value,
349
+ children: group.children&.map { |child| argument_group_to_message(child) }
338
350
  )
351
+ end
339
352
 
340
- output_envelope(message)
353
+ def parameter_type_name(step_match_argument)
354
+ step_match_argument.parameter_type&.name if step_match_argument.respond_to?(:parameter_type)
341
355
  end
342
356
 
343
- def on_test_run_hook_finished(event)
344
- result = event.test_result
345
- result_message = result.to_message
357
+ def create_error_message(message_element)
358
+ <<~ERROR_MESSAGE
359
+ #{message_element.message} (#{message_element.class})
360
+ #{message_element.backtrace}
361
+ ERROR_MESSAGE
362
+ end
346
363
 
347
- if result.failed?
348
- result_message = Cucumber::Messages::TestStepResult.new(
349
- status: result_message.status,
350
- duration: result_message.duration,
351
- message: create_error_message(result.exception),
352
- exception: create_exception_object(result, result.exception)
353
- )
354
- end
364
+ def create_exception_object(result, message_element)
365
+ return unless result.failed?
355
366
 
356
- message = Cucumber::Messages::Envelope.new(
357
- test_run_hook_finished: Cucumber::Messages::TestRunHookFinished.new(
358
- test_run_hook_started_id: @current_test_run_hook_started_id,
359
- timestamp: time_to_timestamp(Time.now),
360
- result: result_message
361
- )
367
+ Cucumber::Messages::Exception.new(
368
+ type: message_element.class,
369
+ message: message_element.message,
370
+ stack_trace: message_element.backtrace.join("\n")
362
371
  )
372
+ end
363
373
 
364
- output_envelope(message)
374
+ def fake_query_hook_id(test_step)
375
+ @hook_id_by_test_step_id.fetch(test_step.id)
365
376
  end
366
377
 
367
- def on_undefined_parameter_type(event)
368
- message = Cucumber::Messages::Envelope.new(
369
- undefined_parameter_type: Cucumber::Messages::UndefinedParameterType.new(
370
- name: event.type_name,
371
- expression: event.expression
372
- )
373
- )
378
+ def fake_query_pickle_id(test_case)
379
+ @pickle_id_by_test_case_id.fetch(test_case.id)
380
+ end
374
381
 
375
- output_envelope(message)
382
+ def fake_query_step_definition_ids(test_step)
383
+ @step_definition_ids_by_test_step_id.fetch(test_step.id)
376
384
  end
377
385
 
378
- def test_case_started_id(test_case)
379
- @test_case_started_by_test_case.test_case_started_id_by_test_case(test_case)
386
+ def fake_query_step_match_arguments(test_step)
387
+ @step_match_arguments_by_test_step_id.fetch(test_step.id, nil)
380
388
  end
381
389
  end
382
390
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cucumber
4
+ module Formatter
5
+ # Common Message Handlers to be used across all message-based formatters
6
+ # Designed to work solely with events of type `Envelope`
7
+ module MessageHandlers
8
+ def store_current_test_run_hook_started_id(event)
9
+ @current_test_run_hook_started_id = event.envelope.test_run_hook_started.id if event.envelope.test_run_hook_started
10
+ end
11
+ end
12
+ end
13
+ end
@@ -33,8 +33,6 @@ module Cucumber
33
33
  @io = ensure_io(config.out_stream, config.error_stream)
34
34
  @config = config
35
35
  @options = config.to_hash
36
- @snippets_input = []
37
- @undefined_parameter_types = []
38
36
  @total_duration = 0
39
37
  @exceptions = []
40
38
  @gherkin_sources = {}
@@ -66,6 +64,7 @@ module Cucumber
66
64
  config.on_event :test_case_finished, &method(:on_test_case_finished)
67
65
  config.on_event :test_run_finished, &method(:on_test_run_finished)
68
66
  config.on_event :undefined_parameter_type, &method(:collect_undefined_parameter_type_names)
67
+ config.on_event :attach_called, &method(:on_attach_called)
69
68
  end
70
69
 
71
70
  def on_gherkin_source_read(event)
@@ -73,8 +72,7 @@ module Cucumber
73
72
  end
74
73
 
75
74
  def on_step_activated(event)
76
- test_step, step_match = *event.attributes
77
- @step_matches[test_step.to_s] = step_match
75
+ @step_matches[event.test_step.to_s] = event.step_match
78
76
  end
79
77
 
80
78
  def on_test_case_started(event)
@@ -140,13 +138,13 @@ module Cucumber
140
138
  print_summary
141
139
  end
142
140
 
143
- def attach(src, media_type, filename)
144
- return unless media_type == 'text/x.cucumber.log+plain'
141
+ def on_attach_called(event)
142
+ return unless event.media_type == 'text/x.cucumber.log+plain'
145
143
 
146
- if filename
147
- @test_step_output.push("#{filename}: #{src}")
144
+ if event.filename
145
+ @test_step_output.push("#{event.filename}: #{event.src}")
148
146
  else
149
- @test_step_output.push(src)
147
+ @test_step_output.push(event.src)
150
148
  end
151
149
  end
152
150
 
@@ -20,8 +20,6 @@ module Cucumber
20
20
  def initialize(config)
21
21
  @config = config
22
22
  @io = ensure_io(config.out_stream, config.error_stream)
23
- @snippets_input = []
24
- @undefined_parameter_types = []
25
23
  @total_duration = 0
26
24
  @matches = {}
27
25
  @pending_step_matches = []
@@ -38,6 +36,7 @@ module Cucumber
38
36
  config.on_event :test_case_finished, &method(:on_test_case_finished)
39
37
  config.on_event :test_run_finished, &method(:on_test_run_finished)
40
38
  config.on_event :undefined_parameter_type, &method(:collect_undefined_parameter_type_names)
39
+ config.on_event :attach_called, &method(:on_attach_called)
41
40
  end
42
41
 
43
42
  def on_step_activated(event)