cucumber 3.1.2 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +173 -14
  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.rb +1 -1
  8. data/lib/cucumber/cli/configuration.rb +5 -5
  9. data/lib/cucumber/cli/main.rb +12 -12
  10. data/lib/cucumber/cli/options.rb +69 -74
  11. data/lib/cucumber/cli/profile_loader.rb +49 -26
  12. data/lib/cucumber/configuration.rb +31 -23
  13. data/lib/cucumber/constantize.rb +2 -5
  14. data/lib/cucumber/deprecate.rb +31 -7
  15. data/lib/cucumber/errors.rb +5 -7
  16. data/lib/cucumber/events.rb +13 -6
  17. data/lib/cucumber/events/envelope.rb +9 -0
  18. data/lib/cucumber/events/gherkin_source_parsed.rb +11 -0
  19. data/lib/cucumber/events/hook_test_step_created.rb +13 -0
  20. data/lib/cucumber/events/step_activated.rb +2 -1
  21. data/lib/cucumber/events/test_case_created.rb +13 -0
  22. data/lib/cucumber/events/test_case_ready.rb +12 -0
  23. data/lib/cucumber/events/test_step_created.rb +13 -0
  24. data/lib/cucumber/events/undefined_parameter_type.rb +10 -0
  25. data/lib/cucumber/file_specs.rb +6 -6
  26. data/lib/cucumber/filters.rb +1 -0
  27. data/lib/cucumber/filters/activate_steps.rb +5 -3
  28. data/lib/cucumber/filters/broadcast_test_case_ready_event.rb +12 -0
  29. data/lib/cucumber/filters/prepare_world.rb +5 -9
  30. data/lib/cucumber/filters/quit.rb +1 -3
  31. data/lib/cucumber/filters/tag_limits/verifier.rb +2 -4
  32. data/lib/cucumber/formatter/ansicolor.rb +40 -45
  33. data/lib/cucumber/formatter/ast_lookup.rb +165 -0
  34. data/lib/cucumber/formatter/backtrace_filter.rb +9 -8
  35. data/lib/cucumber/formatter/console.rb +58 -66
  36. data/lib/cucumber/formatter/console_counts.rb +4 -9
  37. data/lib/cucumber/formatter/console_issues.rb +6 -3
  38. data/lib/cucumber/formatter/duration.rb +1 -1
  39. data/lib/cucumber/formatter/duration_extractor.rb +3 -1
  40. data/lib/cucumber/formatter/errors.rb +6 -0
  41. data/lib/cucumber/formatter/fanout.rb +2 -0
  42. data/lib/cucumber/formatter/html.rb +11 -598
  43. data/lib/cucumber/formatter/http_io.rb +146 -0
  44. data/lib/cucumber/formatter/ignore_missing_messages.rb +1 -1
  45. data/lib/cucumber/formatter/interceptor.rb +8 -28
  46. data/lib/cucumber/formatter/io.rb +17 -11
  47. data/lib/cucumber/formatter/json.rb +101 -109
  48. data/lib/cucumber/formatter/junit.rb +56 -56
  49. data/lib/cucumber/formatter/message.rb +22 -0
  50. data/lib/cucumber/formatter/message_builder.rb +255 -0
  51. data/lib/cucumber/formatter/pretty.rb +359 -153
  52. data/lib/cucumber/formatter/progress.rb +30 -32
  53. data/lib/cucumber/formatter/query/hook_by_test_step.rb +31 -0
  54. data/lib/cucumber/formatter/query/pickle_by_test.rb +26 -0
  55. data/lib/cucumber/formatter/query/pickle_step_by_test_step.rb +26 -0
  56. data/lib/cucumber/formatter/query/step_definitions_by_test_step.rb +40 -0
  57. data/lib/cucumber/formatter/query/test_case_started_by_test_case.rb +40 -0
  58. data/lib/cucumber/formatter/rerun.rb +22 -4
  59. data/lib/cucumber/formatter/stepdefs.rb +1 -2
  60. data/lib/cucumber/formatter/steps.rb +2 -3
  61. data/lib/cucumber/formatter/summary.rb +16 -8
  62. data/lib/cucumber/formatter/unicode.rb +15 -17
  63. data/lib/cucumber/formatter/usage.rb +11 -10
  64. data/lib/cucumber/gherkin/data_table_parser.rb +17 -6
  65. data/lib/cucumber/gherkin/formatter/ansi_escapes.rb +13 -17
  66. data/lib/cucumber/gherkin/formatter/escaping.rb +2 -2
  67. data/lib/cucumber/gherkin/steps_parser.rb +17 -8
  68. data/lib/cucumber/glue/dsl.rb +1 -1
  69. data/lib/cucumber/glue/hook.rb +34 -11
  70. data/lib/cucumber/glue/invoke_in_world.rb +13 -18
  71. data/lib/cucumber/glue/proto_world.rb +42 -33
  72. data/lib/cucumber/glue/registry_and_more.rb +42 -12
  73. data/lib/cucumber/glue/snippet.rb +23 -22
  74. data/lib/cucumber/glue/step_definition.rb +42 -19
  75. data/lib/cucumber/glue/world_factory.rb +1 -1
  76. data/lib/cucumber/hooks.rb +11 -11
  77. data/lib/cucumber/multiline_argument.rb +4 -6
  78. data/lib/cucumber/multiline_argument/data_table.rb +97 -64
  79. data/lib/cucumber/multiline_argument/data_table/diff_matrices.rb +1 -1
  80. data/lib/cucumber/multiline_argument/doc_string.rb +1 -1
  81. data/lib/cucumber/platform.rb +3 -3
  82. data/lib/cucumber/rake/task.rb +16 -16
  83. data/lib/cucumber/rspec/disable_option_parser.rb +9 -8
  84. data/lib/cucumber/running_test_case.rb +2 -53
  85. data/lib/cucumber/runtime.rb +54 -58
  86. data/lib/cucumber/runtime/after_hooks.rb +8 -4
  87. data/lib/cucumber/runtime/before_hooks.rb +8 -4
  88. data/lib/cucumber/runtime/for_programming_languages.rb +4 -2
  89. data/lib/cucumber/runtime/step_hooks.rb +3 -2
  90. data/lib/cucumber/runtime/support_code.rb +13 -15
  91. data/lib/cucumber/runtime/user_interface.rb +6 -16
  92. data/lib/cucumber/step_definition_light.rb +4 -3
  93. data/lib/cucumber/step_definitions.rb +2 -2
  94. data/lib/cucumber/step_match.rb +12 -11
  95. data/lib/cucumber/step_match_search.rb +2 -1
  96. data/lib/cucumber/term/ansicolor.rb +9 -9
  97. data/lib/cucumber/version +1 -1
  98. metadata +224 -82
  99. data/lib/cucumber/events/gherkin_source_parsed.rb~ +0 -14
  100. data/lib/cucumber/formatter/ast_lookup.rb~ +0 -9
  101. data/lib/cucumber/formatter/cucumber.css +0 -286
  102. data/lib/cucumber/formatter/cucumber.sass +0 -247
  103. data/lib/cucumber/formatter/hook_query_visitor.rb +0 -42
  104. data/lib/cucumber/formatter/html_builder.rb +0 -121
  105. data/lib/cucumber/formatter/inline-js.js +0 -30
  106. data/lib/cucumber/formatter/jquery-min.js +0 -154
  107. data/lib/cucumber/formatter/json_pretty.rb +0 -11
  108. data/lib/cucumber/formatter/legacy_api/adapter.rb +0 -1028
  109. data/lib/cucumber/formatter/legacy_api/ast.rb +0 -394
  110. data/lib/cucumber/formatter/legacy_api/results.rb +0 -50
  111. data/lib/cucumber/formatter/legacy_api/runtime_facade.rb +0 -32
  112. data/lib/cucumber/step_argument.rb +0 -25
@@ -0,0 +1,146 @@
1
+ require 'net/http'
2
+ require 'tempfile'
3
+
4
+ module Cucumber
5
+ module Formatter
6
+ class HTTPIO
7
+ class << self
8
+ # Returns an IO that will write to a HTTP request's body
9
+ def open(url, https_verify_mode = nil)
10
+ @https_verify_mode = https_verify_mode
11
+ uri, method, headers = CurlOptionParser.parse(url)
12
+ IOHTTPBuffer.new(uri, method, headers, https_verify_mode)
13
+ end
14
+ end
15
+ end
16
+
17
+ class CurlOptionParser
18
+ def self.parse(options)
19
+ chunks = options.split(/\s/).compact
20
+ http_method = 'PUT'
21
+ url = chunks[0]
22
+ headers = ''
23
+
24
+ last_flag = nil
25
+ chunks.each do |chunk|
26
+ if ['-X', '--request'].include?(chunk)
27
+ last_flag = '-X'
28
+ next
29
+ end
30
+
31
+ if chunk == '-H'
32
+ last_flag = '-H'
33
+ next
34
+ end
35
+
36
+ if last_flag == '-X'
37
+ http_method = chunk
38
+ last_flag = nil
39
+ end
40
+
41
+ headers += chunk if last_flag == '-H'
42
+ end
43
+
44
+ [
45
+ url,
46
+ http_method,
47
+ make_headers(headers)
48
+ ]
49
+ end
50
+
51
+ def self.make_headers(headers)
52
+ hash_headers = {}
53
+ str_scanner = /("(?<key>[^":]+)\s*:\s*(?<value>[^":]+)")|('(?<key1>[^':]+)\s*:\s*(?<value1>[^':]+)')/
54
+
55
+ headers.scan(str_scanner) do |header|
56
+ header = header.compact!
57
+ hash_headers[header[0]] = header[1]&.strip
58
+ end
59
+
60
+ hash_headers
61
+ end
62
+ end
63
+
64
+ class IOHTTPBuffer
65
+ attr_reader :uri, :method, :headers
66
+
67
+ def initialize(uri, method, headers = {}, https_verify_mode = nil)
68
+ @uri = URI(uri)
69
+ @method = method
70
+ @headers = headers
71
+ @write_io = Tempfile.new('cucumber', encoding: 'UTF-8')
72
+ @https_verify_mode = https_verify_mode
73
+ end
74
+
75
+ def close
76
+ post_content(@uri, @method, @headers)
77
+ @write_io.close
78
+ end
79
+
80
+ def write(data)
81
+ @write_io.write(data)
82
+ end
83
+
84
+ def flush
85
+ @write_io.flush
86
+ end
87
+
88
+ def closed?
89
+ @write_io.closed?
90
+ end
91
+
92
+ private
93
+
94
+ def post_content(uri, method, headers, attempt = 10)
95
+ content = @write_io
96
+ http = build_client(uri, @https_verify_mode)
97
+
98
+ raise StandardError, "request to #{uri} failed (too many redirections)" if attempt <= 0
99
+ req = build_request(
100
+ uri,
101
+ method,
102
+ headers.merge(
103
+ 'Content-Length' => content.size.to_s
104
+ )
105
+ )
106
+
107
+ content.rewind
108
+ req.body_stream = content
109
+
110
+ begin
111
+ response = http.request(req)
112
+ rescue SystemCallError
113
+ # We may get the redirect response before pushing the file.
114
+ response = http.request(build_request(uri, method, headers))
115
+ end
116
+
117
+ case response
118
+ when Net::HTTPSuccess
119
+ response
120
+ when Net::HTTPRedirection
121
+ post_content(URI(response['Location']), method, headers, attempt - 1)
122
+ else
123
+ raise StandardError, "request to #{uri} failed with status #{response.code}"
124
+ end
125
+ end
126
+
127
+ def build_request(uri, method, headers)
128
+ method_class_name = "#{method[0].upcase}#{method[1..-1].downcase}"
129
+ req = Net::HTTP.const_get(method_class_name).new(uri)
130
+ headers.each do |header, value|
131
+ req[header] = value
132
+ end
133
+ req
134
+ end
135
+
136
+ def build_client(uri, https_verify_mode)
137
+ http = Net::HTTP.new(uri.hostname, uri.port)
138
+ if uri.scheme == 'https'
139
+ http.use_ssl = true
140
+ http.verify_mode = https_verify_mode if https_verify_mode
141
+ end
142
+ http
143
+ end
144
+ end
145
+ end
146
+ end
@@ -8,7 +8,7 @@ module Cucumber
8
8
  end
9
9
 
10
10
  def method_missing(message, *args)
11
- @receiver.send(message, *args) if @receiver.respond_to?(message)
11
+ @receiver.respond_to?(message) ? @receiver.send(message, *args) : super
12
12
  end
13
13
 
14
14
  def respond_to_missing?(name, include_private = false)
@@ -9,30 +9,18 @@ module Cucumber
9
9
  @pipe = pipe
10
10
  @buffer = StringIO.new
11
11
  @wrapped = true
12
+ @lock = Mutex.new
12
13
  end
13
14
 
14
15
  def write(str)
15
- lock.synchronize do
16
+ @lock.synchronize do
16
17
  @buffer << str if @wrapped
17
18
  return @pipe.write(str)
18
19
  end
19
20
  end
20
21
 
21
- # @deprecated use #buffer_string
22
- def buffer
23
- require 'cucumber/deprecate.rb'
24
- Cucumber.deprecate(
25
- 'Use Cucumber::Formatter::Interceptor::Pipe#buffer_string instead',
26
- 'Cucumber::Formatter::Interceptor::Pipe#buffer',
27
- '3.99'
28
- )
29
- lock.synchronize do
30
- return @buffer.string.lines
31
- end
32
- end
33
-
34
22
  def buffer_string
35
- lock.synchronize do
23
+ @lock.synchronize do
36
24
  return @buffer.string.dup
37
25
  end
38
26
  end
@@ -43,17 +31,15 @@ module Cucumber
43
31
  end
44
32
 
45
33
  def method_missing(method, *args, &blk)
46
- @pipe.send(method, *args, &blk)
34
+ @pipe.send(method, *args, &blk) || super
47
35
  end
48
36
 
49
- def respond_to?(method, include_private = false)
37
+ def respond_to_missing?(method, include_private = false)
50
38
  super || @pipe.respond_to?(method, include_private)
51
39
  end
52
40
 
53
41
  def self.validate_pipe(pipe)
54
- unless [:stdout, :stderr].include? pipe
55
- raise ArgumentError, '#wrap only accepts :stderr or :stdout'
56
- end
42
+ raise ArgumentError, '#wrap only accepts :stderr or :stdout' unless %i[stdout stderr].include? pipe
57
43
  end
58
44
 
59
45
  def self.unwrap!(pipe)
@@ -75,19 +61,13 @@ module Cucumber
75
61
 
76
62
  case pipe
77
63
  when :stderr
78
- $stderr = self.new($stderr)
64
+ $stderr = new($stderr)
79
65
  return $stderr
80
66
  when :stdout
81
- $stdout = self.new($stdout)
67
+ $stdout = new($stdout)
82
68
  return $stdout
83
69
  end
84
70
  end
85
-
86
- private
87
-
88
- def lock
89
- @lock ||= Mutex.new
90
- end
91
71
  end
92
72
  end
93
73
  end
@@ -1,32 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'cucumber/formatter/http_io'
4
+
3
5
  module Cucumber
4
6
  module Formatter
5
7
  module Io
6
8
  module_function
7
9
 
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'))
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.match(%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
12
18
  at_exit do
13
- unless file.closed?
14
- file.flush
15
- file.close
19
+ unless io.closed?
20
+ io.flush
21
+ io.close
16
22
  end
17
23
  end
18
- file
24
+ io
19
25
  end
20
26
 
21
27
  def ensure_file(path, name)
22
- raise "You *must* specify --out FILE for the #{name} formatter" unless String === path
28
+ raise "You *must* specify --out FILE for the #{name} formatter" unless String == path.class
23
29
  raise "I can't write #{name} to a directory - it has to be a file" if File.directory?(path)
24
- raise "I can't write #{name} to a file in the non-existing directory #{File.dirname(path)}" if !File.directory?(File.dirname(path))
30
+ raise "I can't write #{name} to a file in the non-existing directory #{File.dirname(path)}" unless File.directory?(File.dirname(path))
25
31
  ensure_io(path)
26
32
  end
27
33
 
28
34
  def ensure_dir(path, name)
29
- raise "You *must* specify --out DIR for the #{name} formatter" unless String === path
35
+ raise "You *must* specify --out DIR for the #{name} formatter" unless String == path.class
30
36
  raise "I can't write #{name} reports to a file - it has to be a directory" if File.file?(path)
31
37
  FileUtils.mkdir_p(path) unless File.directory?(path)
32
38
  File.absolute_path path
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'multi_json'
3
+ require '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
+ require 'cucumber/deprecate'
8
9
 
9
10
  module Cucumber
10
11
  module Formatter
@@ -13,7 +14,16 @@ module Cucumber
13
14
  include Io
14
15
 
15
16
  def initialize(config)
17
+ Cucumber::Deprecate::CliOption.deprecate(
18
+ config.error_stream,
19
+ '--format=json',
20
+ "Please use --format=message and stand-alone json-formatter.\n" \
21
+ 'json-formatter homepage: https://github.com/cucumber/cucumber/tree/master/json-formatter#cucumber-json-formatter',
22
+ '5.0.0'
23
+ )
24
+
16
25
  @io = ensure_io(config.out_stream)
26
+ @ast_lookup = AstLookup.new(config)
17
27
  @feature_hashes = []
18
28
  @step_or_hook_hash = {}
19
29
  config.on_event :test_case_started, &method(:on_test_case_started)
@@ -25,16 +35,18 @@ module Cucumber
25
35
 
26
36
  def on_test_case_started(event)
27
37
  test_case = event.test_case
28
- builder = Builder.new(test_case)
29
- unless same_feature_as_previous_test_case?(test_case.feature)
38
+ builder = Builder.new(test_case, @ast_lookup)
39
+ unless same_feature_as_previous_test_case?(test_case)
30
40
  @feature_hash = builder.feature_hash
31
41
  @feature_hashes << @feature_hash
32
42
  end
33
43
  @test_case_hash = builder.test_case_hash
34
44
  if builder.background?
45
+ @in_background = true
35
46
  feature_elements << builder.background_hash
36
47
  @element_hash = builder.background_hash
37
48
  else
49
+ @in_background = false
38
50
  feature_elements << @test_case_hash
39
51
  @element_hash = @test_case_hash
40
52
  end
@@ -44,17 +56,17 @@ module Cucumber
44
56
  def on_test_step_started(event)
45
57
  test_step = event.test_step
46
58
  return if internal_hook?(test_step)
47
- hook_query = HookQueryVisitor.new(test_step)
48
- if hook_query.hook?
59
+ if test_step.hook?
49
60
  @step_or_hook_hash = {}
50
- hooks_of_type(hook_query) << @step_or_hook_hash
61
+ hooks_of_type(test_step) << @step_or_hook_hash
51
62
  return
52
63
  end
53
64
  if first_step_after_background?(test_step)
65
+ @in_background = false
54
66
  feature_elements << @test_case_hash
55
67
  @element_hash = @test_case_hash
56
68
  end
57
- @step_or_hook_hash = create_step_hash(test_step.source.last)
69
+ @step_or_hook_hash = create_step_hash(test_step)
58
70
  steps << @step_or_hook_hash
59
71
  @step_hash = @step_or_hook_hash
60
72
  end
@@ -74,44 +86,42 @@ module Cucumber
74
86
  end
75
87
 
76
88
  def on_test_run_finished(_event)
77
- @io.write(MultiJson.dump(@feature_hashes, pretty: true))
78
- end
79
-
80
- def puts(message)
81
- test_step_output << message
89
+ @io.write(JSON.generate(@feature_hashes, pretty: true))
82
90
  end
83
91
 
84
- def embed(src, mime_type, _label)
92
+ def attach(src, mime_type)
93
+ if mime_type == 'text/x.cucumber.log+plain'
94
+ test_step_output << src
95
+ return
96
+ end
85
97
  if File.file?(src)
86
98
  content = File.open(src, 'rb', &:read)
87
99
  data = encode64(content)
100
+ elsif mime_type =~ /;base64$/
101
+ mime_type = mime_type[0..-8]
102
+ data = src
88
103
  else
89
- if mime_type =~ /;base64$/
90
- mime_type = mime_type[0..-8]
91
- data = src
92
- else
93
- data = encode64(src)
94
- end
104
+ data = encode64(src)
95
105
  end
96
106
  test_step_embeddings << { mime_type: mime_type, data: data }
97
107
  end
98
108
 
99
109
  private
100
110
 
101
- def same_feature_as_previous_test_case?(feature)
102
- current_feature[:uri] == feature.file && current_feature[:line] == feature.location.line
111
+ def same_feature_as_previous_test_case?(test_case)
112
+ current_feature[:uri] == test_case.location.file
103
113
  end
104
114
 
105
115
  def first_step_after_background?(test_step)
106
- test_step.source[1].to_s != @element_hash[:name]
116
+ @in_background && test_step.location.lines.max >= @test_case_hash[:line]
107
117
  end
108
118
 
109
119
  def internal_hook?(test_step)
110
- test_step.source.last.location.file.include?('lib/cucumber/')
120
+ test_step.location.file.include?('lib/cucumber/')
111
121
  end
112
122
 
113
123
  def current_feature
114
- @feature_hash ||= {}
124
+ @feature_hash ||= {} # rubocop:disable Naming/MemoizedInstanceVariableName
115
125
  end
116
126
 
117
127
  def feature_elements
@@ -122,16 +132,16 @@ module Cucumber
122
132
  @element_hash[:steps] ||= []
123
133
  end
124
134
 
125
- def hooks_of_type(hook_query)
126
- case hook_query.type
127
- when :before
128
- return before_hooks
129
- when :after
130
- return after_hooks
131
- when :after_step
132
- return after_step_hooks
135
+ def hooks_of_type(hook_step)
136
+ case hook_step.text
137
+ when 'Before hook'
138
+ before_hooks
139
+ when 'After hook'
140
+ after_hooks
141
+ when 'AfterStep hook'
142
+ after_step_hooks
133
143
  else
134
- fail 'Unknown hook type ' + hook_query.type.to_s
144
+ raise 'Unknown hook type ' + hook_step.to_s
135
145
  end
136
146
  end
137
147
 
@@ -159,20 +169,20 @@ module Cucumber
159
169
  @step_or_hook_hash[:embeddings] ||= []
160
170
  end
161
171
 
162
- def create_step_hash(step_source)
172
+ def create_step_hash(test_step)
173
+ step_source = @ast_lookup.step_source(test_step).step
163
174
  step_hash = {
164
175
  keyword: step_source.keyword,
165
- name: step_source.to_s,
166
- line: step_source.original_location.line
176
+ name: test_step.text,
177
+ line: test_step.location.lines.min
167
178
  }
168
- step_hash[:comments] = Formatter.create_comments_array(step_source.comments) unless step_source.comments.empty?
169
- step_hash[:doc_string] = create_doc_string_hash(step_source.multiline_arg) if step_source.multiline_arg.doc_string?
170
- step_hash[:rows] = create_data_table_value(step_source.multiline_arg) if step_source.multiline_arg.data_table?
179
+ step_hash[:doc_string] = create_doc_string_hash(step_source.doc_string) unless step_source.doc_string.nil?
180
+ step_hash[:rows] = create_data_table_value(step_source.data_table) unless step_source.data_table.nil?
171
181
  step_hash
172
182
  end
173
183
 
174
184
  def create_doc_string_hash(doc_string)
175
- content_type = doc_string.content_type ? doc_string.content_type : ''
185
+ content_type = doc_string.media_type || ''
176
186
  {
177
187
  value: doc_string.content,
178
188
  content_type: content_type,
@@ -181,14 +191,15 @@ module Cucumber
181
191
  end
182
192
 
183
193
  def create_data_table_value(data_table)
184
- data_table.raw.map do |row|
185
- { cells: row }
194
+ data_table.rows.map do |row|
195
+ { cells: row.cells.map(&:value) }
186
196
  end
187
197
  end
188
198
 
189
199
  def add_match_and_result(test_step, result)
190
200
  @step_or_hook_hash[:match] = create_match_hash(test_step, result)
191
201
  @step_or_hook_hash[:result] = create_result_hash(result)
202
+ result.embeddings.each { |e| embed(e['src'], e['mime_type'], e['label']) } if result.respond_to?(:embeddings)
192
203
  end
193
204
 
194
205
  def add_failed_around_hook(result)
@@ -226,113 +237,94 @@ module Cucumber
226
237
  class Builder
227
238
  attr_reader :feature_hash, :background_hash, :test_case_hash
228
239
 
229
- def initialize(test_case)
240
+ def initialize(test_case, ast_lookup)
230
241
  @background_hash = nil
231
- test_case.describe_source_to(self)
232
- test_case.feature.background.describe_to(self)
242
+ uri = test_case.location.file
243
+ feature = ast_lookup.gherkin_document(uri).feature
244
+ feature(feature, uri)
245
+ background(feature.children.first.background) unless feature.children.first.background.nil?
246
+ scenario(ast_lookup.scenario_source(test_case), test_case)
233
247
  end
234
248
 
235
249
  def background?
236
250
  @background_hash != nil
237
251
  end
238
252
 
239
- def feature(feature)
253
+ def feature(feature, uri)
240
254
  @feature_hash = {
241
- uri: feature.file,
242
- id: create_id(feature),
255
+ id: create_id(feature.name),
256
+ uri: uri,
243
257
  keyword: feature.keyword,
244
- name: feature.to_s,
245
- description: feature.description,
258
+ name: feature.name,
259
+ description: value_or_empty_string(feature.description),
246
260
  line: feature.location.line
247
261
  }
248
- unless feature.tags.empty?
249
- @feature_hash[:tags] = create_tags_array(feature.tags)
250
- @test_case_hash[:tags] = if @test_case_hash[:tags]
251
- @feature_hash[:tags] + @test_case_hash[:tags]
252
- else
253
- @feature_hash[:tags]
254
- end
255
- end
256
- @feature_hash[:comments] = Formatter.create_comments_array(feature.comments) unless feature.comments.empty?
257
- @test_case_hash[:id].insert(0, @feature_hash[:id] + ';')
262
+ return if feature.tags.empty?
263
+ @feature_hash[:tags] = create_tags_array_from_hash_array(feature.tags)
258
264
  end
259
265
 
260
266
  def background(background)
261
267
  @background_hash = {
262
268
  keyword: background.keyword,
263
- name: background.to_s,
264
- description: background.description,
269
+ name: background.name,
270
+ description: value_or_empty_string(background.description),
265
271
  line: background.location.line,
266
272
  type: 'background'
267
273
  }
268
- @background_hash[:comments] = Formatter.create_comments_array(background.comments) unless background.comments.empty?
269
274
  end
270
275
 
271
- def scenario(scenario)
276
+ def scenario(scenario_source, test_case)
277
+ scenario = scenario_source.type == :Scenario ? scenario_source.scenario : scenario_source.scenario_outline
272
278
  @test_case_hash = {
273
- id: create_id(scenario),
279
+ id: "#{@feature_hash[:id]};#{create_id_from_scenario_source(scenario_source)}",
274
280
  keyword: scenario.keyword,
275
- name: scenario.to_s,
276
- description: scenario.description,
277
- line: scenario.location.line,
281
+ name: test_case.name,
282
+ description: value_or_empty_string(scenario.description),
283
+ line: test_case.location.lines.max,
278
284
  type: 'scenario'
279
285
  }
280
- @test_case_hash[:tags] = create_tags_array(scenario.tags) unless scenario.tags.empty?
281
- @test_case_hash[:comments] = Formatter.create_comments_array(scenario.comments) unless scenario.comments.empty?
286
+ @test_case_hash[:tags] = create_tags_array_from_tags_array(test_case.tags) unless test_case.tags.empty?
282
287
  end
283
288
 
284
- def scenario_outline(scenario)
285
- @test_case_hash = {
286
- id: create_id(scenario) + ';' + @example_id,
287
- keyword: scenario.keyword,
288
- name: scenario.to_s,
289
- description: scenario.description,
290
- line: @row.location.line,
291
- type: 'scenario'
292
- }
293
- tags = []
294
- tags += create_tags_array(scenario.tags) unless scenario.tags.empty?
295
- tags += @examples_table_tags if @examples_table_tags
296
- @test_case_hash[:tags] = tags unless tags.empty?
297
- comments = []
298
- comments += Formatter.create_comments_array(scenario.comments) unless scenario.comments.empty?
299
- comments += @examples_table_comments if @examples_table_comments
300
- comments += @row_comments if @row_comments
301
- @test_case_hash[:comments] = comments unless comments.empty?
302
- end
289
+ private
303
290
 
304
- def examples_table(examples_table)
305
- # the json file have traditionally used the header row as row 1,
306
- # wheras cucumber-ruby-core used the first example row as row 1.
307
- @example_id = create_id(examples_table) + ";#{@row.number + 1}"
291
+ def value_or_empty_string(value)
292
+ value.nil? ? '' : value
293
+ end
308
294
 
309
- @examples_table_tags = create_tags_array(examples_table.tags) unless examples_table.tags.empty?
310
- @examples_table_comments = Formatter.create_comments_array(examples_table.comments) unless examples_table.comments.empty?
295
+ def create_id(name)
296
+ name.downcase.tr(' ', '-')
311
297
  end
312
298
 
313
- def examples_table_row(row)
314
- @row = row
315
- @row_comments = Formatter.create_comments_array(row.comments) unless row.comments.empty?
299
+ def create_id_from_scenario_source(scenario_source)
300
+ if scenario_source.type == :Scenario
301
+ create_id(scenario_source.scenario.name)
302
+ else
303
+ scenario_outline_name = scenario_source.scenario_outline.name
304
+ examples_name = scenario_source.examples.name
305
+ row_number = calculate_row_number(scenario_source)
306
+ "#{create_id(scenario_outline_name)};#{create_id(examples_name)};#{row_number}"
307
+ end
316
308
  end
317
309
 
318
- private
310
+ def calculate_row_number(scenario_source)
311
+ scenario_source.examples.table_body.each_with_index do |row, index|
312
+ return index + 2 if row == scenario_source.row
313
+ end
314
+ end
319
315
 
320
- def create_id(element)
321
- element.to_s.downcase.tr(' ', '-')
316
+ def create_tags_array_from_hash_array(tags)
317
+ tags_array = []
318
+ tags.each { |tag| tags_array << { name: tag.name, line: tag.location.line } }
319
+ tags_array
322
320
  end
323
321
 
324
- def create_tags_array(tags)
322
+ def create_tags_array_from_tags_array(tags)
325
323
  tags_array = []
326
324
  tags.each { |tag| tags_array << { name: tag.name, line: tag.location.line } }
327
325
  tags_array
328
326
  end
329
327
  end
330
328
  end
331
-
332
- def self.create_comments_array(comments)
333
- comments_array = []
334
- comments.each { |comment| comments_array << { value: comment.to_s.strip, line: comment.location.line } }
335
- comments_array
336
- end
337
329
  end
338
330
  end