cucumber 3.2.0 → 4.0.0.rc.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 (90) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +61 -18
  3. data/CONTRIBUTING.md +1 -0
  4. data/bin/cucumber +1 -1
  5. data/lib/autotest/cucumber_mixin.rb +42 -39
  6. data/lib/cucumber/cli/configuration.rb +4 -4
  7. data/lib/cucumber/cli/main.rb +11 -12
  8. data/lib/cucumber/cli/options.rb +56 -69
  9. data/lib/cucumber/cli/profile_loader.rb +32 -20
  10. data/lib/cucumber/configuration.rb +20 -21
  11. data/lib/cucumber/constantize.rb +2 -5
  12. data/lib/cucumber/deprecate.rb +5 -5
  13. data/lib/cucumber/errors.rb +4 -6
  14. data/lib/cucumber/events.rb +1 -0
  15. data/lib/cucumber/events/gherkin_source_parsed.rb +11 -0
  16. data/lib/cucumber/events/step_activated.rb +2 -1
  17. data/lib/cucumber/file_specs.rb +6 -6
  18. data/lib/cucumber/filters/activate_steps.rb +5 -3
  19. data/lib/cucumber/filters/prepare_world.rb +5 -9
  20. data/lib/cucumber/filters/quit.rb +1 -3
  21. data/lib/cucumber/filters/tag_limits/verifier.rb +2 -4
  22. data/lib/cucumber/formatter/ansicolor.rb +40 -45
  23. data/lib/cucumber/formatter/ast_lookup.rb +160 -0
  24. data/lib/cucumber/formatter/backtrace_filter.rb +5 -7
  25. data/lib/cucumber/formatter/console.rb +28 -59
  26. data/lib/cucumber/formatter/console_counts.rb +4 -9
  27. data/lib/cucumber/formatter/console_issues.rb +6 -3
  28. data/lib/cucumber/formatter/duration_extractor.rb +1 -1
  29. data/lib/cucumber/formatter/fanout.rb +2 -0
  30. data/lib/cucumber/formatter/ignore_missing_messages.rb +1 -1
  31. data/lib/cucumber/formatter/interceptor.rb +5 -7
  32. data/lib/cucumber/formatter/io.rb +8 -14
  33. data/lib/cucumber/formatter/json.rb +93 -117
  34. data/lib/cucumber/formatter/junit.rb +55 -57
  35. data/lib/cucumber/formatter/pretty.rb +346 -152
  36. data/lib/cucumber/formatter/progress.rb +28 -32
  37. data/lib/cucumber/formatter/rerun.rb +22 -4
  38. data/lib/cucumber/formatter/stepdefs.rb +1 -2
  39. data/lib/cucumber/formatter/steps.rb +2 -3
  40. data/lib/cucumber/formatter/summary.rb +16 -8
  41. data/lib/cucumber/formatter/unicode.rb +15 -17
  42. data/lib/cucumber/formatter/usage.rb +9 -8
  43. data/lib/cucumber/gherkin/data_table_parser.rb +8 -6
  44. data/lib/cucumber/gherkin/formatter/ansi_escapes.rb +13 -17
  45. data/lib/cucumber/gherkin/formatter/escaping.rb +2 -2
  46. data/lib/cucumber/gherkin/steps_parser.rb +7 -8
  47. data/lib/cucumber/glue/dsl.rb +1 -1
  48. data/lib/cucumber/glue/hook.rb +16 -9
  49. data/lib/cucumber/glue/invoke_in_world.rb +13 -18
  50. data/lib/cucumber/glue/proto_world.rb +14 -16
  51. data/lib/cucumber/glue/registry_and_more.rb +7 -9
  52. data/lib/cucumber/glue/snippet.rb +21 -20
  53. data/lib/cucumber/glue/step_definition.rb +14 -15
  54. data/lib/cucumber/glue/world_factory.rb +1 -1
  55. data/lib/cucumber/hooks.rb +11 -11
  56. data/lib/cucumber/multiline_argument.rb +4 -6
  57. data/lib/cucumber/multiline_argument/data_table.rb +88 -59
  58. data/lib/cucumber/multiline_argument/data_table/diff_matrices.rb +1 -1
  59. data/lib/cucumber/multiline_argument/doc_string.rb +1 -1
  60. data/lib/cucumber/platform.rb +3 -3
  61. data/lib/cucumber/rake/task.rb +13 -16
  62. data/lib/cucumber/rspec/disable_option_parser.rb +9 -8
  63. data/lib/cucumber/running_test_case.rb +2 -53
  64. data/lib/cucumber/runtime.rb +27 -57
  65. data/lib/cucumber/runtime/after_hooks.rb +3 -3
  66. data/lib/cucumber/runtime/before_hooks.rb +3 -3
  67. data/lib/cucumber/runtime/for_programming_languages.rb +3 -2
  68. data/lib/cucumber/runtime/step_hooks.rb +1 -1
  69. data/lib/cucumber/runtime/support_code.rb +10 -12
  70. data/lib/cucumber/runtime/user_interface.rb +4 -6
  71. data/lib/cucumber/step_definition_light.rb +4 -3
  72. data/lib/cucumber/step_match.rb +12 -11
  73. data/lib/cucumber/step_match_search.rb +2 -1
  74. data/lib/cucumber/term/ansicolor.rb +9 -9
  75. data/lib/cucumber/version +1 -1
  76. metadata +37 -28
  77. data/lib/cucumber/formatter/cucumber.css +0 -286
  78. data/lib/cucumber/formatter/cucumber.sass +0 -247
  79. data/lib/cucumber/formatter/hook_query_visitor.rb +0 -42
  80. data/lib/cucumber/formatter/html.rb +0 -611
  81. data/lib/cucumber/formatter/html_builder.rb +0 -121
  82. data/lib/cucumber/formatter/http_io.rb +0 -146
  83. data/lib/cucumber/formatter/inline-js.js +0 -30
  84. data/lib/cucumber/formatter/jquery-min.js +0 -154
  85. data/lib/cucumber/formatter/json_pretty.rb +0 -11
  86. data/lib/cucumber/formatter/legacy_api/adapter.rb +0 -1028
  87. data/lib/cucumber/formatter/legacy_api/ast.rb +0 -394
  88. data/lib/cucumber/formatter/legacy_api/results.rb +0 -50
  89. data/lib/cucumber/formatter/legacy_api/runtime_facade.rb +0 -32
  90. data/lib/cucumber/step_argument.rb +0 -25
@@ -1,27 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cucumber/formatter/http_io'
4
-
5
3
  module Cucumber
6
4
  module Formatter
7
5
  module Io
8
6
  module_function
9
7
 
10
- def ensure_io(path_or_url_or_io)
11
- return nil if path_or_url_or_io.nil?
12
- return path_or_url_or_io if path_or_url_or_io.respond_to?(:write)
13
- io = if path_or_url_or_io =~ %r{^https?://}
14
- HTTPIO.open(path_or_url_or_io)
15
- else
16
- File.open(path_or_url_or_io, Cucumber.file_mode('w'))
17
- end
8
+ def ensure_io(path_or_io)
9
+ return nil if path_or_io.nil?
10
+ return path_or_io if path_or_io.respond_to?(:write)
11
+ file = File.open(path_or_io, Cucumber.file_mode('w'))
18
12
  at_exit do
19
- unless io.closed?
20
- io.flush
21
- io.close
13
+ unless file.closed?
14
+ file.flush
15
+ file.close
22
16
  end
23
17
  end
24
- io
18
+ file
25
19
  end
26
20
 
27
21
  def ensure_file(path, name)
@@ -4,7 +4,7 @@ require 'multi_json'
4
4
  require 'base64'
5
5
  require 'cucumber/formatter/backtrace_filter'
6
6
  require 'cucumber/formatter/io'
7
- require 'cucumber/formatter/hook_query_visitor'
7
+ require 'cucumber/formatter/ast_lookup'
8
8
 
9
9
  module Cucumber
10
10
  module Formatter
@@ -14,6 +14,7 @@ module Cucumber
14
14
 
15
15
  def initialize(config)
16
16
  @io = ensure_io(config.out_stream)
17
+ @ast_lookup = AstLookup.new(config)
17
18
  @feature_hashes = []
18
19
  @step_or_hook_hash = {}
19
20
  config.on_event :test_case_started, &method(:on_test_case_started)
@@ -25,16 +26,18 @@ module Cucumber
25
26
 
26
27
  def on_test_case_started(event)
27
28
  test_case = event.test_case
28
- builder = Builder.new(test_case)
29
- unless same_feature_as_previous_test_case?(test_case.feature)
29
+ builder = Builder.new(test_case, @ast_lookup)
30
+ unless same_feature_as_previous_test_case?(test_case)
30
31
  @feature_hash = builder.feature_hash
31
32
  @feature_hashes << @feature_hash
32
33
  end
33
34
  @test_case_hash = builder.test_case_hash
34
35
  if builder.background?
36
+ @in_background = true
35
37
  feature_elements << builder.background_hash
36
38
  @element_hash = builder.background_hash
37
39
  else
40
+ @in_background = false
38
41
  feature_elements << @test_case_hash
39
42
  @element_hash = @test_case_hash
40
43
  end
@@ -44,17 +47,17 @@ module Cucumber
44
47
  def on_test_step_started(event)
45
48
  test_step = event.test_step
46
49
  return if internal_hook?(test_step)
47
- hook_query = HookQueryVisitor.new(test_step)
48
- if hook_query.hook?
50
+ if test_step.hook?
49
51
  @step_or_hook_hash = {}
50
- hooks_of_type(hook_query) << @step_or_hook_hash
52
+ hooks_of_type(test_step) << @step_or_hook_hash
51
53
  return
52
54
  end
53
55
  if first_step_after_background?(test_step)
56
+ @in_background = false
54
57
  feature_elements << @test_case_hash
55
58
  @element_hash = @test_case_hash
56
59
  end
57
- @step_or_hook_hash = create_step_hash(test_step.source.last)
60
+ @step_or_hook_hash = create_step_hash(test_step)
58
61
  steps << @step_or_hook_hash
59
62
  @step_hash = @step_or_hook_hash
60
63
  end
@@ -82,42 +85,34 @@ module Cucumber
82
85
  end
83
86
 
84
87
  def embed(src, mime_type, _label)
85
- begin
86
- is_file = File.file?(src)
87
- rescue ArgumentError
88
- is_file = false
89
- end
90
-
91
- if is_file
88
+ if File.file?(src)
92
89
  content = File.open(src, 'rb', &:read)
93
90
  data = encode64(content)
91
+ elsif mime_type =~ /;base64$/
92
+ mime_type = mime_type[0..-8]
93
+ data = src
94
94
  else
95
- if mime_type =~ /;base64$/
96
- mime_type = mime_type[0..-8]
97
- data = src
98
- else
99
- data = encode64(src)
100
- end
95
+ data = encode64(src)
101
96
  end
102
97
  test_step_embeddings << { mime_type: mime_type, data: data }
103
98
  end
104
99
 
105
100
  private
106
101
 
107
- def same_feature_as_previous_test_case?(feature)
108
- current_feature[:uri] == feature.file && current_feature[:line] == feature.location.line
102
+ def same_feature_as_previous_test_case?(test_case)
103
+ current_feature[:uri] == test_case.location.file
109
104
  end
110
105
 
111
106
  def first_step_after_background?(test_step)
112
- test_step.source[1].to_s != @element_hash[:name]
107
+ @in_background && test_step.location.lines.max >= @test_case_hash[:line]
113
108
  end
114
109
 
115
110
  def internal_hook?(test_step)
116
- test_step.source.last.location.file.include?('lib/cucumber/')
111
+ test_step.location.file.include?('lib/cucumber/')
117
112
  end
118
113
 
119
114
  def current_feature
120
- @feature_hash ||= {}
115
+ @feature_hash ||= {} # rubocop:disable Naming/MemoizedInstanceVariableName
121
116
  end
122
117
 
123
118
  def feature_elements
@@ -128,16 +123,16 @@ module Cucumber
128
123
  @element_hash[:steps] ||= []
129
124
  end
130
125
 
131
- def hooks_of_type(hook_query)
132
- case hook_query.type
133
- when :before
134
- return before_hooks
135
- when :after
136
- return after_hooks
137
- when :after_step
138
- return after_step_hooks
126
+ def hooks_of_type(hook_step)
127
+ case hook_step.text
128
+ when 'Before hook'
129
+ before_hooks
130
+ when 'After hook'
131
+ after_hooks
132
+ when 'AfterStep hook'
133
+ after_step_hooks
139
134
  else
140
- fail 'Unknown hook type ' + hook_query.type.to_s
135
+ raise 'Unknown hook type ' + hook_step.to_s
141
136
  end
142
137
  end
143
138
 
@@ -165,30 +160,30 @@ module Cucumber
165
160
  @step_or_hook_hash[:embeddings] ||= []
166
161
  end
167
162
 
168
- def create_step_hash(step_source)
163
+ def create_step_hash(test_step)
164
+ step_source = @ast_lookup.step_source(test_step).step
169
165
  step_hash = {
170
- keyword: step_source.keyword,
171
- name: step_source.to_s,
172
- line: step_source.original_location.line
166
+ keyword: step_source[:keyword],
167
+ name: test_step.text,
168
+ line: test_step.location.lines.min
173
169
  }
174
- step_hash[:comments] = Formatter.create_comments_array(step_source.comments) unless step_source.comments.empty?
175
- step_hash[:doc_string] = create_doc_string_hash(step_source.multiline_arg) if step_source.multiline_arg.doc_string?
176
- step_hash[:rows] = create_data_table_value(step_source.multiline_arg) if step_source.multiline_arg.data_table?
170
+ step_hash[:doc_string] = create_doc_string_hash(step_source[:doc_string]) unless step_source[:doc_string].nil?
171
+ step_hash[:rows] = create_data_table_value(step_source[:data_table]) unless step_source[:data_table].nil?
177
172
  step_hash
178
173
  end
179
174
 
180
175
  def create_doc_string_hash(doc_string)
181
- content_type = doc_string.content_type ? doc_string.content_type : ''
176
+ content_type = doc_string[:content_type] || ''
182
177
  {
183
- value: doc_string.content,
178
+ value: doc_string[:content],
184
179
  content_type: content_type,
185
- line: doc_string.location.line
180
+ line: doc_string[:location][:line]
186
181
  }
187
182
  end
188
183
 
189
184
  def create_data_table_value(data_table)
190
- data_table.raw.map do |row|
191
- { cells: row }
185
+ data_table[:rows].map do |row|
186
+ { cells: row[:cells].map { |cell| cell[:value] } }
192
187
  end
193
188
  end
194
189
 
@@ -232,113 +227,94 @@ module Cucumber
232
227
  class Builder
233
228
  attr_reader :feature_hash, :background_hash, :test_case_hash
234
229
 
235
- def initialize(test_case)
230
+ def initialize(test_case, ast_lookup)
236
231
  @background_hash = nil
237
- test_case.describe_source_to(self)
238
- test_case.feature.background.describe_to(self)
232
+ uri = test_case.location.file
233
+ feature = ast_lookup.gherkin_document(uri)[:feature]
234
+ feature(feature, uri)
235
+ background(feature[:children].first[:background]) unless feature[:children].first[:background].nil?
236
+ scenario(ast_lookup.scenario_source(test_case), test_case)
239
237
  end
240
238
 
241
239
  def background?
242
240
  @background_hash != nil
243
241
  end
244
242
 
245
- def feature(feature)
243
+ def feature(feature, uri)
246
244
  @feature_hash = {
247
- uri: feature.file,
248
- id: create_id(feature),
249
- keyword: feature.keyword,
250
- name: feature.to_s,
251
- description: feature.description,
252
- line: feature.location.line
245
+ id: create_id(feature[:name]),
246
+ uri: uri,
247
+ keyword: feature[:keyword],
248
+ name: feature[:name],
249
+ description: value_or_empty_string(feature[:description]),
250
+ line: feature[:location][:line]
253
251
  }
254
- unless feature.tags.empty?
255
- @feature_hash[:tags] = create_tags_array(feature.tags)
256
- @test_case_hash[:tags] = if @test_case_hash[:tags]
257
- @feature_hash[:tags] + @test_case_hash[:tags]
258
- else
259
- @feature_hash[:tags]
260
- end
261
- end
262
- @feature_hash[:comments] = Formatter.create_comments_array(feature.comments) unless feature.comments.empty?
263
- @test_case_hash[:id].insert(0, @feature_hash[:id] + ';')
252
+ return if feature[:tags].empty?
253
+ @feature_hash[:tags] = create_tags_array_from_hash_array(feature[:tags])
264
254
  end
265
255
 
266
256
  def background(background)
267
257
  @background_hash = {
268
- keyword: background.keyword,
269
- name: background.to_s,
270
- description: background.description,
271
- line: background.location.line,
258
+ keyword: background[:keyword],
259
+ name: background[:name],
260
+ description: value_or_empty_string(background[:description]),
261
+ line: background[:location][:line],
272
262
  type: 'background'
273
263
  }
274
- @background_hash[:comments] = Formatter.create_comments_array(background.comments) unless background.comments.empty?
275
264
  end
276
265
 
277
- def scenario(scenario)
266
+ def scenario(scenario_source, test_case)
267
+ scenario = scenario_source.type == :Scenario ? scenario_source.scenario : scenario_source.scenario_outline
278
268
  @test_case_hash = {
279
- id: create_id(scenario),
280
- keyword: scenario.keyword,
281
- name: scenario.to_s,
282
- description: scenario.description,
283
- line: scenario.location.line,
269
+ id: "#{@feature_hash[:id]};#{create_id_from_scenario_source(scenario_source)}",
270
+ keyword: scenario[:keyword],
271
+ name: scenario[:name],
272
+ description: value_or_empty_string(scenario[:description]),
273
+ line: test_case.location.lines.max,
284
274
  type: 'scenario'
285
275
  }
286
- @test_case_hash[:tags] = create_tags_array(scenario.tags) unless scenario.tags.empty?
287
- @test_case_hash[:comments] = Formatter.create_comments_array(scenario.comments) unless scenario.comments.empty?
276
+ @test_case_hash[:tags] = create_tags_array_from_tags_array(test_case.tags) unless test_case.tags.empty?
288
277
  end
289
278
 
290
- def scenario_outline(scenario)
291
- @test_case_hash = {
292
- id: create_id(scenario) + ';' + @example_id,
293
- keyword: scenario.keyword,
294
- name: scenario.to_s,
295
- description: scenario.description,
296
- line: @row.location.line,
297
- type: 'scenario'
298
- }
299
- tags = []
300
- tags += create_tags_array(scenario.tags) unless scenario.tags.empty?
301
- tags += @examples_table_tags if @examples_table_tags
302
- @test_case_hash[:tags] = tags unless tags.empty?
303
- comments = []
304
- comments += Formatter.create_comments_array(scenario.comments) unless scenario.comments.empty?
305
- comments += @examples_table_comments if @examples_table_comments
306
- comments += @row_comments if @row_comments
307
- @test_case_hash[:comments] = comments unless comments.empty?
308
- end
279
+ private
309
280
 
310
- def examples_table(examples_table)
311
- # the json file have traditionally used the header row as row 1,
312
- # wheras cucumber-ruby-core used the first example row as row 1.
313
- @example_id = create_id(examples_table) + ";#{@row.number + 1}"
281
+ def value_or_empty_string(value)
282
+ value.nil? ? '' : value
283
+ end
314
284
 
315
- @examples_table_tags = create_tags_array(examples_table.tags) unless examples_table.tags.empty?
316
- @examples_table_comments = Formatter.create_comments_array(examples_table.comments) unless examples_table.comments.empty?
285
+ def create_id(name)
286
+ name.downcase.tr(' ', '-')
317
287
  end
318
288
 
319
- def examples_table_row(row)
320
- @row = row
321
- @row_comments = Formatter.create_comments_array(row.comments) unless row.comments.empty?
289
+ def create_id_from_scenario_source(scenario_source)
290
+ if scenario_source.type == :Scenario
291
+ create_id(scenario_source.scenario[:name])
292
+ else
293
+ scenario_outline_name = scenario_source.scenario_outline[:name]
294
+ examples_name = scenario_source.examples[:name]
295
+ row_number = calculate_row_number(scenario_source)
296
+ "#{create_id(scenario_outline_name)};#{create_id(examples_name)};#{row_number}"
297
+ end
322
298
  end
323
299
 
324
- private
300
+ def calculate_row_number(scenario_source)
301
+ scenario_source.examples[:table_body].each_with_index do |row, index|
302
+ return index + 2 if row == scenario_source.row
303
+ end
304
+ end
325
305
 
326
- def create_id(element)
327
- element.to_s.downcase.tr(' ', '-')
306
+ def create_tags_array_from_hash_array(tags)
307
+ tags_array = []
308
+ tags.each { |tag| tags_array << { name: tag[:name], line: tag[:location][:line] } }
309
+ tags_array
328
310
  end
329
311
 
330
- def create_tags_array(tags)
312
+ def create_tags_array_from_tags_array(tags)
331
313
  tags_array = []
332
314
  tags.each { |tag| tags_array << { name: tag.name, line: tag.location.line } }
333
315
  tags_array
334
316
  end
335
317
  end
336
318
  end
337
-
338
- def self.create_comments_array(comments)
339
- comments_array = []
340
- comments.each { |comment| comments_array << { value: comment.to_s.strip, line: comment.location.line } }
341
- comments_array
342
- end
343
319
  end
344
320
  end
@@ -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('%.6f', 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
- classname = @current_feature_data[:feature].name
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('%.6f', 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 { |cell| cell[:value] }.join(' | ') + ' |'
216
215
  @name_suffix = " (outline example : #{@row_name})"
217
- self
218
216
  end
219
217
  end
220
218
 
@@ -238,7 +236,7 @@ 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
243
241
  end
244
242
  end