cucumber 3.0.2 → 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 (145) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +216 -17
  3. data/CONTRIBUTING.md +4 -21
  4. data/README.md +8 -10
  5. data/bin/cucumber +1 -1
  6. data/lib/autotest/cucumber.rb +1 -0
  7. data/lib/autotest/cucumber_mixin.rb +35 -39
  8. data/lib/autotest/cucumber_rails.rb +1 -0
  9. data/lib/autotest/cucumber_rails_rspec.rb +1 -0
  10. data/lib/autotest/cucumber_rails_rspec2.rb +1 -0
  11. data/lib/autotest/cucumber_rspec.rb +1 -0
  12. data/lib/autotest/cucumber_rspec2.rb +1 -0
  13. data/lib/autotest/discover.rb +1 -0
  14. data/lib/cucumber.rb +2 -1
  15. data/lib/cucumber/cli/configuration.rb +6 -5
  16. data/lib/cucumber/cli/main.rb +14 -14
  17. data/lib/cucumber/cli/options.rb +113 -116
  18. data/lib/cucumber/cli/profile_loader.rb +50 -29
  19. data/lib/cucumber/cli/rerun_file.rb +1 -0
  20. data/lib/cucumber/configuration.rb +38 -29
  21. data/lib/cucumber/constantize.rb +8 -10
  22. data/lib/cucumber/core_ext/string.rb +1 -0
  23. data/lib/cucumber/deprecate.rb +32 -8
  24. data/lib/cucumber/encoding.rb +2 -1
  25. data/lib/cucumber/errors.rb +6 -7
  26. data/lib/cucumber/events.rb +14 -7
  27. data/lib/cucumber/events/envelope.rb +9 -0
  28. data/lib/cucumber/events/gherkin_source_parsed.rb +11 -0
  29. data/lib/cucumber/events/gherkin_source_read.rb +1 -4
  30. data/lib/cucumber/events/hook_test_step_created.rb +13 -0
  31. data/lib/cucumber/events/step_activated.rb +6 -6
  32. data/lib/cucumber/events/step_definition_registered.rb +4 -8
  33. data/lib/cucumber/events/test_case_created.rb +13 -0
  34. data/lib/cucumber/events/test_case_finished.rb +0 -4
  35. data/lib/cucumber/events/test_case_ready.rb +12 -0
  36. data/lib/cucumber/events/test_case_started.rb +0 -4
  37. data/lib/cucumber/events/test_run_finished.rb +2 -3
  38. data/lib/cucumber/events/test_run_started.rb +2 -4
  39. data/lib/cucumber/events/test_step_created.rb +13 -0
  40. data/lib/cucumber/events/test_step_finished.rb +0 -4
  41. data/lib/cucumber/events/test_step_started.rb +1 -5
  42. data/lib/cucumber/events/undefined_parameter_type.rb +10 -0
  43. data/lib/cucumber/file_specs.rb +7 -6
  44. data/lib/cucumber/filters.rb +2 -0
  45. data/lib/cucumber/filters/activate_steps.rb +6 -4
  46. data/lib/cucumber/filters/apply_after_hooks.rb +1 -0
  47. data/lib/cucumber/filters/apply_after_step_hooks.rb +1 -0
  48. data/lib/cucumber/filters/apply_around_hooks.rb +1 -0
  49. data/lib/cucumber/filters/apply_before_hooks.rb +1 -0
  50. data/lib/cucumber/filters/broadcast_test_case_ready_event.rb +12 -0
  51. data/lib/cucumber/filters/broadcast_test_run_started_event.rb +2 -1
  52. data/lib/cucumber/filters/gated_receiver.rb +1 -2
  53. data/lib/cucumber/filters/prepare_world.rb +6 -13
  54. data/lib/cucumber/filters/quit.rb +3 -6
  55. data/lib/cucumber/filters/randomizer.rb +6 -7
  56. data/lib/cucumber/filters/retry.rb +2 -2
  57. data/lib/cucumber/filters/tag_limits.rb +2 -2
  58. data/lib/cucumber/filters/tag_limits/test_case_index.rb +1 -2
  59. data/lib/cucumber/filters/tag_limits/verifier.rb +3 -6
  60. data/lib/cucumber/formatter/ansicolor.rb +33 -37
  61. data/lib/cucumber/formatter/ast_lookup.rb +165 -0
  62. data/lib/cucumber/formatter/backtrace_filter.rb +10 -10
  63. data/lib/cucumber/formatter/console.rb +65 -74
  64. data/lib/cucumber/formatter/console_counts.rb +4 -9
  65. data/lib/cucumber/formatter/console_issues.rb +9 -6
  66. data/lib/cucumber/formatter/duration.rb +2 -1
  67. data/lib/cucumber/formatter/duration_extractor.rb +4 -2
  68. data/lib/cucumber/formatter/errors.rb +6 -0
  69. data/lib/cucumber/formatter/fail_fast.rb +9 -6
  70. data/lib/cucumber/formatter/fanout.rb +3 -3
  71. data/lib/cucumber/formatter/html.rb +11 -602
  72. data/lib/cucumber/formatter/http_io.rb +146 -0
  73. data/lib/cucumber/formatter/ignore_missing_messages.rb +2 -3
  74. data/lib/cucumber/formatter/interceptor.rb +11 -18
  75. data/lib/cucumber/formatter/io.rb +18 -11
  76. data/lib/cucumber/formatter/json.rb +102 -109
  77. data/lib/cucumber/formatter/junit.rb +73 -68
  78. data/lib/cucumber/formatter/message.rb +22 -0
  79. data/lib/cucumber/formatter/message_builder.rb +255 -0
  80. data/lib/cucumber/formatter/pretty.rb +360 -153
  81. data/lib/cucumber/formatter/progress.rb +31 -32
  82. data/lib/cucumber/formatter/query/hook_by_test_step.rb +31 -0
  83. data/lib/cucumber/formatter/query/pickle_by_test.rb +26 -0
  84. data/lib/cucumber/formatter/query/pickle_step_by_test_step.rb +26 -0
  85. data/lib/cucumber/formatter/query/step_definitions_by_test_step.rb +40 -0
  86. data/lib/cucumber/formatter/query/test_case_started_by_test_case.rb +40 -0
  87. data/lib/cucumber/formatter/rerun.rb +23 -4
  88. data/lib/cucumber/formatter/stepdefs.rb +2 -2
  89. data/lib/cucumber/formatter/steps.rb +4 -5
  90. data/lib/cucumber/formatter/summary.rb +17 -9
  91. data/lib/cucumber/formatter/unicode.rb +16 -18
  92. data/lib/cucumber/formatter/usage.rb +30 -26
  93. data/lib/cucumber/gherkin/data_table_parser.rb +18 -6
  94. data/lib/cucumber/gherkin/formatter/ansi_escapes.rb +83 -86
  95. data/lib/cucumber/gherkin/formatter/escaping.rb +13 -12
  96. data/lib/cucumber/gherkin/i18n.rb +1 -0
  97. data/lib/cucumber/gherkin/steps_parser.rb +18 -8
  98. data/lib/cucumber/glue/dsl.rb +2 -1
  99. data/lib/cucumber/glue/hook.rb +35 -11
  100. data/lib/cucumber/glue/invoke_in_world.rb +15 -20
  101. data/lib/cucumber/glue/proto_world.rb +47 -39
  102. data/lib/cucumber/glue/registry_and_more.rb +54 -23
  103. data/lib/cucumber/glue/snippet.rb +24 -27
  104. data/lib/cucumber/glue/step_definition.rb +51 -28
  105. data/lib/cucumber/glue/world_factory.rb +1 -3
  106. data/lib/cucumber/hooks.rb +24 -14
  107. data/lib/cucumber/load_path.rb +1 -0
  108. data/lib/cucumber/multiline_argument.rb +6 -8
  109. data/lib/cucumber/multiline_argument/data_table.rb +106 -73
  110. data/lib/cucumber/multiline_argument/data_table/diff_matrices.rb +8 -11
  111. data/lib/cucumber/multiline_argument/doc_string.rb +2 -1
  112. data/lib/cucumber/platform.rb +4 -3
  113. data/lib/cucumber/project_initializer.rb +1 -1
  114. data/lib/cucumber/rake/task.rb +21 -18
  115. data/lib/cucumber/rspec/disable_option_parser.rb +10 -8
  116. data/lib/cucumber/rspec/doubles.rb +1 -0
  117. data/lib/cucumber/running_test_case.rb +4 -54
  118. data/lib/cucumber/runtime.rb +57 -61
  119. data/lib/cucumber/runtime/after_hooks.rb +9 -4
  120. data/lib/cucumber/runtime/before_hooks.rb +9 -4
  121. data/lib/cucumber/runtime/for_programming_languages.rb +12 -9
  122. data/lib/cucumber/runtime/step_hooks.rb +5 -2
  123. data/lib/cucumber/runtime/support_code.rb +16 -22
  124. data/lib/cucumber/runtime/user_interface.rb +8 -19
  125. data/lib/cucumber/step_definition_light.rb +6 -4
  126. data/lib/cucumber/step_definitions.rb +3 -2
  127. data/lib/cucumber/step_match.rb +20 -18
  128. data/lib/cucumber/step_match_search.rb +9 -9
  129. data/lib/cucumber/term/ansicolor.rb +39 -39
  130. data/lib/cucumber/unit.rb +1 -0
  131. data/lib/cucumber/version +1 -1
  132. data/lib/simplecov_setup.rb +1 -0
  133. metadata +214 -127
  134. data/lib/cucumber/formatter/cucumber.css +0 -286
  135. data/lib/cucumber/formatter/cucumber.sass +0 -247
  136. data/lib/cucumber/formatter/hook_query_visitor.rb +0 -41
  137. data/lib/cucumber/formatter/html_builder.rb +0 -120
  138. data/lib/cucumber/formatter/inline-js.js +0 -30
  139. data/lib/cucumber/formatter/jquery-min.js +0 -154
  140. data/lib/cucumber/formatter/json_pretty.rb +0 -10
  141. data/lib/cucumber/formatter/legacy_api/adapter.rb +0 -1028
  142. data/lib/cucumber/formatter/legacy_api/ast.rb +0 -394
  143. data/lib/cucumber/formatter/legacy_api/results.rb +0 -50
  144. data/lib/cucumber/formatter/legacy_api/runtime_facade.rb +0 -32
  145. data/lib/cucumber/step_argument.rb +0 -24
@@ -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
@@ -1,20 +1,19 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Cucumber
3
4
  module Formatter
4
-
5
5
  class IgnoreMissingMessages < BasicObject
6
6
  def initialize(receiver)
7
7
  @receiver = receiver
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)
15
15
  @receiver.respond_to?(name, include_private)
16
16
  end
17
17
  end
18
-
19
18
  end
20
19
  end
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- require 'thread'
3
2
 
4
3
  module Cucumber
5
4
  module Formatter
@@ -8,20 +7,21 @@ module Cucumber
8
7
  attr_reader :pipe
9
8
  def initialize(pipe)
10
9
  @pipe = pipe
11
- @buffer = []
10
+ @buffer = StringIO.new
12
11
  @wrapped = true
12
+ @lock = Mutex.new
13
13
  end
14
14
 
15
15
  def write(str)
16
- lock.synchronize do
16
+ @lock.synchronize do
17
17
  @buffer << str if @wrapped
18
18
  return @pipe.write(str)
19
19
  end
20
20
  end
21
21
 
22
- def buffer
23
- lock.synchronize do
24
- return @buffer.dup
22
+ def buffer_string
23
+ @lock.synchronize do
24
+ return @buffer.string.dup
25
25
  end
26
26
  end
27
27
 
@@ -31,17 +31,15 @@ module Cucumber
31
31
  end
32
32
 
33
33
  def method_missing(method, *args, &blk)
34
- @pipe.send(method, *args, &blk)
34
+ @pipe.send(method, *args, &blk) || super
35
35
  end
36
36
 
37
- def respond_to?(method, include_private=false)
37
+ def respond_to_missing?(method, include_private = false)
38
38
  super || @pipe.respond_to?(method, include_private)
39
39
  end
40
40
 
41
41
  def self.validate_pipe(pipe)
42
- unless [:stdout, :stderr].include? pipe
43
- raise ArgumentError, '#wrap only accepts :stderr or :stdout'
44
- end
42
+ raise ArgumentError, '#wrap only accepts :stderr or :stdout' unless %i[stdout stderr].include? pipe
45
43
  end
46
44
 
47
45
  def self.unwrap!(pipe)
@@ -63,18 +61,13 @@ module Cucumber
63
61
 
64
62
  case pipe
65
63
  when :stderr
66
- $stderr = self.new($stderr)
64
+ $stderr = new($stderr)
67
65
  return $stderr
68
66
  when :stdout
69
- $stdout = self.new($stdout)
67
+ $stdout = new($stdout)
70
68
  return $stdout
71
69
  end
72
70
  end
73
-
74
- private
75
- def lock
76
- @lock||=Mutex.new
77
- end
78
71
  end
79
72
  end
80
73
  end
@@ -1,31 +1,38 @@
1
1
  # frozen_string_literal: true
2
+
3
+ require 'cucumber/formatter/http_io'
4
+
2
5
  module Cucumber
3
6
  module Formatter
4
7
  module Io
5
8
  module_function
6
9
 
7
- def ensure_io(path_or_io)
8
- return nil if path_or_io.nil?
9
- return path_or_io if path_or_io.respond_to?(:write)
10
- 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
11
18
  at_exit do
12
- unless file.closed?
13
- file.flush
14
- file.close
19
+ unless io.closed?
20
+ io.flush
21
+ io.close
15
22
  end
16
23
  end
17
- file
24
+ io
18
25
  end
19
26
 
20
27
  def ensure_file(path, name)
21
- 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
22
29
  raise "I can't write #{name} to a directory - it has to be a file" if File.directory?(path)
23
- 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))
24
31
  ensure_io(path)
25
32
  end
26
33
 
27
34
  def ensure_dir(path, name)
28
- 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
29
36
  raise "I can't write #{name} reports to a file - it has to be a directory" if File.file?(path)
30
37
  FileUtils.mkdir_p(path) unless File.directory?(path)
31
38
  File.absolute_path path
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
- require 'multi_json'
2
+
3
+ require 'json'
3
4
  require 'base64'
4
5
  require 'cucumber/formatter/backtrace_filter'
5
6
  require 'cucumber/formatter/io'
6
- require 'cucumber/formatter/hook_query_visitor'
7
+ require 'cucumber/formatter/ast_lookup'
8
+ require 'cucumber/deprecate'
7
9
 
8
10
  module Cucumber
9
11
  module Formatter
@@ -12,7 +14,16 @@ module Cucumber
12
14
  include Io
13
15
 
14
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
+
15
25
  @io = ensure_io(config.out_stream)
26
+ @ast_lookup = AstLookup.new(config)
16
27
  @feature_hashes = []
17
28
  @step_or_hook_hash = {}
18
29
  config.on_event :test_case_started, &method(:on_test_case_started)
@@ -24,16 +35,18 @@ module Cucumber
24
35
 
25
36
  def on_test_case_started(event)
26
37
  test_case = event.test_case
27
- builder = Builder.new(test_case)
28
- 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)
29
40
  @feature_hash = builder.feature_hash
30
41
  @feature_hashes << @feature_hash
31
42
  end
32
43
  @test_case_hash = builder.test_case_hash
33
44
  if builder.background?
45
+ @in_background = true
34
46
  feature_elements << builder.background_hash
35
47
  @element_hash = builder.background_hash
36
48
  else
49
+ @in_background = false
37
50
  feature_elements << @test_case_hash
38
51
  @element_hash = @test_case_hash
39
52
  end
@@ -43,17 +56,17 @@ module Cucumber
43
56
  def on_test_step_started(event)
44
57
  test_step = event.test_step
45
58
  return if internal_hook?(test_step)
46
- hook_query = HookQueryVisitor.new(test_step)
47
- if hook_query.hook?
59
+ if test_step.hook?
48
60
  @step_or_hook_hash = {}
49
- hooks_of_type(hook_query) << @step_or_hook_hash
61
+ hooks_of_type(test_step) << @step_or_hook_hash
50
62
  return
51
63
  end
52
64
  if first_step_after_background?(test_step)
65
+ @in_background = false
53
66
  feature_elements << @test_case_hash
54
67
  @element_hash = @test_case_hash
55
68
  end
56
- @step_or_hook_hash = create_step_hash(test_step.source.last)
69
+ @step_or_hook_hash = create_step_hash(test_step)
57
70
  steps << @step_or_hook_hash
58
71
  @step_hash = @step_or_hook_hash
59
72
  end
@@ -73,44 +86,42 @@ module Cucumber
73
86
  end
74
87
 
75
88
  def on_test_run_finished(_event)
76
- @io.write(MultiJson.dump(@feature_hashes, pretty: true))
77
- end
78
-
79
- def puts(message)
80
- test_step_output << message
89
+ @io.write(JSON.generate(@feature_hashes, pretty: true))
81
90
  end
82
91
 
83
- 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
84
97
  if File.file?(src)
85
98
  content = File.open(src, 'rb', &:read)
86
99
  data = encode64(content)
100
+ elsif mime_type =~ /;base64$/
101
+ mime_type = mime_type[0..-8]
102
+ data = src
87
103
  else
88
- if mime_type =~ /;base64$/
89
- mime_type = mime_type[0..-8]
90
- data = src
91
- else
92
- data = encode64(src)
93
- end
104
+ data = encode64(src)
94
105
  end
95
106
  test_step_embeddings << { mime_type: mime_type, data: data }
96
107
  end
97
108
 
98
109
  private
99
110
 
100
- def same_feature_as_previous_test_case?(feature)
101
- 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
102
113
  end
103
114
 
104
115
  def first_step_after_background?(test_step)
105
- test_step.source[1].to_s != @element_hash[:name]
116
+ @in_background && test_step.location.lines.max >= @test_case_hash[:line]
106
117
  end
107
118
 
108
119
  def internal_hook?(test_step)
109
- test_step.source.last.location.file.include?('lib/cucumber/')
120
+ test_step.location.file.include?('lib/cucumber/')
110
121
  end
111
122
 
112
123
  def current_feature
113
- @feature_hash ||= {}
124
+ @feature_hash ||= {} # rubocop:disable Naming/MemoizedInstanceVariableName
114
125
  end
115
126
 
116
127
  def feature_elements
@@ -121,16 +132,16 @@ module Cucumber
121
132
  @element_hash[:steps] ||= []
122
133
  end
123
134
 
124
- def hooks_of_type(hook_query)
125
- case hook_query.type
126
- when :before
127
- return before_hooks
128
- when :after
129
- return after_hooks
130
- when :after_step
131
- 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
132
143
  else
133
- fail 'Unknown hook type ' + hook_query.type.to_s
144
+ raise 'Unknown hook type ' + hook_step.to_s
134
145
  end
135
146
  end
136
147
 
@@ -158,20 +169,20 @@ module Cucumber
158
169
  @step_or_hook_hash[:embeddings] ||= []
159
170
  end
160
171
 
161
- def create_step_hash(step_source)
172
+ def create_step_hash(test_step)
173
+ step_source = @ast_lookup.step_source(test_step).step
162
174
  step_hash = {
163
175
  keyword: step_source.keyword,
164
- name: step_source.to_s,
165
- line: step_source.location.line
176
+ name: test_step.text,
177
+ line: test_step.location.lines.min
166
178
  }
167
- step_hash[:comments] = Formatter.create_comments_array(step_source.comments) unless step_source.comments.empty?
168
- step_hash[:doc_string] = create_doc_string_hash(step_source.multiline_arg) if step_source.multiline_arg.doc_string?
169
- 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?
170
181
  step_hash
171
182
  end
172
183
 
173
184
  def create_doc_string_hash(doc_string)
174
- content_type = doc_string.content_type ? doc_string.content_type : ''
185
+ content_type = doc_string.media_type || ''
175
186
  {
176
187
  value: doc_string.content,
177
188
  content_type: content_type,
@@ -180,14 +191,15 @@ module Cucumber
180
191
  end
181
192
 
182
193
  def create_data_table_value(data_table)
183
- data_table.raw.map do |row|
184
- { cells: row }
194
+ data_table.rows.map do |row|
195
+ { cells: row.cells.map(&:value) }
185
196
  end
186
197
  end
187
198
 
188
199
  def add_match_and_result(test_step, result)
189
200
  @step_or_hook_hash[:match] = create_match_hash(test_step, result)
190
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)
191
203
  end
192
204
 
193
205
  def add_failed_around_hook(result)
@@ -225,113 +237,94 @@ module Cucumber
225
237
  class Builder
226
238
  attr_reader :feature_hash, :background_hash, :test_case_hash
227
239
 
228
- def initialize(test_case)
240
+ def initialize(test_case, ast_lookup)
229
241
  @background_hash = nil
230
- test_case.describe_source_to(self)
231
- 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)
232
247
  end
233
248
 
234
249
  def background?
235
250
  @background_hash != nil
236
251
  end
237
252
 
238
- def feature(feature)
253
+ def feature(feature, uri)
239
254
  @feature_hash = {
240
- uri: feature.file,
241
- id: create_id(feature),
255
+ id: create_id(feature.name),
256
+ uri: uri,
242
257
  keyword: feature.keyword,
243
- name: feature.to_s,
244
- description: feature.description,
258
+ name: feature.name,
259
+ description: value_or_empty_string(feature.description),
245
260
  line: feature.location.line
246
261
  }
247
- unless feature.tags.empty?
248
- @feature_hash[:tags] = create_tags_array(feature.tags)
249
- @test_case_hash[:tags] = if @test_case_hash[:tags]
250
- @feature_hash[:tags] + @test_case_hash[:tags]
251
- else
252
- @feature_hash[:tags]
253
- end
254
- end
255
- @feature_hash[:comments] = Formatter.create_comments_array(feature.comments) unless feature.comments.empty?
256
- @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)
257
264
  end
258
265
 
259
266
  def background(background)
260
267
  @background_hash = {
261
268
  keyword: background.keyword,
262
- name: background.to_s,
263
- description: background.description,
269
+ name: background.name,
270
+ description: value_or_empty_string(background.description),
264
271
  line: background.location.line,
265
272
  type: 'background'
266
273
  }
267
- @background_hash[:comments] = Formatter.create_comments_array(background.comments) unless background.comments.empty?
268
274
  end
269
275
 
270
- def scenario(scenario)
276
+ def scenario(scenario_source, test_case)
277
+ scenario = scenario_source.type == :Scenario ? scenario_source.scenario : scenario_source.scenario_outline
271
278
  @test_case_hash = {
272
- id: create_id(scenario),
279
+ id: "#{@feature_hash[:id]};#{create_id_from_scenario_source(scenario_source)}",
273
280
  keyword: scenario.keyword,
274
- name: scenario.to_s,
275
- description: scenario.description,
276
- line: scenario.location.line,
281
+ name: test_case.name,
282
+ description: value_or_empty_string(scenario.description),
283
+ line: test_case.location.lines.max,
277
284
  type: 'scenario'
278
285
  }
279
- @test_case_hash[:tags] = create_tags_array(scenario.tags) unless scenario.tags.empty?
280
- @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?
281
287
  end
282
288
 
283
- def scenario_outline(scenario)
284
- @test_case_hash = {
285
- id: create_id(scenario) + ';' + @example_id,
286
- keyword: scenario.keyword,
287
- name: scenario.to_s,
288
- description: scenario.description,
289
- line: @row.location.line,
290
- type: 'scenario'
291
- }
292
- tags = []
293
- tags += create_tags_array(scenario.tags) unless scenario.tags.empty?
294
- tags += @examples_table_tags if @examples_table_tags
295
- @test_case_hash[:tags] = tags unless tags.empty?
296
- comments = []
297
- comments += Formatter.create_comments_array(scenario.comments) unless scenario.comments.empty?
298
- comments += @examples_table_comments if @examples_table_comments
299
- comments += @row_comments if @row_comments
300
- @test_case_hash[:comments] = comments unless comments.empty?
301
- end
289
+ private
302
290
 
303
- def examples_table(examples_table)
304
- # the json file have traditionally used the header row as row 1,
305
- # wheras cucumber-ruby-core used the first example row as row 1.
306
- @example_id = create_id(examples_table) + ";#{@row.number + 1}"
291
+ def value_or_empty_string(value)
292
+ value.nil? ? '' : value
293
+ end
307
294
 
308
- @examples_table_tags = create_tags_array(examples_table.tags) unless examples_table.tags.empty?
309
- @examples_table_comments = Formatter.create_comments_array(examples_table.comments) unless examples_table.comments.empty?
295
+ def create_id(name)
296
+ name.downcase.tr(' ', '-')
310
297
  end
311
298
 
312
- def examples_table_row(row)
313
- @row = row
314
- @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
315
308
  end
316
309
 
317
- 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
318
315
 
319
- def create_id(element)
320
- 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
321
320
  end
322
321
 
323
- def create_tags_array(tags)
322
+ def create_tags_array_from_tags_array(tags)
324
323
  tags_array = []
325
324
  tags.each { |tag| tags_array << { name: tag.name, line: tag.location.line } }
326
325
  tags_array
327
326
  end
328
327
  end
329
328
  end
330
-
331
- def self.create_comments_array(comments)
332
- comments_array = []
333
- comments.each { |comment| comments_array << { value: comment.to_s.strip, line: comment.location.line } }
334
- comments_array
335
- end
336
329
  end
337
330
  end