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
@@ -5,6 +5,7 @@ require 'cucumber/formatter/backtrace_filter'
5
5
  require 'cucumber/formatter/io'
6
6
  require 'cucumber/formatter/interceptor'
7
7
  require 'fileutils'
8
+ require 'cucumber/formatter/ast_lookup'
8
9
 
9
10
  module Cucumber
10
11
  module Formatter
@@ -19,6 +20,7 @@ module Cucumber
19
20
  end
20
21
 
21
22
  def initialize(config)
23
+ @ast_lookup = AstLookup.new(config)
22
24
  config.on_event :test_case_started, &method(:on_test_case_started)
23
25
  config.on_event :test_case_finished, &method(:on_test_case_finished)
24
26
  config.on_event :test_step_finished, &method(:on_test_step_finished)
@@ -33,17 +35,15 @@ module Cucumber
33
35
  tests: 0,
34
36
  skipped: 0,
35
37
  time: 0,
36
- builder: Builder::XmlMarkup.new(:indent => 2)
38
+ builder: Builder::XmlMarkup.new(indent: 2)
37
39
  }
38
40
  end
39
41
  end
40
42
 
41
43
  def on_test_case_started(event)
42
44
  test_case = event.test_case
43
- unless same_feature_as_previous_test_case?(test_case.feature)
44
- start_feature(test_case.feature)
45
- end
46
- @failing_step_source = nil
45
+ start_feature(test_case) unless same_feature_as_previous_test_case?(test_case)
46
+ @failing_test_step = nil
47
47
  # In order to fill out <system-err/> and <system-out/>, we need to
48
48
  # intercept the $stderr and $stdout
49
49
  @interceptedout = Interceptor::Pipe.wrap(:stdout)
@@ -52,15 +52,15 @@ module Cucumber
52
52
 
53
53
  def on_test_step_finished(event)
54
54
  test_step, result = *event.attributes
55
- return if @failing_step_source
55
+ return if @failing_test_step
56
56
 
57
- @failing_step_source = test_step.source.last unless result.ok?(@config.strict)
57
+ @failing_test_step = test_step unless result.ok?(@config.strict)
58
58
  end
59
59
 
60
60
  def on_test_case_finished(event)
61
61
  test_case, result = *event.attributes
62
62
  result = result.with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter)
63
- test_case_name = NameBuilder.new(test_case)
63
+ test_case_name = NameBuilder.new(test_case, @ast_lookup)
64
64
  scenario = test_case_name.scenario_name
65
65
  scenario_designation = "#{scenario}#{test_case_name.name_suffix}"
66
66
  output = create_output_string(test_case, scenario, result, test_case_name.row_name)
@@ -71,45 +71,54 @@ module Cucumber
71
71
  end
72
72
 
73
73
  def on_test_run_finished(_event)
74
- @features_data.each { |file, data| end_feature(data) }
74
+ @features_data.each { |_file, data| end_feature(data) }
75
75
  end
76
76
 
77
77
  private
78
78
 
79
- def same_feature_as_previous_test_case?(feature)
80
- @current_feature_data && @current_feature_data[:feature].file == feature.file && @current_feature_data[:feature].location == feature.location
79
+ def same_feature_as_previous_test_case?(test_case)
80
+ @current_feature_data && @current_feature_data[:uri] == test_case.location.file
81
81
  end
82
82
 
83
- def start_feature(feature)
84
- raise UnNamedFeatureError.new(feature.file) if feature.name.empty?
85
- @current_feature_data = @features_data[feature.file]
83
+ def start_feature(test_case)
84
+ uri = test_case.location.file
85
+ feature = @ast_lookup.gherkin_document(uri).feature
86
+ raise UnNamedFeatureError, uri if feature.name.empty?
87
+ @current_feature_data = @features_data[uri]
88
+ @current_feature_data[:uri] = uri unless @current_feature_data[:uri]
86
89
  @current_feature_data[:feature] = feature unless @current_feature_data[:feature]
87
90
  end
88
91
 
89
92
  def end_feature(feature_data)
90
- @testsuite = Builder::XmlMarkup.new(:indent => 2)
93
+ @testsuite = Builder::XmlMarkup.new(indent: 2)
91
94
  @testsuite.instruct!
92
95
  @testsuite.testsuite(
93
- :failures => feature_data[:failures],
94
- :errors => feature_data[:errors],
95
- :skipped => feature_data[:skipped],
96
- :tests => feature_data[:tests],
97
- :time => format('%.6f', feature_data[:time]),
98
- :name => feature_data[:feature].name
96
+ failures: feature_data[:failures],
97
+ errors: feature_data[:errors],
98
+ skipped: feature_data[:skipped],
99
+ tests: feature_data[:tests],
100
+ time: format('%<time>.6f', time: feature_data[:time]),
101
+ name: feature_data[:feature].name
99
102
  ) do
100
103
  @testsuite << feature_data[:builder].target!
101
104
  end
102
105
 
103
- write_file(feature_result_filename(feature_data[:feature].file), @testsuite.target!)
106
+ write_file(feature_result_filename(feature_data[:uri]), @testsuite.target!)
104
107
  end
105
108
 
106
- def create_output_string(test_case, scenario, result, row_name)
107
- output = "#{test_case.keyword}: #{scenario}\n\n"
109
+ def create_output_string(test_case, scenario, result, row_name) # rubocop:disable Metrics/PerceivedComplexity
110
+ scenario_source = @ast_lookup.scenario_source(test_case)
111
+ keyword = scenario_source.type == :Scenario ? scenario_source.scenario.keyword : scenario_source.scenario_outline.keyword
112
+ output = "#{keyword}: #{scenario}\n\n"
108
113
  return output if result.ok?(@config.strict)
109
- if test_case.keyword == 'Scenario'
110
- if @failing_step_source
111
- output += @failing_step_source.keyword.to_s unless hook?(@failing_step_source)
112
- output += "#{@failing_step_source}\n"
114
+ if scenario_source.type == :Scenario
115
+ if @failing_test_step
116
+ if @failing_test_step.hook?
117
+ output += "#{@failing_test_step.text} at #{@failing_test_step.location}\n"
118
+ else
119
+ step_source = @ast_lookup.step_source(@failing_test_step).step
120
+ output += "#{step_source.keyword}#{@failing_test_step.text}\n"
121
+ end
113
122
  else # An Around hook has failed
114
123
  output += "Around hook\n"
115
124
  end
@@ -119,24 +128,20 @@ module Cucumber
119
128
  output + "\nMessage:\n"
120
129
  end
121
130
 
122
- def hook?(step)
123
- ['Before hook', 'After hook', 'AfterStep hook'].include? step.text
124
- end
125
-
126
131
  def build_testcase(result, scenario_designation, output)
127
132
  duration = ResultBuilder.new(result).test_case_duration
128
133
  @current_feature_data[:time] += duration
129
134
  classname = @current_feature_data[:feature].name
130
135
  name = scenario_designation
131
136
 
132
- @current_feature_data[:builder].testcase(:classname => classname, :name => name, :time => format('%.6f', duration)) do
137
+ @current_feature_data[:builder].testcase(classname: classname, name: name, time: format('%<duration>.6f', duration: duration)) do
133
138
  if !result.passed? && result.ok?(@config.strict)
134
139
  @current_feature_data[:builder].skipped
135
140
  @current_feature_data[:skipped] += 1
136
141
  elsif !result.passed?
137
142
  status = result.to_sym
138
143
  exception = get_backtrace_object(result)
139
- @current_feature_data[:builder].failure(:message => "#{status} #{name}", :type => status) do
144
+ @current_feature_data[:builder].failure(message: "#{status} #{name}", type: status) do
140
145
  @current_feature_data[:builder].cdata! output
141
146
  @current_feature_data[:builder].cdata!(format_exception(exception)) if exception
142
147
  end
@@ -154,11 +159,9 @@ module Cucumber
154
159
 
155
160
  def get_backtrace_object(result)
156
161
  if result.failed?
157
- return result.exception
162
+ result.exception
158
163
  elsif result.backtrace
159
- return result
160
- else
161
- return nil
164
+ result
162
165
  end
163
166
  end
164
167
 
@@ -171,7 +174,7 @@ module Cucumber
171
174
  end
172
175
 
173
176
  def basename(feature_file)
174
- File.basename(feature_file.gsub(/[\\\/]/, '-'), '.feature')
177
+ File.basename(feature_file.gsub(/[\\\/]/, '-'), '.feature') # rubocop:disable Style/RegexpLiteral
175
178
  end
176
179
 
177
180
  def write_file(feature_filename, data)
@@ -187,34 +190,29 @@ module Cucumber
187
190
  class NameBuilder
188
191
  attr_reader :scenario_name, :name_suffix, :row_name
189
192
 
190
- def initialize(test_case)
193
+ def initialize(test_case, ast_lookup)
191
194
  @name_suffix = ''
192
195
  @row_name = ''
193
- test_case.describe_source_to self
194
- end
195
-
196
- def feature(*)
197
- self
196
+ scenario_source = ast_lookup.scenario_source(test_case)
197
+ if scenario_source.type == :Scenario
198
+ scenario(scenario_source.scenario)
199
+ else
200
+ scenario_outline(scenario_source.scenario_outline)
201
+ examples_table_row(scenario_source.row)
202
+ end
198
203
  end
199
204
 
200
205
  def scenario(scenario)
201
- @scenario_name = (scenario.name.nil? || scenario.name == '') ? 'Unnamed scenario' : scenario.name
202
- self
206
+ @scenario_name = scenario.name.empty? ? 'Unnamed scenario' : scenario.name
203
207
  end
204
208
 
205
209
  def scenario_outline(outline)
206
- @scenario_name = (outline.name.nil? || outline.name == '') ? 'Unnamed scenario outline' : outline.name
207
- self
208
- end
209
-
210
- def examples_table(*)
211
- self
210
+ @scenario_name = outline.name.empty? ? 'Unnamed scenario outline' : outline.name
212
211
  end
213
212
 
214
213
  def examples_table_row(row)
215
- @row_name = '| ' + row.values.join(' | ') + ' |'
214
+ @row_name = '| ' + row.cells.map(&:value).join(' | ') + ' |'
216
215
  @name_suffix = " (outline example : #{@row_name})"
217
- self
218
216
  end
219
217
  end
220
218
 
@@ -238,8 +236,10 @@ module Cucumber
238
236
  def exception(*) end
239
237
 
240
238
  def duration(duration, *)
241
- duration.tap { |duration| @test_case_duration = duration.nanoseconds / 10**9.0 }
239
+ duration.tap { |dur| @test_case_duration = dur.nanoseconds / 10**9.0 }
242
240
  end
241
+
242
+ def attach(*) end
243
243
  end
244
244
  end
245
245
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cucumber/formatter/io'
4
+ require 'cucumber/formatter/message_builder'
5
+
6
+ module Cucumber
7
+ module Formatter
8
+ # The formatter used for <tt>--format message</tt>
9
+ class Message < MessageBuilder
10
+ include Io
11
+
12
+ def initialize(config)
13
+ @io = ensure_io(config.out_stream)
14
+ super(config)
15
+ end
16
+
17
+ def output_envelope(envelope)
18
+ envelope.write_ndjson_to(@io)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,255 @@
1
+ require 'base64'
2
+ require 'cucumber/formatter/backtrace_filter'
3
+ require 'cucumber/formatter/query/hook_by_test_step'
4
+ require 'cucumber/formatter/query/pickle_by_test'
5
+ require 'cucumber/formatter/query/pickle_step_by_test_step'
6
+ require 'cucumber/formatter/query/step_definitions_by_test_step'
7
+ require 'cucumber/formatter/query/test_case_started_by_test_case'
8
+
9
+ module Cucumber
10
+ module Formatter
11
+ class MessageBuilder
12
+ include Cucumber::Messages::TimeConversion
13
+
14
+ def initialize(config)
15
+ @config = config
16
+
17
+ @hook_by_test_step = Query::HookByTestStep.new(config)
18
+ @pickle_by_test = Query::PickleByTest.new(config)
19
+ @pickle_step_by_test_step = Query::PickleStepByTestStep.new(config)
20
+ @step_definitions_by_test_step = Query::StepDefinitionsByTestStep.new(config)
21
+ @test_case_started_by_test_case = Query::TestCaseStartedByTestCase.new(config)
22
+
23
+ config.on_event :envelope, &method(:on_envelope)
24
+ config.on_event :gherkin_source_read, &method(:on_gherkin_source_read)
25
+ config.on_event :test_case_ready, &method(:on_test_case_ready)
26
+ config.on_event :test_run_started, &method(:on_test_run_started)
27
+ config.on_event :test_case_started, &method(:on_test_case_started)
28
+ config.on_event :test_step_started, &method(:on_test_step_started)
29
+ config.on_event :test_step_finished, &method(:on_test_step_finished)
30
+ config.on_event :test_case_finished, &method(:on_test_case_finished)
31
+ config.on_event :test_run_finished, &method(:on_test_run_finished)
32
+ config.on_event :undefined_parameter_type, &method(:on_undefined_parameter_type)
33
+
34
+ @test_case_by_step_id = {}
35
+ @current_test_case_started_id = nil
36
+ @current_test_step_id = nil
37
+ end
38
+
39
+ def output_message
40
+ raise 'To be implemented'
41
+ end
42
+
43
+ def attach(src, media_type)
44
+ attachment_data = {
45
+ test_step_id: @current_test_step_id,
46
+ test_case_started_id: @current_test_case_started_id,
47
+ media_type: media_type
48
+ }
49
+
50
+ if media_type.start_with?('text/')
51
+ attachment_data[:content_encoding] = Cucumber::Messages::Attachment::ContentEncoding::IDENTITY
52
+ attachment_data[:body] = src
53
+ else
54
+ body = src.respond_to?(:read) ? src.read : src
55
+
56
+ attachment_data[:content_encoding] = Cucumber::Messages::Attachment::ContentEncoding::BASE64
57
+ attachment_data[:body] = Base64.strict_encode64(body)
58
+ end
59
+
60
+ message = Cucumber::Messages::Envelope.new(
61
+ attachment: Cucumber::Messages::Attachment.new(**attachment_data)
62
+ )
63
+
64
+ output_envelope(message)
65
+ end
66
+
67
+ private
68
+
69
+ def on_envelope(event)
70
+ output_envelope(event.envelope)
71
+ end
72
+
73
+ def on_gherkin_source_read(event)
74
+ message = Cucumber::Messages::Envelope.new(
75
+ source: Cucumber::Messages::Source.new(
76
+ uri: event.path,
77
+ data: event.body,
78
+ media_type: 'text/x.cucumber.gherkin+plain'
79
+ )
80
+ )
81
+
82
+ output_envelope(message)
83
+ end
84
+
85
+ def on_test_case_ready(event)
86
+ event.test_case.test_steps.each do |step|
87
+ @test_case_by_step_id[step.id] = event.test_case
88
+ end
89
+
90
+ message = Cucumber::Messages::Envelope.new(
91
+ test_case: Cucumber::Messages::TestCase.new(
92
+ id: event.test_case.id,
93
+ pickle_id: @pickle_by_test.pickle_id(event.test_case),
94
+ test_steps: event.test_case.test_steps.map { |step| test_step_to_message(step) }
95
+ )
96
+ )
97
+
98
+ output_envelope(message)
99
+ end
100
+
101
+ def test_step_to_message(step)
102
+ return hook_step_to_message(step) if step.hook?
103
+
104
+ Cucumber::Messages::TestCase::TestStep.new(
105
+ id: step.id,
106
+ pickle_step_id: @pickle_step_by_test_step.pickle_step_id(step),
107
+ step_definition_ids: @step_definitions_by_test_step.step_definition_ids(step),
108
+ step_match_arguments_lists: step_match_arguments_lists(step)
109
+ )
110
+ end
111
+
112
+ def hook_step_to_message(step)
113
+ Cucumber::Messages::TestCase::TestStep.new(
114
+ id: step.id,
115
+ hook_id: @hook_by_test_step.hook_id(step)
116
+ )
117
+ end
118
+
119
+ def step_match_arguments_lists(step)
120
+ match_arguments = step_match_arguments(step)
121
+ [Cucumber::Messages::TestCase::TestStep::StepMatchArgumentsList.new(
122
+ step_match_arguments: match_arguments
123
+ )]
124
+ rescue Cucumber::Formatter::TestStepUnknownError
125
+ []
126
+ end
127
+
128
+ def step_match_arguments(step)
129
+ @step_definitions_by_test_step.step_match_arguments(step).map do |argument|
130
+ Cucumber::Messages::TestCase::TestStep::StepMatchArgumentsList::StepMatchArgument.new(
131
+ group: argument_group_to_message(argument.group),
132
+ parameter_type_name: argument.parameter_type.name
133
+ )
134
+ end
135
+ end
136
+
137
+ def argument_group_to_message(group)
138
+ Cucumber::Messages::TestCase::TestStep::StepMatchArgumentsList::StepMatchArgument::Group.new(
139
+ start: group.start,
140
+ value: group.value,
141
+ children: group.children.map { |child| argument_group_to_message(child) }
142
+ )
143
+ end
144
+
145
+ def on_test_run_started(*)
146
+ message = Cucumber::Messages::Envelope.new(
147
+ test_run_started: Cucumber::Messages::TestRunStarted.new(
148
+ timestamp: time_to_timestamp(Time.now)
149
+ )
150
+ )
151
+
152
+ output_envelope(message)
153
+ end
154
+
155
+ def on_test_case_started(event)
156
+ @current_test_case_started_id = test_case_started_id(event.test_case)
157
+
158
+ message = Cucumber::Messages::Envelope.new(
159
+ test_case_started: Cucumber::Messages::TestCaseStarted.new(
160
+ id: test_case_started_id(event.test_case),
161
+ test_case_id: event.test_case.id,
162
+ timestamp: time_to_timestamp(Time.now),
163
+ attempt: @test_case_started_by_test_case.attempt_by_test_case(event.test_case)
164
+ )
165
+ )
166
+
167
+ output_envelope(message)
168
+ end
169
+
170
+ def on_test_step_started(event)
171
+ @current_test_step_id = event.test_step.id
172
+ test_case = @test_case_by_step_id[event.test_step.id]
173
+
174
+ message = Cucumber::Messages::Envelope.new(
175
+ test_step_started: Cucumber::Messages::TestStepStarted.new(
176
+ test_step_id: event.test_step.id,
177
+ test_case_started_id: test_case_started_id(test_case),
178
+ timestamp: time_to_timestamp(Time.now)
179
+ )
180
+ )
181
+
182
+ output_envelope(message)
183
+ end
184
+
185
+ def on_test_step_finished(event)
186
+ test_case = @test_case_by_step_id[event.test_step.id]
187
+ result = event
188
+ .result
189
+ .with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter)
190
+
191
+ result_message = result.to_message
192
+ if result.failed? || result.pending?
193
+ result_message = Cucumber::Messages::TestStepFinished::TestStepResult.new(
194
+ status: result_message.status,
195
+ duration: result_message.duration,
196
+ message: create_error_message(result)
197
+ )
198
+ end
199
+
200
+ message = Cucumber::Messages::Envelope.new(
201
+ test_step_finished: Cucumber::Messages::TestStepFinished.new(
202
+ test_step_id: event.test_step.id,
203
+ test_case_started_id: test_case_started_id(test_case),
204
+ test_step_result: result_message,
205
+ timestamp: time_to_timestamp(Time.now)
206
+ )
207
+ )
208
+
209
+ output_envelope(message)
210
+ end
211
+
212
+ def create_error_message(result)
213
+ message_element = result.failed? ? result.exception : result
214
+ message = "#{message_element.message} (#{message_element.class})"
215
+ ([message] + message_element.backtrace).join("\n")
216
+ end
217
+
218
+ def on_test_case_finished(event)
219
+ message = Cucumber::Messages::Envelope.new(
220
+ test_case_finished: Cucumber::Messages::TestCaseFinished.new(
221
+ test_case_started_id: test_case_started_id(event.test_case),
222
+ timestamp: time_to_timestamp(Time.now)
223
+ )
224
+ )
225
+
226
+ output_envelope(message)
227
+ end
228
+
229
+ def on_test_run_finished(*)
230
+ message = Cucumber::Messages::Envelope.new(
231
+ test_run_finished: Cucumber::Messages::TestRunFinished.new(
232
+ timestamp: time_to_timestamp(Time.now)
233
+ )
234
+ )
235
+
236
+ output_envelope(message)
237
+ end
238
+
239
+ def on_undefined_parameter_type(event)
240
+ message = Cucumber::Messages::Envelope.new(
241
+ undefined_parameter_type: Cucumber::Messages::UndefinedParameterType.new(
242
+ name: event.type_name,
243
+ expression: event.expression
244
+ )
245
+ )
246
+
247
+ output_envelope(message)
248
+ end
249
+
250
+ def test_case_started_id(test_case)
251
+ @test_case_started_by_test_case.test_case_started_id_by_test_case(test_case)
252
+ end
253
+ end
254
+ end
255
+ end