cucumber 7.1.0 → 9.2.1
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.
- checksums.yaml +4 -4
- data/README.md +17 -27
- data/VERSION +1 -0
- data/lib/cucumber/cli/configuration.rb +4 -1
- data/lib/cucumber/cli/main.rb +5 -4
- data/lib/cucumber/cli/options.rb +73 -67
- data/lib/cucumber/cli/profile_loader.rb +6 -10
- data/lib/cucumber/cli/rerun_file.rb +1 -1
- data/lib/cucumber/configuration.rb +12 -6
- data/lib/cucumber/constantize.rb +1 -1
- data/lib/cucumber/deprecate.rb +6 -46
- data/lib/cucumber/errors.rb +3 -2
- data/lib/cucumber/events/envelope.rb +2 -0
- data/lib/cucumber/events/gherkin_source_parsed.rb +2 -0
- data/lib/cucumber/events/gherkin_source_read.rb +2 -0
- data/lib/cucumber/events/hook_test_step_created.rb +1 -2
- data/lib/cucumber/events/step_activated.rb +0 -6
- data/lib/cucumber/events/step_definition_registered.rb +0 -5
- data/lib/cucumber/events/test_case_created.rb +1 -2
- data/lib/cucumber/events/test_case_finished.rb +2 -0
- data/lib/cucumber/events/test_case_started.rb +2 -0
- data/lib/cucumber/events/test_run_finished.rb +2 -1
- data/lib/cucumber/events/test_step_created.rb +1 -2
- data/lib/cucumber/events/test_step_finished.rb +2 -0
- data/lib/cucumber/events/test_step_started.rb +2 -0
- data/lib/cucumber/events/undefined_parameter_type.rb +3 -2
- data/lib/cucumber/events.rb +1 -1
- data/lib/cucumber/file_specs.rb +2 -1
- data/lib/cucumber/filters/activate_steps.rb +1 -0
- data/lib/cucumber/filters/retry.rb +20 -1
- data/lib/cucumber/filters/tag_limits/verifier.rb +1 -3
- data/lib/cucumber/filters/tag_limits.rb +1 -3
- data/lib/cucumber/formatter/ansicolor.rb +70 -78
- data/lib/cucumber/formatter/ast_lookup.rb +16 -8
- data/lib/cucumber/formatter/backtrace_filter.rb +2 -1
- data/lib/cucumber/formatter/console.rb +26 -16
- data/lib/cucumber/formatter/console_counts.rb +3 -1
- data/lib/cucumber/formatter/console_issues.rb +10 -3
- data/lib/cucumber/formatter/curl_option_parser.rb +49 -0
- data/lib/cucumber/formatter/duration_extractor.rb +1 -0
- data/lib/cucumber/formatter/errors.rb +3 -0
- data/lib/cucumber/formatter/fail_fast.rb +1 -1
- data/lib/cucumber/formatter/fanout.rb +1 -1
- data/lib/cucumber/formatter/html.rb +2 -0
- data/lib/cucumber/formatter/http_io.rb +10 -137
- data/lib/cucumber/formatter/ignore_missing_messages.rb +1 -1
- data/lib/cucumber/formatter/io.rb +5 -3
- data/lib/cucumber/formatter/io_http_buffer.rb +88 -0
- data/lib/cucumber/formatter/json.rb +10 -12
- data/lib/cucumber/formatter/junit.rb +10 -7
- data/lib/cucumber/formatter/message_builder.rb +24 -8
- data/lib/cucumber/formatter/pretty.rb +24 -10
- data/lib/cucumber/formatter/progress.rb +1 -0
- data/lib/cucumber/formatter/publish_banner_printer.rb +0 -2
- data/lib/cucumber/formatter/query/hook_by_test_step.rb +3 -0
- data/lib/cucumber/formatter/query/pickle_by_test.rb +2 -0
- data/lib/cucumber/formatter/query/pickle_step_by_test_step.rb +2 -0
- data/lib/cucumber/formatter/query/step_definitions_by_test_step.rb +2 -0
- data/lib/cucumber/formatter/query/test_case_started_by_test_case.rb +4 -0
- data/lib/cucumber/formatter/rerun.rb +5 -3
- data/lib/cucumber/formatter/summary.rb +1 -0
- data/lib/cucumber/formatter/unicode.rb +7 -7
- data/lib/cucumber/formatter/url_reporter.rb +3 -1
- data/lib/cucumber/formatter/usage.rb +3 -3
- data/lib/cucumber/gherkin/data_table_parser.rb +1 -0
- data/lib/cucumber/gherkin/formatter/ansi_escapes.rb +25 -27
- data/lib/cucumber/glue/dsl.rb +20 -25
- data/lib/cucumber/glue/hook.rb +6 -3
- data/lib/cucumber/glue/invoke_in_world.rb +5 -5
- data/lib/cucumber/glue/proto_world.rb +30 -34
- data/lib/cucumber/glue/registry_and_more.rb +15 -25
- data/lib/cucumber/glue/snippet.rb +4 -2
- data/lib/cucumber/glue/step_definition.rb +6 -3
- data/lib/cucumber/glue/world_factory.rb +2 -0
- data/lib/cucumber/hooks.rb +1 -0
- data/lib/cucumber/multiline_argument/data_table/diff_matrices.rb +4 -1
- data/lib/cucumber/multiline_argument/data_table.rb +68 -80
- data/lib/cucumber/platform.rb +11 -16
- data/lib/cucumber/rake/task.rb +22 -15
- data/lib/cucumber/rspec/disable_option_parser.rb +6 -3
- data/lib/cucumber/running_test_case.rb +2 -1
- data/lib/cucumber/runtime/for_programming_languages.rb +1 -2
- data/lib/cucumber/runtime/meta_message_builder.rb +108 -0
- data/lib/cucumber/runtime/support_code.rb +3 -0
- data/lib/cucumber/runtime/user_interface.rb +7 -6
- data/lib/cucumber/runtime.rb +22 -38
- data/lib/cucumber/step_match.rb +6 -4
- data/lib/cucumber/step_match_search.rb +3 -2
- data/lib/cucumber/term/ansicolor.rb +74 -50
- data/lib/cucumber/term/banner.rb +3 -0
- data/lib/cucumber.rb +2 -1
- data/lib/simplecov_setup.rb +1 -1
- metadata +95 -244
- data/CHANGELOG.md +0 -3131
- data/CONTRIBUTING.md +0 -250
- data/lib/autotest/cucumber.rb +0 -8
- data/lib/autotest/cucumber_mixin.rb +0 -130
- data/lib/autotest/cucumber_rails.rb +0 -8
- data/lib/autotest/cucumber_rails_rspec.rb +0 -8
- data/lib/autotest/cucumber_rails_rspec2.rb +0 -8
- data/lib/autotest/cucumber_rspec.rb +0 -8
- data/lib/autotest/cucumber_rspec2.rb +0 -8
- data/lib/autotest/discover.rb +0 -13
- data/lib/cucumber/version +0 -1
@@ -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'
|
@@ -34,7 +32,7 @@ module Cucumber
|
|
34
32
|
|
35
33
|
def format_step(keyword, step_match, status, source_indent)
|
36
34
|
comment = if source_indent
|
37
|
-
c = indent(
|
35
|
+
c = indent("# #{step_match.location}", source_indent)
|
38
36
|
format_string(c, :comment)
|
39
37
|
else
|
40
38
|
''
|
@@ -45,10 +43,10 @@ module Cucumber
|
|
45
43
|
format_string(line, status)
|
46
44
|
end
|
47
45
|
|
48
|
-
def format_string(
|
46
|
+
def format_string(input, status)
|
49
47
|
fmt = format_for(status)
|
50
|
-
|
51
|
-
if
|
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,22 +92,25 @@ module Cucumber
|
|
94
92
|
@io.flush
|
95
93
|
end
|
96
94
|
|
97
|
-
def print_exception(
|
98
|
-
string = exception_message_string(
|
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(
|
103
|
-
message = "#{
|
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#{
|
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
|
112
|
-
|
109
|
+
return msg unless max.positive?
|
110
|
+
|
111
|
+
msg.gsub(/.{1,#{max}}(?:\s|\Z)/) do
|
112
|
+
(Regexp.last_match(0) + 5.chr).gsub(/\n\005/, "\n").gsub(/\005/, "\n")
|
113
|
+
end.rstrip
|
113
114
|
end
|
114
115
|
|
115
116
|
def collect_snippet_data(test_step, ast_lookup)
|
@@ -150,6 +151,7 @@ module Cucumber
|
|
150
151
|
|
151
152
|
def print_passing_wip(config, passed_test_cases, ast_lookup)
|
152
153
|
return unless config.wip?
|
154
|
+
|
153
155
|
messages = passed_test_cases.map do |test_case|
|
154
156
|
scenario_source = ast_lookup.scenario_source(test_case)
|
155
157
|
keyword = scenario_source.type == :Scenario ? scenario_source.scenario.keyword : scenario_source.scenario_outline.keyword
|
@@ -167,16 +169,23 @@ module Cucumber
|
|
167
169
|
end
|
168
170
|
end
|
169
171
|
|
170
|
-
def attach(src, media_type)
|
172
|
+
def attach(src, media_type, filename)
|
171
173
|
return unless media_type == 'text/x.cucumber.log+plain'
|
172
174
|
return unless @io
|
175
|
+
|
173
176
|
@io.puts
|
174
|
-
|
177
|
+
if filename
|
178
|
+
@io.puts("#{filename}: #{format_string(src, :tag)}")
|
179
|
+
else
|
180
|
+
@io.puts(format_string(src, :tag))
|
181
|
+
end
|
182
|
+
|
175
183
|
@io.flush
|
176
184
|
end
|
177
185
|
|
178
186
|
def print_profile_information
|
179
187
|
return if @options[:skip_profile_information] || @options[:profiles].nil? || @options[:profiles].empty?
|
188
|
+
|
180
189
|
do_print_profile_information(@options[:profiles])
|
181
190
|
end
|
182
191
|
|
@@ -224,6 +233,7 @@ module Cucumber
|
|
224
233
|
key = keys.join('_').to_sym
|
225
234
|
fmt = FORMATS[key]
|
226
235
|
raise "No format for #{key.inspect}: #{FORMATS.inspect}" if fmt.nil?
|
236
|
+
|
227
237
|
fmt
|
228
238
|
end
|
229
239
|
|
@@ -246,6 +256,7 @@ module Cucumber
|
|
246
256
|
|
247
257
|
class SnippetData
|
248
258
|
attr_reader :actual_keyword, :step
|
259
|
+
|
249
260
|
def initialize(actual_keyword, step)
|
250
261
|
@actual_keyword = actual_keyword
|
251
262
|
@step = step
|
@@ -254,4 +265,3 @@ module Cucumber
|
|
254
265
|
end
|
255
266
|
end
|
256
267
|
end
|
257
|
-
# 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
|
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
|
@@ -23,6 +25,7 @@ module Cucumber
|
|
23
25
|
|
24
26
|
def to_s
|
25
27
|
return if @issues.empty?
|
28
|
+
|
26
29
|
result = Core::Test::Result::TYPES.map { |type| scenario_listing(type, @issues[type]) }
|
27
30
|
result.flatten.join("\n")
|
28
31
|
end
|
@@ -35,6 +38,7 @@ module Cucumber
|
|
35
38
|
|
36
39
|
def scenario_listing(type, test_cases)
|
37
40
|
return [] if test_cases.empty?
|
41
|
+
|
38
42
|
[format_string("#{type_heading(type)} Scenarios:", type)] + test_cases.map do |test_case|
|
39
43
|
scenario_source = @ast_lookup.scenario_source(test_case)
|
40
44
|
keyword = scenario_source.type == :Scenario ? scenario_source.scenario.keyword : scenario_source.scenario_outline.keyword
|
@@ -54,7 +58,10 @@ module Cucumber
|
|
54
58
|
|
55
59
|
def profiles_string
|
56
60
|
return if @config.custom_profiles.empty?
|
57
|
-
|
61
|
+
|
62
|
+
profiles = @config.custom_profiles.map { |profile| "-p #{profile}" }.join(' ')
|
63
|
+
|
64
|
+
"#{profiles} "
|
58
65
|
end
|
59
66
|
end
|
60
67
|
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
|
@@ -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
|
@@ -21,7 +21,7 @@ module Cucumber
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def respond_to_missing?(name, include_private = false)
|
24
|
-
recipients.any? { |recipient| recipient.respond_to?(name, include_private) }
|
24
|
+
recipients.any? { |recipient| recipient.respond_to?(name, include_private) } || super(name, include_private)
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
@@ -1,146 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'net/http'
|
2
4
|
require 'tempfile'
|
3
|
-
|
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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
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)
|
144
17
|
end
|
145
18
|
end
|
146
19
|
end
|
@@ -57,19 +57,21 @@ module Cucumber
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def url?(path_or_url_or_io)
|
60
|
-
path_or_url_or_io.match(
|
60
|
+
path_or_url_or_io.match(/^https?:\/\//)
|
61
61
|
end
|
62
62
|
|
63
63
|
def ensure_file(path, name)
|
64
|
-
raise "You *must* specify --out FILE for the #{name} formatter" unless
|
64
|
+
raise "You *must* specify --out FILE for the #{name} formatter" unless path.instance_of? String
|
65
65
|
raise "I can't write #{name} to a directory - it has to be a file" if File.directory?(path)
|
66
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
|
+
|
67
68
|
ensure_io(path, nil)
|
68
69
|
end
|
69
70
|
|
70
71
|
def ensure_dir(path, name)
|
71
|
-
raise "You *must* specify --out DIR for the #{name} formatter" unless
|
72
|
+
raise "You *must* specify --out DIR for the #{name} formatter" unless path.instance_of? String
|
72
73
|
raise "I can't write #{name} reports to a file - it has to be a directory" if File.file?(path)
|
74
|
+
|
73
75
|
FileUtils.mkdir_p(path) unless File.directory?(path)
|
74
76
|
File.absolute_path path
|
75
77
|
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
|
@@ -68,6 +68,7 @@ module Cucumber
|
|
68
68
|
test_step, result = *event.attributes
|
69
69
|
result = result.with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter)
|
70
70
|
return if internal_hook?(test_step)
|
71
|
+
|
71
72
|
add_match_and_result(test_step, result)
|
72
73
|
@any_step_failed = true if result.failed?
|
73
74
|
end
|
@@ -81,10 +82,10 @@ module Cucumber
|
|
81
82
|
end
|
82
83
|
|
83
84
|
def on_test_run_finished(_event)
|
84
|
-
@io.write(JSON.
|
85
|
+
@io.write(JSON.pretty_generate(@feature_hashes))
|
85
86
|
end
|
86
87
|
|
87
|
-
def attach(src, mime_type)
|
88
|
+
def attach(src, mime_type, _filename)
|
88
89
|
if mime_type == 'text/x.cucumber.log+plain'
|
89
90
|
test_step_output << src
|
90
91
|
return
|
@@ -101,21 +102,17 @@ module Cucumber
|
|
101
102
|
private
|
102
103
|
|
103
104
|
def same_feature_as_previous_test_case?(test_case)
|
104
|
-
|
105
|
+
@feature_hash&.fetch(:uri, nil) == test_case.location.file
|
105
106
|
end
|
106
107
|
|
107
108
|
def first_step_after_background?(test_step)
|
108
|
-
@in_background && test_step.location.lines.max >= @test_case_hash[:line]
|
109
|
+
@in_background && test_step.location.file == @feature_hash[:uri] && test_step.location.lines.max >= @test_case_hash[:line]
|
109
110
|
end
|
110
111
|
|
111
112
|
def internal_hook?(test_step)
|
112
113
|
test_step.location.file.include?('lib/cucumber/')
|
113
114
|
end
|
114
115
|
|
115
|
-
def current_feature
|
116
|
-
@feature_hash ||= {} # rubocop:disable Naming/MemoizedInstanceVariableName
|
117
|
-
end
|
118
|
-
|
119
116
|
def feature_elements
|
120
117
|
@feature_hash[:elements] ||= []
|
121
118
|
end
|
@@ -133,7 +130,7 @@ module Cucumber
|
|
133
130
|
when 'AfterStep hook'
|
134
131
|
after_step_hooks
|
135
132
|
else
|
136
|
-
raise
|
133
|
+
raise "Unknown hook type #{hook_step}"
|
137
134
|
end
|
138
135
|
end
|
139
136
|
|
@@ -175,15 +172,15 @@ module Cucumber
|
|
175
172
|
name: test_step.text,
|
176
173
|
line: test_step.location.lines.min
|
177
174
|
}
|
178
|
-
step_hash[:doc_string] = create_doc_string_hash(step_source.doc_string) unless step_source.doc_string.nil?
|
175
|
+
step_hash[:doc_string] = create_doc_string_hash(step_source.doc_string, test_step.multiline_arg.content) unless step_source.doc_string.nil?
|
179
176
|
step_hash[:rows] = create_data_table_value(step_source.data_table) unless step_source.data_table.nil?
|
180
177
|
step_hash
|
181
178
|
end
|
182
179
|
|
183
|
-
def create_doc_string_hash(doc_string)
|
180
|
+
def create_doc_string_hash(doc_string, doc_string_content)
|
184
181
|
content_type = doc_string.media_type || ''
|
185
182
|
{
|
186
|
-
value:
|
183
|
+
value: doc_string_content,
|
187
184
|
content_type: content_type,
|
188
185
|
line: doc_string.location.line
|
189
186
|
}
|
@@ -259,6 +256,7 @@ module Cucumber
|
|
259
256
|
line: feature.location.line
|
260
257
|
}
|
261
258
|
return if feature.tags.empty?
|
259
|
+
|
262
260
|
@feature_hash[:tags] = create_tags_array_from_hash_array(feature.tags)
|
263
261
|
end
|
264
262
|
|