cucumber 3.1.2 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +287 -14
  3. data/CONTRIBUTING.md +11 -25
  4. data/README.md +4 -5
  5. data/bin/cucumber +1 -1
  6. data/lib/autotest/cucumber_mixin.rb +46 -53
  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 +97 -76
  11. data/lib/cucumber/cli/profile_loader.rb +49 -26
  12. data/lib/cucumber/configuration.rb +44 -29
  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 +163 -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 +147 -0
  44. data/lib/cucumber/formatter/ignore_missing_messages.rb +1 -1
  45. data/lib/cucumber/formatter/interceptor.rb +11 -30
  46. data/lib/cucumber/formatter/io.rb +55 -13
  47. data/lib/cucumber/formatter/json.rb +102 -110
  48. data/lib/cucumber/formatter/junit.rb +55 -55
  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/publish_banner_printer.rb +77 -0
  54. data/lib/cucumber/formatter/query/hook_by_test_step.rb +31 -0
  55. data/lib/cucumber/formatter/query/pickle_by_test.rb +26 -0
  56. data/lib/cucumber/formatter/query/pickle_step_by_test_step.rb +26 -0
  57. data/lib/cucumber/formatter/query/step_definitions_by_test_step.rb +40 -0
  58. data/lib/cucumber/formatter/query/test_case_started_by_test_case.rb +40 -0
  59. data/lib/cucumber/formatter/rerun.rb +22 -4
  60. data/lib/cucumber/formatter/stepdefs.rb +1 -2
  61. data/lib/cucumber/formatter/steps.rb +3 -4
  62. data/lib/cucumber/formatter/summary.rb +16 -8
  63. data/lib/cucumber/formatter/unicode.rb +15 -17
  64. data/lib/cucumber/formatter/url_reporter.rb +17 -0
  65. data/lib/cucumber/formatter/usage.rb +11 -10
  66. data/lib/cucumber/gherkin/data_table_parser.rb +17 -6
  67. data/lib/cucumber/gherkin/formatter/ansi_escapes.rb +13 -17
  68. data/lib/cucumber/gherkin/formatter/escaping.rb +2 -2
  69. data/lib/cucumber/gherkin/steps_parser.rb +17 -8
  70. data/lib/cucumber/glue/hook.rb +34 -11
  71. data/lib/cucumber/glue/invoke_in_world.rb +13 -18
  72. data/lib/cucumber/glue/proto_world.rb +42 -33
  73. data/lib/cucumber/glue/registry_and_more.rb +42 -12
  74. data/lib/cucumber/glue/snippet.rb +23 -22
  75. data/lib/cucumber/glue/step_definition.rb +42 -19
  76. data/lib/cucumber/glue/world_factory.rb +1 -1
  77. data/lib/cucumber/hooks.rb +11 -11
  78. data/lib/cucumber/multiline_argument.rb +4 -6
  79. data/lib/cucumber/multiline_argument/data_table.rb +97 -64
  80. data/lib/cucumber/multiline_argument/data_table/diff_matrices.rb +2 -2
  81. data/lib/cucumber/multiline_argument/doc_string.rb +1 -1
  82. data/lib/cucumber/platform.rb +3 -3
  83. data/lib/cucumber/rake/task.rb +16 -18
  84. data/lib/cucumber/rspec/disable_option_parser.rb +9 -8
  85. data/lib/cucumber/rspec/doubles.rb +3 -5
  86. data/lib/cucumber/running_test_case.rb +2 -53
  87. data/lib/cucumber/runtime.rb +41 -58
  88. data/lib/cucumber/runtime/after_hooks.rb +8 -4
  89. data/lib/cucumber/runtime/before_hooks.rb +8 -4
  90. data/lib/cucumber/runtime/for_programming_languages.rb +4 -2
  91. data/lib/cucumber/runtime/step_hooks.rb +6 -2
  92. data/lib/cucumber/runtime/support_code.rb +13 -15
  93. data/lib/cucumber/runtime/user_interface.rb +6 -16
  94. data/lib/cucumber/step_definition_light.rb +4 -3
  95. data/lib/cucumber/step_definitions.rb +2 -2
  96. data/lib/cucumber/step_match.rb +12 -11
  97. data/lib/cucumber/step_match_search.rb +2 -1
  98. data/lib/cucumber/term/ansicolor.rb +9 -9
  99. data/lib/cucumber/term/banner.rb +56 -0
  100. data/lib/cucumber/version +1 -1
  101. metadata +254 -83
  102. data/lib/cucumber/events/gherkin_source_parsed.rb~ +0 -14
  103. data/lib/cucumber/formatter/ast_lookup.rb~ +0 -9
  104. data/lib/cucumber/formatter/cucumber.css +0 -286
  105. data/lib/cucumber/formatter/cucumber.sass +0 -247
  106. data/lib/cucumber/formatter/hook_query_visitor.rb +0 -42
  107. data/lib/cucumber/formatter/html_builder.rb +0 -121
  108. data/lib/cucumber/formatter/inline-js.js +0 -30
  109. data/lib/cucumber/formatter/jquery-min.js +0 -154
  110. data/lib/cucumber/formatter/json_pretty.rb +0 -11
  111. data/lib/cucumber/formatter/legacy_api/adapter.rb +0 -1028
  112. data/lib/cucumber/formatter/legacy_api/ast.rb +0 -394
  113. data/lib/cucumber/formatter/legacy_api/results.rb +0 -50
  114. data/lib/cucumber/formatter/legacy_api/runtime_facade.rb +0 -32
  115. data/lib/cucumber/step_argument.rb +0 -25
@@ -0,0 +1,147 @@
1
+ require 'net/http'
2
+ require 'tempfile'
3
+ require 'shellwords'
4
+
5
+ module Cucumber
6
+ module Formatter
7
+ class HTTPIO
8
+ class << self
9
+ # Returns an IO that will write to a HTTP request's body
10
+ # https_verify_mode can be set to OpenSSL::SSL::VERIFY_NONE
11
+ # to ignore unsigned certificate - setting to nil will verify the certificate
12
+ def open(url, https_verify_mode = nil, reporter = nil)
13
+ @https_verify_mode = https_verify_mode
14
+ uri, method, headers = CurlOptionParser.parse(url)
15
+ IOHTTPBuffer.new(uri, method, headers, https_verify_mode, reporter)
16
+ end
17
+ end
18
+ end
19
+
20
+ class CurlOptionParser
21
+ def self.parse(options)
22
+ args = Shellwords.split(options)
23
+
24
+ url = nil
25
+ http_method = 'PUT'
26
+ headers = {}
27
+
28
+ until args.empty?
29
+ arg = args.shift
30
+ case arg
31
+ when '-X', '--request'
32
+ http_method = remove_arg_for(args, arg)
33
+ when '-H'
34
+ header_arg = remove_arg_for(args, arg)
35
+ headers = headers.merge(parse_header(header_arg))
36
+ else
37
+ raise StandardError, "#{options} was not a valid curl command. Can't set url to #{arg} it is already set to #{url}" if url
38
+ url = arg
39
+ end
40
+ end
41
+ raise StandardError, "#{options} was not a valid curl command" unless url
42
+
43
+ [
44
+ url,
45
+ http_method,
46
+ headers
47
+ ]
48
+ end
49
+
50
+ def self.remove_arg_for(args, arg)
51
+ return args.shift unless args.empty?
52
+ raise StandardError, "Missing argument for #{arg}"
53
+ end
54
+
55
+ def self.parse_header(header_arg)
56
+ parts = header_arg.split(':', 2)
57
+ raise StandardError, "#{header_arg} was not a valid header" unless parts.length == 2
58
+ { parts[0].strip => parts[1].strip }
59
+ end
60
+ end
61
+
62
+ class IOHTTPBuffer
63
+ attr_reader :uri, :method, :headers
64
+
65
+ def initialize(uri, method, headers = {}, https_verify_mode = nil, reporter = nil)
66
+ @uri = URI(uri)
67
+ @method = method
68
+ @headers = headers
69
+ @write_io = Tempfile.new('cucumber', encoding: 'UTF-8')
70
+ @https_verify_mode = https_verify_mode
71
+ @reporter = reporter || NoReporter.new
72
+ end
73
+
74
+ def close
75
+ response = send_content(@uri, @method, @headers)
76
+ @reporter.report(response.body)
77
+ @write_io.close
78
+ return if response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPRedirection)
79
+ raise StandardError, "request to #{uri} failed with status #{response.code}"
80
+ end
81
+
82
+ def write(data)
83
+ @write_io.write(data)
84
+ end
85
+
86
+ def flush
87
+ @write_io.flush
88
+ end
89
+
90
+ def closed?
91
+ @write_io.closed?
92
+ end
93
+
94
+ private
95
+
96
+ def send_content(uri, method, headers, attempt = 10)
97
+ content = (method == 'GET' ? StringIO.new : @write_io)
98
+ http = build_client(uri, @https_verify_mode)
99
+
100
+ raise StandardError, "request to #{uri} failed (too many redirections)" if attempt <= 0
101
+ req = build_request(
102
+ uri,
103
+ method,
104
+ headers.merge(
105
+ 'Content-Length' => content.size.to_s
106
+ )
107
+ )
108
+
109
+ content.rewind
110
+ req.body_stream = content
111
+
112
+ begin
113
+ response = http.request(req)
114
+ rescue SystemCallError
115
+ # We may get the redirect response before pushing the file.
116
+ response = http.request(build_request(uri, method, headers))
117
+ end
118
+
119
+ case response
120
+ when Net::HTTPAccepted
121
+ send_content(URI(response['Location']), 'PUT', {}, attempt - 1) if response['Location']
122
+ when Net::HTTPRedirection
123
+ send_content(URI(response['Location']), method, headers, attempt - 1)
124
+ end
125
+ response
126
+ end
127
+
128
+ def build_request(uri, method, headers)
129
+ method_class_name = "#{method[0].upcase}#{method[1..-1].downcase}"
130
+ req = Net::HTTP.const_get(method_class_name).new(uri)
131
+ headers.each do |header, value|
132
+ req[header] = value
133
+ end
134
+ req
135
+ end
136
+
137
+ def build_client(uri, https_verify_mode)
138
+ http = Net::HTTP.new(uri.hostname, uri.port)
139
+ if uri.scheme == 'https'
140
+ http.use_ssl = true
141
+ http.verify_mode = https_verify_mode if https_verify_mode
142
+ end
143
+ http
144
+ end
145
+ end
146
+ end
147
+ 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)
@@ -5,34 +5,23 @@ module Cucumber
5
5
  module Interceptor
6
6
  class Pipe
7
7
  attr_reader :pipe
8
+
8
9
  def initialize(pipe)
9
10
  @pipe = pipe
10
11
  @buffer = StringIO.new
11
12
  @wrapped = true
13
+ @lock = Mutex.new
12
14
  end
13
15
 
14
16
  def write(str)
15
- lock.synchronize do
17
+ @lock.synchronize do
16
18
  @buffer << str if @wrapped
17
19
  return @pipe.write(str)
18
20
  end
19
21
  end
20
22
 
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
23
  def buffer_string
35
- lock.synchronize do
24
+ @lock.synchronize do
36
25
  return @buffer.string.dup
37
26
  end
38
27
  end
@@ -43,17 +32,15 @@ module Cucumber
43
32
  end
44
33
 
45
34
  def method_missing(method, *args, &blk)
46
- @pipe.send(method, *args, &blk)
35
+ @pipe.respond_to?(method) ? @pipe.send(method, *args, &blk) : super
47
36
  end
48
37
 
49
- def respond_to?(method, include_private = false)
38
+ def respond_to_missing?(method, include_private = false)
50
39
  super || @pipe.respond_to?(method, include_private)
51
40
  end
52
41
 
53
42
  def self.validate_pipe(pipe)
54
- unless [:stdout, :stderr].include? pipe
55
- raise ArgumentError, '#wrap only accepts :stderr or :stdout'
56
- end
43
+ raise ArgumentError, '#wrap only accepts :stderr or :stdout' unless %i[stdout stderr].include? pipe
57
44
  end
58
45
 
59
46
  def self.unwrap!(pipe)
@@ -75,19 +62,13 @@ module Cucumber
75
62
 
76
63
  case pipe
77
64
  when :stderr
78
- $stderr = self.new($stderr)
79
- return $stderr
65
+ $stderr = new($stderr)
66
+ $stderr
80
67
  when :stdout
81
- $stdout = self.new($stdout)
82
- return $stdout
68
+ $stdout = new($stdout)
69
+ $stdout
83
70
  end
84
71
  end
85
-
86
- private
87
-
88
- def lock
89
- @lock ||= Mutex.new
90
- end
91
72
  end
92
73
  end
93
74
  end
@@ -1,32 +1,74 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'cucumber/formatter/http_io'
4
+ require 'cucumber/formatter/url_reporter'
5
+ require 'cucumber/cli/options'
6
+
3
7
  module Cucumber
4
8
  module Formatter
5
9
  module Io
6
10
  module_function
7
11
 
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'))
12
- at_exit do
13
- unless file.closed?
14
- file.flush
15
- file.close
12
+ def ensure_io(path_or_url_or_io, error_stream)
13
+ return nil if path_or_url_or_io.nil?
14
+ return path_or_url_or_io if io?(path_or_url_or_io)
15
+
16
+ io = if url?(path_or_url_or_io)
17
+ url = path_or_url_or_io
18
+ reporter = url.start_with?(Cucumber::Cli::Options::CUCUMBER_PUBLISH_URL) ? URLReporter.new(error_stream) : NoReporter.new
19
+ HTTPIO.open(url, nil, reporter)
20
+ else
21
+ File.open(path_or_url_or_io, Cucumber.file_mode('w'))
22
+ end
23
+ @io_objects_to_close ||= []
24
+ @io_objects_to_close.push(io)
25
+ io
26
+ end
27
+
28
+ module ClassMethods
29
+ def new(*args, &block)
30
+ instance = super
31
+
32
+ config = args[0]
33
+ if config.respond_to? :on_event
34
+ config.on_event :test_run_finished do
35
+ ios = instance.instance_variable_get(:@io_objects_to_close) || []
36
+ ios.each do |io|
37
+ at_exit do
38
+ unless io.closed?
39
+ io.flush
40
+ io.close
41
+ end
42
+ end
43
+ end
44
+ end
16
45
  end
46
+
47
+ instance
17
48
  end
18
- file
49
+ end
50
+
51
+ def self.included(formatter_class)
52
+ formatter_class.extend(ClassMethods)
53
+ end
54
+
55
+ def io?(path_or_url_or_io)
56
+ path_or_url_or_io.respond_to?(:write)
57
+ end
58
+
59
+ def url?(path_or_url_or_io)
60
+ path_or_url_or_io.match(%r{^https?://})
19
61
  end
20
62
 
21
63
  def ensure_file(path, name)
22
- raise "You *must* specify --out FILE for the #{name} formatter" unless String === path
64
+ raise "You *must* specify --out FILE for the #{name} formatter" unless String == path.class
23
65
  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))
25
- ensure_io(path)
66
+ raise "I can't write #{name} to a file in the non-existing directory #{File.dirname(path)}" unless File.directory?(File.dirname(path))
67
+ ensure_io(path, nil)
26
68
  end
27
69
 
28
70
  def ensure_dir(path, name)
29
- raise "You *must* specify --out DIR for the #{name} formatter" unless String === path
71
+ raise "You *must* specify --out DIR for the #{name} formatter" unless String == path.class
30
72
  raise "I can't write #{name} reports to a file - it has to be a directory" if File.file?(path)
31
73
  FileUtils.mkdir_p(path) unless File.directory?(path)
32
74
  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)
16
- @io = ensure_io(config.out_stream)
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
+ '6.0.0'
23
+ )
24
+
25
+ @io = ensure_io(config.out_stream, config.error_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))
89
+ @io.write(JSON.generate(@feature_hashes, pretty: true))
78
90
  end
79
91
 
80
- def puts(message)
81
- test_step_output << message
82
- end
83
-
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