cucumber 3.0.2 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
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