cucumber 8.0.0 → 9.2.0

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