cucumber 8.0.0 → 9.2.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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -23
  3. data/VERSION +1 -0
  4. data/lib/cucumber/cli/main.rb +1 -1
  5. data/lib/cucumber/cli/options.rb +66 -66
  6. data/lib/cucumber/cli/profile_loader.rb +5 -5
  7. data/lib/cucumber/configuration.rb +7 -2
  8. data/lib/cucumber/deprecate.rb +6 -47
  9. data/lib/cucumber/errors.rb +2 -1
  10. data/lib/cucumber/events/envelope.rb +2 -0
  11. data/lib/cucumber/events/gherkin_source_parsed.rb +2 -0
  12. data/lib/cucumber/events/gherkin_source_read.rb +2 -0
  13. data/lib/cucumber/events/test_case_finished.rb +2 -0
  14. data/lib/cucumber/events/test_case_started.rb +2 -0
  15. data/lib/cucumber/events/test_step_finished.rb +2 -0
  16. data/lib/cucumber/events/test_step_started.rb +2 -0
  17. data/lib/cucumber/events/undefined_parameter_type.rb +2 -0
  18. data/lib/cucumber/file_specs.rb +1 -1
  19. data/lib/cucumber/filters/retry.rb +20 -1
  20. data/lib/cucumber/formatter/ansicolor.rb +19 -27
  21. data/lib/cucumber/formatter/ast_lookup.rb +14 -6
  22. data/lib/cucumber/formatter/console.rb +16 -14
  23. data/lib/cucumber/formatter/console_counts.rb +3 -1
  24. data/lib/cucumber/formatter/console_issues.rb +4 -2
  25. data/lib/cucumber/formatter/curl_option_parser.rb +49 -0
  26. data/lib/cucumber/formatter/errors.rb +2 -0
  27. data/lib/cucumber/formatter/fail_fast.rb +1 -1
  28. data/lib/cucumber/formatter/html.rb +2 -0
  29. data/lib/cucumber/formatter/http_io.rb +10 -142
  30. data/lib/cucumber/formatter/io_http_buffer.rb +88 -0
  31. data/lib/cucumber/formatter/json.rb +2 -6
  32. data/lib/cucumber/formatter/junit.rb +4 -4
  33. data/lib/cucumber/formatter/message_builder.rb +21 -6
  34. data/lib/cucumber/formatter/pretty.rb +9 -5
  35. data/lib/cucumber/formatter/publish_banner_printer.rb +0 -2
  36. data/lib/cucumber/formatter/query/hook_by_test_step.rb +2 -0
  37. data/lib/cucumber/formatter/query/pickle_by_test.rb +2 -0
  38. data/lib/cucumber/formatter/query/pickle_step_by_test_step.rb +2 -0
  39. data/lib/cucumber/formatter/query/step_definitions_by_test_step.rb +2 -0
  40. data/lib/cucumber/formatter/query/test_case_started_by_test_case.rb +2 -0
  41. data/lib/cucumber/formatter/rerun.rb +3 -3
  42. data/lib/cucumber/formatter/unicode.rb +3 -3
  43. data/lib/cucumber/formatter/url_reporter.rb +3 -1
  44. data/lib/cucumber/gherkin/formatter/ansi_escapes.rb +23 -25
  45. data/lib/cucumber/glue/invoke_in_world.rb +2 -2
  46. data/lib/cucumber/glue/proto_world.rb +20 -25
  47. data/lib/cucumber/glue/registry_and_more.rb +9 -5
  48. data/lib/cucumber/glue/snippet.rb +4 -2
  49. data/lib/cucumber/glue/world_factory.rb +2 -0
  50. data/lib/cucumber/multiline_argument/data_table/diff_matrices.rb +2 -0
  51. data/lib/cucumber/multiline_argument/data_table.rb +34 -35
  52. data/lib/cucumber/platform.rb +11 -16
  53. data/lib/cucumber/rake/task.rb +2 -6
  54. data/lib/cucumber/running_test_case.rb +1 -1
  55. data/lib/cucumber/runtime/for_programming_languages.rb +1 -2
  56. data/lib/cucumber/runtime/meta_message_builder.rb +4 -2
  57. data/lib/cucumber/runtime/user_interface.rb +2 -2
  58. data/lib/cucumber/runtime.rb +5 -5
  59. data/lib/cucumber/step_match.rb +1 -1
  60. data/lib/cucumber/term/ansicolor.rb +1 -1
  61. data/lib/cucumber/term/banner.rb +2 -0
  62. metadata +83 -239
  63. data/CHANGELOG.md +0 -3231
  64. data/CONTRIBUTING.md +0 -246
  65. data/lib/autotest/cucumber.rb +0 -8
  66. data/lib/autotest/cucumber_mixin.rb +0 -133
  67. data/lib/autotest/cucumber_rails.rb +0 -8
  68. data/lib/autotest/cucumber_rails_rspec.rb +0 -8
  69. data/lib/autotest/cucumber_rails_rspec2.rb +0 -8
  70. data/lib/autotest/cucumber_rspec.rb +0 -8
  71. data/lib/autotest/cucumber_rspec2.rb +0 -8
  72. data/lib/autotest/discover.rb +0 -14
  73. data/lib/cucumber/version +0 -1
@@ -3,7 +3,7 @@
3
3
  require 'cucumber/platform'
4
4
  require 'cucumber/term/ansicolor'
5
5
 
6
- Cucumber::Term::ANSIColor.coloring = false if !$stdout.tty? && !ENV.key?('AUTOTEST')
6
+ Cucumber::Term::ANSIColor.coloring = false unless $stdout.tty?
7
7
 
8
8
  module Cucumber
9
9
  module Formatter
@@ -63,30 +63,24 @@ module Cucumber
63
63
  module ANSIColor
64
64
  include Cucumber::Term::ANSIColor
65
65
 
66
- # :stopdoc:
67
66
  ALIASES = Hash.new do |h, k|
68
67
  next unless k.to_s =~ /(.*)_param/
69
68
 
70
69
  "#{h[Regexp.last_match(1)]},bold"
71
70
  end.merge(
72
71
  'undefined' => 'yellow',
73
- 'pending' => 'yellow',
74
- 'flaky' => 'yellow',
75
- 'failed' => 'red',
76
- 'passed' => 'green',
77
- 'outline' => 'cyan',
78
- 'skipped' => 'cyan',
79
- 'comment' => 'grey',
80
- 'tag' => 'cyan'
72
+ 'pending' => 'yellow',
73
+ 'flaky' => 'yellow',
74
+ 'failed' => 'red',
75
+ 'passed' => 'green',
76
+ 'outline' => 'cyan',
77
+ 'skipped' => 'cyan',
78
+ 'comment' => 'grey',
79
+ 'tag' => 'cyan'
81
80
  )
82
- # :startdoc:
83
81
 
84
- # Apply the custom color scheme
85
- #
86
- # example:
87
- #
88
- # apply_custom_colors('passed=white')
89
- def apply_custom_colors(colors)
82
+ # Apply the custom color scheme -> i.e. apply_custom_colors('passed=white')
83
+ def self.apply_custom_colors(colors)
90
84
  colors.split(':').each do |pair|
91
85
  a = pair.split('=')
92
86
  ALIASES[a[0]] = a[1]
@@ -117,23 +111,21 @@ module Cucumber
117
111
  end
118
112
  end
119
113
 
120
- # :stopdoc:
121
- def cukes(n)
122
- ('(::) ' * n).strip
114
+ def cukes(amount)
115
+ ('(::) ' * amount).strip
123
116
  end
124
117
 
125
- def green_cukes(n)
126
- blink(green(cukes(n)))
118
+ def green_cukes(amount)
119
+ blink(green(cukes(amount)))
127
120
  end
128
121
 
129
- def red_cukes(n)
130
- blink(red(cukes(n)))
122
+ def red_cukes(amount)
123
+ blink(red(cukes(amount)))
131
124
  end
132
125
 
133
- def yellow_cukes(n)
134
- blink(yellow(cukes(n)))
126
+ def yellow_cukes(amount)
127
+ blink(yellow(cukes(amount)))
135
128
  end
136
- # :startdoc:
137
129
 
138
130
  private
139
131
 
@@ -112,16 +112,24 @@ module Cucumber
112
112
  if child.respond_to?(:rule) && child.rule
113
113
  process_scenario_container(child.rule)
114
114
  elsif child.respond_to?(:scenario) && child.scenario
115
- child.scenario.steps.each do |step|
116
- @lookup_hash[step.location.line] = StepSource.new(:Step, step)
117
- end
115
+ store_scenario_source_steps(child.scenario)
118
116
  elsif !child.background.nil?
119
- child.background.steps.each do |step|
120
- @lookup_hash[step.location.line] = StepSource.new(:Step, step)
121
- end
117
+ store_background_source_steps(child.background)
122
118
  end
123
119
  end
124
120
  end
121
+
122
+ def store_scenario_source_steps(scenario)
123
+ scenario.steps.each do |step|
124
+ @lookup_hash[step.location.line] = StepSource.new(:Step, step)
125
+ end
126
+ end
127
+
128
+ def store_background_source_steps(background)
129
+ background.steps.each do |step|
130
+ @lookup_hash[step.location.line] = StepSource.new(:Step, step)
131
+ end
132
+ end
125
133
  end
126
134
 
127
135
  KeywordSearchNode = Struct.new(:keyword, :previous_node)
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Metrics/ModuleLength
4
-
5
3
  require 'cucumber/formatter/ansicolor'
6
4
  require 'cucumber/formatter/duration'
7
5
  require 'cucumber/gherkin/i18n'
@@ -45,10 +43,10 @@ module Cucumber
45
43
  format_string(line, status)
46
44
  end
47
45
 
48
- def format_string(o, status)
46
+ def format_string(input, status)
49
47
  fmt = format_for(status)
50
- o.to_s.split("\n").map do |line|
51
- if Proc == fmt.class
48
+ input.to_s.split("\n").map do |line|
49
+ if fmt.instance_of?(Proc)
52
50
  fmt.call(line)
53
51
  else
54
52
  fmt % line
@@ -94,21 +92,21 @@ module Cucumber
94
92
  @io.flush
95
93
  end
96
94
 
97
- def print_exception(e, status, indent)
98
- string = exception_message_string(e, indent)
95
+ def print_exception(exception, status, indent)
96
+ string = exception_message_string(exception, indent)
99
97
  @io.puts(format_string(string, status))
100
98
  end
101
99
 
102
- def exception_message_string(e, indent_amount)
103
- message = "#{e.message} (#{e.class})".dup.force_encoding('UTF-8')
100
+ def exception_message_string(exception, indent_amount)
101
+ message = "#{exception.message} (#{exception.class})".dup.force_encoding('UTF-8')
104
102
  message = linebreaks(message, ENV['CUCUMBER_TRUNCATE_OUTPUT'].to_i)
105
103
 
106
- indent("#{message}\n#{e.backtrace.join("\n")}", indent_amount)
104
+ indent("#{message}\n#{exception.backtrace.join("\n")}", indent_amount)
107
105
  end
108
106
 
109
107
  # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/10655
110
108
  def linebreaks(msg, max)
111
- return msg unless max && max > 0
109
+ return msg unless max.positive?
112
110
 
113
111
  msg.gsub(/.{1,#{max}}(?:\s|\Z)/) do
114
112
  (Regexp.last_match(0) + 5.chr).gsub(/\n\005/, "\n").gsub(/\005/, "\n")
@@ -171,12 +169,17 @@ module Cucumber
171
169
  end
172
170
  end
173
171
 
174
- def attach(src, media_type)
172
+ def attach(src, media_type, filename)
175
173
  return unless media_type == 'text/x.cucumber.log+plain'
176
174
  return unless @io
177
175
 
178
176
  @io.puts
179
- @io.puts(format_string(src, :tag))
177
+ if filename
178
+ @io.puts("#{filename}: #{format_string(src, :tag)}")
179
+ else
180
+ @io.puts(format_string(src, :tag))
181
+ end
182
+
180
183
  @io.flush
181
184
  end
182
185
 
@@ -262,4 +265,3 @@ module Cucumber
262
265
  end
263
266
  end
264
267
  end
265
- # rubocop:enable Metrics/ModuleLength
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'cucumber/formatter/console'
2
4
 
3
5
  module Cucumber
@@ -30,7 +32,7 @@ module Cucumber
30
32
 
31
33
  def status_counts(summary)
32
34
  counts = Core::Test::Result::TYPES.map { |status| [status, summary.total(status)] }
33
- counts = counts.select { |_status, count| count > 0 }
35
+ counts = counts.select { |_status, count| count.positive? }
34
36
  counts = counts.map { |status, count| format_string("#{count} #{status}", status) }
35
37
  "(#{counts.join(', ')})" if counts.any?
36
38
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'cucumber/formatter/console'
2
4
 
3
5
  module Cucumber
@@ -12,9 +14,9 @@ module Cucumber
12
14
  @config.on_event(:test_case_finished) do |event|
13
15
  if event.test_case != @previous_test_case
14
16
  @previous_test_case = event.test_case
15
- @issues[event.result.to_sym] << event.test_case unless event.result.ok?(@config.strict)
17
+ @issues[event.result.to_sym] << event.test_case unless event.result.ok?(strict: @config.strict)
16
18
  elsif event.result.passed?
17
- @issues[:flaky] << event.test_case unless Core::Test::Result::Flaky.ok?(@config.strict.strict?(:flaky))
19
+ @issues[:flaky] << event.test_case unless Core::Test::Result::Flaky.ok?(strict: @config.strict.strict?(:flaky))
18
20
  @issues[:failed].delete(event.test_case)
19
21
  end
20
22
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+
5
+ module Cucumber
6
+ module Formatter
7
+ class CurlOptionParser
8
+ def self.parse(options)
9
+ args = Shellwords.split(options)
10
+
11
+ url = nil
12
+ http_method = 'PUT'
13
+ headers = {}
14
+
15
+ until args.empty?
16
+ arg = args.shift
17
+ case arg
18
+ when '-X', '--request'
19
+ http_method = remove_arg_for(args, arg)
20
+ when '-H'
21
+ header_arg = remove_arg_for(args, arg)
22
+ headers = headers.merge(parse_header(header_arg))
23
+ else
24
+ raise StandardError, "#{options} was not a valid curl command. Can't set url to #{arg} it is already set to #{url}" if url
25
+
26
+ url = arg
27
+ end
28
+ end
29
+ raise StandardError, "#{options} was not a valid curl command" unless url
30
+
31
+ [url, http_method, headers]
32
+ end
33
+
34
+ # TODO: [LH] -> Switch below methods to private
35
+ def self.remove_arg_for(args, arg)
36
+ return args.shift unless args.empty?
37
+
38
+ raise StandardError, "Missing argument for #{arg}"
39
+ end
40
+
41
+ def self.parse_header(header_arg)
42
+ parts = header_arg.split(':', 2)
43
+ raise StandardError, "#{header_arg} was not a valid header" unless parts.length == 2
44
+
45
+ { parts[0].strip => parts[1].strip }
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cucumber
2
4
  module Formatter
3
5
  class TestCaseUnknownError < StandardError; end
@@ -12,7 +12,7 @@ module Cucumber
12
12
  test_case, result = *event.attributes
13
13
  if test_case != @previous_test_case
14
14
  @previous_test_case = event.test_case
15
- Cucumber.wants_to_quit = true unless result.ok?(configuration.strict)
15
+ Cucumber.wants_to_quit = true unless result.ok?(strict: configuration.strict)
16
16
  elsif result.passed?
17
17
  Cucumber.wants_to_quit = false
18
18
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'cucumber/formatter/io'
2
4
  require 'cucumber/html_formatter'
3
5
  require 'cucumber/formatter/message_builder'
@@ -1,151 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'net/http'
2
4
  require 'tempfile'
3
- require 'shellwords'
5
+ require_relative 'curl_option_parser'
6
+ require_relative 'io_http_buffer'
4
7
 
5
8
  module Cucumber
6
9
  module Formatter
7
10
  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
-
39
- url = arg
40
- end
41
- end
42
- raise StandardError, "#{options} was not a valid curl command" unless url
43
-
44
- [
45
- url,
46
- http_method,
47
- headers
48
- ]
49
- end
50
-
51
- def self.remove_arg_for(args, arg)
52
- return args.shift unless args.empty?
53
-
54
- raise StandardError, "Missing argument for #{arg}"
55
- end
56
-
57
- def self.parse_header(header_arg)
58
- parts = header_arg.split(':', 2)
59
- raise StandardError, "#{header_arg} was not a valid header" unless parts.length == 2
60
-
61
- { parts[0].strip => parts[1].strip }
62
- end
63
- end
64
-
65
- class IOHTTPBuffer
66
- attr_reader :uri, :method, :headers
67
-
68
- def initialize(uri, method, headers = {}, https_verify_mode = nil, reporter = nil)
69
- @uri = URI(uri)
70
- @method = method
71
- @headers = headers
72
- @write_io = Tempfile.new('cucumber', encoding: 'UTF-8')
73
- @https_verify_mode = https_verify_mode
74
- @reporter = reporter || NoReporter.new
75
- end
76
-
77
- def close
78
- response = send_content(@uri, @method, @headers)
79
- @reporter.report(response.body)
80
- @write_io.close
81
- return if response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPRedirection)
82
-
83
- raise StandardError, "request to #{uri} failed with status #{response.code}"
84
- end
85
-
86
- def write(data)
87
- @write_io.write(data)
88
- end
89
-
90
- def flush
91
- @write_io.flush
92
- end
93
-
94
- def closed?
95
- @write_io.closed?
96
- end
97
-
98
- private
99
-
100
- def send_content(uri, method, headers, attempt = 10)
101
- content = (method == 'GET' ? StringIO.new : @write_io)
102
- http = build_client(uri, @https_verify_mode)
103
-
104
- raise StandardError, "request to #{uri} failed (too many redirections)" if attempt <= 0
105
-
106
- req = build_request(
107
- uri,
108
- method,
109
- headers.merge(
110
- 'Content-Length' => content.size.to_s
111
- )
112
- )
113
-
114
- content.rewind
115
- req.body_stream = content
116
-
117
- begin
118
- response = http.request(req)
119
- rescue SystemCallError
120
- # We may get the redirect response before pushing the file.
121
- response = http.request(build_request(uri, method, headers))
122
- end
123
-
124
- case response
125
- when Net::HTTPAccepted
126
- send_content(URI(response['Location']), 'PUT', {}, attempt - 1) if response['Location']
127
- when Net::HTTPRedirection
128
- send_content(URI(response['Location']), method, headers, attempt - 1)
129
- end
130
- response
131
- end
132
-
133
- def build_request(uri, method, headers)
134
- method_class_name = "#{method[0].upcase}#{method[1..].downcase}"
135
- req = Net::HTTP.const_get(method_class_name).new(uri)
136
- headers.each do |header, value|
137
- req[header] = value
138
- end
139
- req
140
- end
141
-
142
- def build_client(uri, https_verify_mode)
143
- http = Net::HTTP.new(uri.hostname, uri.port)
144
- if uri.scheme == 'https'
145
- http.use_ssl = true
146
- http.verify_mode = https_verify_mode if https_verify_mode
147
- end
148
- http
11
+ # Returns an IO that will write to a HTTP request's body
12
+ # https_verify_mode can be set to OpenSSL::SSL::VERIFY_NONE
13
+ # to ignore unsigned certificate - setting to nil will verify the certificate
14
+ def self.open(url, https_verify_mode = nil, reporter = nil)
15
+ uri, method, headers = CurlOptionParser.parse(url)
16
+ IOHTTPBuffer.new(uri, method, headers, https_verify_mode, reporter)
149
17
  end
150
18
  end
151
19
  end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cucumber
4
+ module Formatter
5
+ class IOHTTPBuffer
6
+ attr_reader :uri, :method, :headers
7
+
8
+ def initialize(uri, method, headers = {}, https_verify_mode = nil, reporter = nil)
9
+ @uri = URI(uri)
10
+ @method = method
11
+ @headers = headers
12
+ @write_io = Tempfile.new('cucumber', encoding: 'UTF-8')
13
+ @https_verify_mode = https_verify_mode
14
+ @reporter = reporter || NoReporter.new
15
+ end
16
+
17
+ def close
18
+ @reporter.report(response.body)
19
+ @write_io.close
20
+ return if response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPRedirection)
21
+
22
+ raise StandardError, "request to #{uri} failed with status #{response.code}"
23
+ end
24
+
25
+ def write(data)
26
+ @write_io.write(data)
27
+ end
28
+
29
+ def flush
30
+ @write_io.flush
31
+ end
32
+
33
+ def closed?
34
+ @write_io.closed?
35
+ end
36
+
37
+ private
38
+
39
+ def response
40
+ @response ||= send_content(uri, method, headers)
41
+ end
42
+
43
+ def send_content(uri, method, headers, attempts_remaining = 10)
44
+ content = (method == 'GET' ? StringIO.new : @write_io)
45
+ http = build_client(uri)
46
+
47
+ raise StandardError, "request to #{uri} failed (too many redirections)" if attempts_remaining <= 0
48
+
49
+ request = build_request(uri, method, headers.merge('Content-Length' => content.size.to_s))
50
+ content.rewind
51
+ request.body_stream = content
52
+
53
+ begin
54
+ response = http.request(request)
55
+ rescue SystemCallError
56
+ # We may get the redirect response before pushing the file.
57
+ response = http.request(build_request(uri, method, headers))
58
+ end
59
+
60
+ case response
61
+ when Net::HTTPAccepted
62
+ send_content(URI(response['Location']), 'PUT', {}, attempts_remaining - 1) if response['Location']
63
+ when Net::HTTPRedirection
64
+ send_content(URI(response['Location']), method, headers, attempts_remaining - 1)
65
+ end
66
+ response
67
+ end
68
+
69
+ def build_request(uri, method, headers)
70
+ method_class_name = "#{method[0].upcase}#{method[1..].downcase}"
71
+ Net::HTTP.const_get(method_class_name).new(uri).tap do |request|
72
+ headers.each do |header, value|
73
+ request[header] = value
74
+ end
75
+ end
76
+ end
77
+
78
+ def build_client(uri)
79
+ Net::HTTP.new(uri.hostname, uri.port).tap do |http|
80
+ if uri.scheme == 'https'
81
+ http.use_ssl = true
82
+ http.verify_mode = @https_verify_mode if @https_verify_mode
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -85,7 +85,7 @@ module Cucumber
85
85
  @io.write(JSON.pretty_generate(@feature_hashes))
86
86
  end
87
87
 
88
- def attach(src, mime_type)
88
+ def attach(src, mime_type, _filename)
89
89
  if mime_type == 'text/x.cucumber.log+plain'
90
90
  test_step_output << src
91
91
  return
@@ -102,7 +102,7 @@ module Cucumber
102
102
  private
103
103
 
104
104
  def same_feature_as_previous_test_case?(test_case)
105
- current_feature[:uri] == test_case.location.file
105
+ @feature_hash&.fetch(:uri, nil) == test_case.location.file
106
106
  end
107
107
 
108
108
  def first_step_after_background?(test_step)
@@ -113,10 +113,6 @@ module Cucumber
113
113
  test_step.location.file.include?('lib/cucumber/')
114
114
  end
115
115
 
116
- def current_feature
117
- @feature_hash ||= {} # rubocop:disable Naming/MemoizedInstanceVariableName
118
- end
119
-
120
116
  def feature_elements
121
117
  @feature_hash[:elements] ||= []
122
118
  end
@@ -54,7 +54,7 @@ module Cucumber
54
54
  test_step, result = *event.attributes
55
55
  return if @failing_test_step
56
56
 
57
- @failing_test_step = test_step unless result.ok?(@config.strict)
57
+ @failing_test_step = test_step unless result.ok?(strict: @config.strict)
58
58
  end
59
59
 
60
60
  def on_test_case_finished(event)
@@ -71,7 +71,7 @@ module Cucumber
71
71
  end
72
72
 
73
73
  def on_test_run_finished(_event)
74
- @features_data.each { |_file, data| end_feature(data) }
74
+ @features_data.each_value { |data| end_feature(data) }
75
75
  end
76
76
 
77
77
  private
@@ -111,7 +111,7 @@ module Cucumber
111
111
  scenario_source = @ast_lookup.scenario_source(test_case)
112
112
  keyword = scenario_source.type == :Scenario ? scenario_source.scenario.keyword : scenario_source.scenario_outline.keyword
113
113
  output = "#{keyword}: #{scenario}\n\n"
114
- return output if result.ok?(@config.strict)
114
+ return output if result.ok?(strict: @config.strict)
115
115
 
116
116
  if scenario_source.type == :Scenario
117
117
  if @failing_test_step
@@ -140,7 +140,7 @@ module Cucumber
140
140
  testcase_attributes = get_testcase_attributes(classname, name, duration, filename)
141
141
 
142
142
  @current_feature_data[:builder].testcase(testcase_attributes) do
143
- if !result.passed? && result.ok?(@config.strict)
143
+ if !result.passed? && result.ok?(strict: @config.strict)
144
144
  @current_feature_data[:builder].skipped
145
145
  @current_feature_data[:skipped] += 1
146
146
  elsif !result.passed?
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'base64'
2
4
  require 'cucumber/formatter/backtrace_filter'
3
5
  require 'cucumber/formatter/query/hook_by_test_step'
@@ -40,11 +42,12 @@ module Cucumber
40
42
  raise 'To be implemented'
41
43
  end
42
44
 
43
- def attach(src, media_type)
45
+ def attach(src, media_type, filename)
44
46
  attachment_data = {
45
47
  test_step_id: @current_test_step_id,
46
48
  test_case_started_id: @current_test_case_started_id,
47
- media_type: media_type
49
+ media_type: media_type,
50
+ file_name: filename
48
51
  }
49
52
 
50
53
  if media_type.start_with?('text/')
@@ -129,7 +132,7 @@ module Cucumber
129
132
  @step_definitions_by_test_step.step_match_arguments(step).map do |argument|
130
133
  Cucumber::Messages::StepMatchArgument.new(
131
134
  group: argument_group_to_message(argument.group),
132
- parameter_type_name: argument.parameter_type.name
135
+ parameter_type_name: parameter_type_name(argument)
133
136
  )
134
137
  end
135
138
  end
@@ -142,6 +145,10 @@ module Cucumber
142
145
  )
143
146
  end
144
147
 
148
+ def parameter_type_name(step_match_argument)
149
+ step_match_argument.parameter_type&.name if step_match_argument.respond_to?(:parameter_type)
150
+ end
151
+
145
152
  def on_test_run_started(*)
146
153
  message = Cucumber::Messages::Envelope.new(
147
154
  test_run_started: Cucumber::Messages::TestRunStarted.new(
@@ -190,10 +197,13 @@ module Cucumber
190
197
 
191
198
  result_message = result.to_message
192
199
  if result.failed? || result.pending?
200
+ message_element = result.failed? ? result.exception : result
201
+
193
202
  result_message = Cucumber::Messages::TestStepResult.new(
194
203
  status: result_message.status,
195
204
  duration: result_message.duration,
196
- message: create_error_message(result)
205
+ message: create_error_message(message_element),
206
+ exception: create_exception_object(result, message_element)
197
207
  )
198
208
  end
199
209
 
@@ -209,12 +219,17 @@ module Cucumber
209
219
  output_envelope(message)
210
220
  end
211
221
 
212
- def create_error_message(result)
213
- message_element = result.failed? ? result.exception : result
222
+ def create_error_message(message_element)
214
223
  message = "#{message_element.message} (#{message_element.class})"
215
224
  ([message] + message_element.backtrace).join("\n")
216
225
  end
217
226
 
227
+ def create_exception_object(result, message_element)
228
+ return unless result.failed?
229
+
230
+ Cucumber::Messages::Exception.from_h({ type: message_element.class, message: message_element.message })
231
+ end
232
+
218
233
  def on_test_case_finished(event)
219
234
  message = Cucumber::Messages::Envelope.new(
220
235
  test_case_finished: Cucumber::Messages::TestCaseFinished.new(