cucumber 4.0.0.rc.5 → 5.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 388c05be14607a5aaa4acaef66d2bb5f6dab1258bc3e785e71a077fae3d366a7
4
- data.tar.gz: 2546749fa602b12b756e0dee45ff756fc89507329504b94f633b39cd5692464e
3
+ metadata.gz: b3c9c9e718ba5a344adfb97aaebfb58f5c99bb6f8142bb74c115c9b7fe94d0f3
4
+ data.tar.gz: 3a70805fa19b0b83393e61853a6803eecc3b5cfd578a3f1e2201c258fa11b107
5
5
  SHA512:
6
- metadata.gz: ed762cc475552e7698cf9ebae51b00366f88f000fcbbbd9697bfa1056a2ec7a7a9d49bb946e0f785d8c1d435da443bd2cca834a2309a11403836dc11f93de169
7
- data.tar.gz: 112fb462eb9a3bdb1056c3ad32670ad1160ad0f40a6b804bc09b5e948443073df55a8b6c2818c0cb8f7794623b8e71f04d913a1559bb0b69536cc7bf7634873c
6
+ metadata.gz: 81ed3402348bf4882f9e8a3b43e3b926396813d844cb93c00e1bfecac15f3fb7672750e0162c01182bada9aed90aff96d1751d66fe3bcd7ff9a0bb0b10052c29
7
+ data.tar.gz: 8f13726940d23325f2f8278dd6c6ddabbbe4cec7fa3b61d7066c2c068b3c326af011bb6eca6b4a18c0cc9ffa00043bb535d2932185259f60baf51ebefbd19087
@@ -9,6 +9,113 @@ This document is formatted according to the principles of [Keep A CHANGELOG](htt
9
9
  Please visit [cucumber/CONTRIBUTING.md](https://github.com/cucumber/cucumber/blob/master/CONTRIBUTING.md) for more info on how to contribute to Cucumber.
10
10
 
11
11
  ----
12
+
13
+ ## [In GIT](https://github.com/cucumber/cucumber-ruby/compare/v5.0.0...master)
14
+
15
+ ### Added
16
+
17
+ ### Changed
18
+
19
+ ### Removed
20
+
21
+ ### Deprecated
22
+
23
+ ### Fixed
24
+
25
+ ## [5.0.0](https://github.com/cucumber/cucumber-ruby/compare/v4.1.0...5.0.0)
26
+
27
+ ### Added
28
+
29
+ * `--publish` automatically publishes reports to [reports.cucumber.io](https://reports.cucumber.io)
30
+ * `--publish-quiet` does not print information banner about [reports.cucumber.io](https://reports.cucumber.io)
31
+
32
+ ### Changed
33
+
34
+ * `-q, --quiet` will also imply `--publish-quiet` in addition to `--no-snippets --no-source --no-duration`
35
+
36
+ ### Removed
37
+
38
+ * Dropped support for Ruby [2.3](https://www.ruby-lang.org/en/news/2019/03/31/support-of-ruby-2-3-has-ended/)
39
+ and [2.4](https://www.ruby-lang.org/en/news/2020/04/05/support-of-ruby-2-4-has-ended/)
40
+
41
+ ### Fixed
42
+
43
+ * Update code to be compatible with `diff-lcs` versions 1.3 and 1.4
44
+ * Defer registration of `at_exit` hook that flushes and closes formatter streams
45
+ ([#1458](https://github.com/cucumber/cucumber-ruby/pull/1458))
46
+ * Updated gems (see git diff for details)
47
+ * `cucumber-expressions`
48
+ * `cucumber-gherkin`
49
+ * `cucumber-create-meta`
50
+ * `cucumber-messages`
51
+ * Fix issue with timestamp nanos [#1438](https://github.com/cucumber/cucumber-ruby/issues/1438)
52
+ * `cucumber-html-formatter`
53
+ * Add filtering capabilities [#1444](https://github.com/cucumber/cucumber-ruby/issues/1444)
54
+ * Fix Interceptor that was raising exception when calling `puts` on the wrapped stream ([#1445](https://github.com/cucumber/cucumber-ruby/issues/1445))
55
+
56
+ ## [4.1.0](https://github.com/cucumber/cucumber-ruby/compare/v4.0.1...v4.1.0)
57
+
58
+ ### Changed
59
+
60
+ * Use [`cucumber-create-meta`](https://rubygems.org/gems/cucumber-create-meta) to produce the `Meta` message before the run.
61
+
62
+ * Updated gems:
63
+ * `cucumber-wire` ~> 3.1.0
64
+ * `cucumber-core` ~> 7.1.0
65
+ * `cucumber-gherkin` ~> 14.0.1
66
+ * Fix issue with empty feature files [#1427](https://github.com/cucumber/cucumber-ruby/issues/1427)
67
+ * `cucumber-messages` ~> 12.2.0
68
+ * `cucumber-html-formatter` ~> 7.0.0
69
+ * Fix issue with Hook attachments [#1420](https://github.com/cucumber/cucumber-ruby/issues/1420)
70
+
71
+ ### Fixed
72
+
73
+ * `AfterStep` hook do not cause issue when running `message` formatter. [#1433](https://github.com/cucumber/cucumber-ruby/issues/1433) - [#1434](https://github.com/cucumber/cucumber-ruby/pull/1434)
74
+
75
+
76
+ ## [4.0.1](https://github.com/cucumber/cucumber-ruby/compare/v4.0.0...v4.0.1)
77
+
78
+ ### Fixed
79
+
80
+ * force reference to `diff-lcs` to 1.3 as 1.4 introduced breaking changes.
81
+
82
+ ## [4.0.0](https://github.com/cucumber/cucumber-ruby/compare/v4.0.0.rc.5...v4.0.0)
83
+
84
+ ### Changed
85
+
86
+ * `log` method can now be called with non-string objects and will run `.to_s` on them. [#1410](https://github.com/cucumber/cucumber-ruby/issues/1410)
87
+
88
+ ### Improved
89
+
90
+ * Display snippet when using undefined parameter type [#1411](https://github.com/cucumber/cucumber-ruby/issues/1411)
91
+
92
+ ## [4.0.0.rc.6](https://github.com/cucumber/cucumber-ruby/compare/v4.0.0.rc.5...4.0.0.rc.6)
93
+
94
+ ### Changed
95
+
96
+ * Code snippet for an undefined step with a Doc String will ouput `doc_string` instead of `string` in block params
97
+ ([#1401](https://github.com/cucumber/cucumber-ruby/issues/1401)
98
+ [#1402](https://github.com/cucumber/cucumber-ruby/pull/1402)
99
+ [karamosky](https://github.com/karamosky))
100
+
101
+ * Updated monorepo libraries:
102
+ - cucumber-gherkin ~> 13
103
+ - cucumber-html-formatter ~> 6
104
+ - cucumber-cucumber-expressions ~> 10
105
+
106
+ * Use `cucumber-ruby-core` 7.0.0
107
+
108
+ * Use `cucumber-ruby-wire` 3.0.0
109
+
110
+ * Use `body` field of attachments
111
+
112
+ ### Improved
113
+
114
+ * `--out url` updates:
115
+ * supports redirects
116
+ * use `PUT` method by default
117
+ * use a cURL like options (for example: `cucumber --out 'http://example.com -X POST -H Content-Type: json`)
118
+
12
119
  ## [4.0.0.rc.5](https://github.com/cucumber/cucumber-ruby/compare/v4.0.0.rc.4...4.0.0.rc.5)
13
120
 
14
121
  ### Added
@@ -53,7 +160,7 @@ Please visit [cucumber/CONTRIBUTING.md](https://github.com/cucumber/cucumber/blo
53
160
  * If you wish to alter this, then you can set a top level config option: `Cucumber.use_legacy_autoloader`
54
161
  * Like most config options, setting this inside a `spec_helper.rb` or `env.rb` file is advised
55
162
  * For more information on this change, including why it was made. Please read this
56
- [Blog Post](www.google.com)
163
+ [Blog Post](https://cucumber.io/blog/open-source/tweaking-cucumber-rubys-auto-loader/)
57
164
  ([#1349](https://github.com/cucumber/cucumber-ruby/pull/1349),
58
165
  [#1043](https://github.com/cucumber/cucumber-ruby/issues/1043)
59
166
  [luke-hill](https://github.com/luke-hill))
@@ -59,13 +59,13 @@ help us to correct style violations reported here:
59
59
 
60
60
  ## Release Process
61
61
 
62
- * Bump the version number in `lib/cucumber/version`.
63
- * Make sure `CHANGELOG.md` is updated with the upcoming version number, and has entries for all fixes.
64
-
65
- Now release it
66
-
67
- bundle update
68
- bundle exec rake
69
- git commit -m "Release X.Y.Z"
70
- # Make sure you run gem signin as the cukebot@cucumber.io user before running the following step. Credentials can be found in 1Password
71
- rake release
62
+ * Upgrade gems with `scripts/update-gemspec`
63
+ * Bump the version number in `lib/cucumber/version`
64
+ * Update `CHANGELOG.md` with the upcoming version number and create a new `In Git` section
65
+ * Remove empty sections from `CHANGELOG.md`
66
+ * Now release it:
67
+
68
+ ```
69
+ git commit -am "Release X.Y.Z"
70
+ make release
71
+ ```
@@ -24,23 +24,21 @@ module Autotest::CucumberMixin
24
24
  add_sigint_handler
25
25
 
26
26
  loop do # ^c handler
27
- begin
28
- get_to_green
29
- if tainted
30
- rerun_all_tests
31
- rerun_all_features if all_good
32
- else
33
- hook :all_good
34
- end
35
- wait_for_changes
36
- # Once tests and features are green, reset features every
37
- # time a file is changed to see if anything breaks.
38
- reset_features
39
- rescue Interrupt
40
- break if wants_to_quit
41
- reset
42
- reset_features
27
+ get_to_green
28
+ if tainted
29
+ rerun_all_tests
30
+ rerun_all_features if all_good
31
+ else
32
+ hook :all_good
43
33
  end
34
+ wait_for_changes
35
+ # Once tests and features are green, reset features every
36
+ # time a file is changed to see if anything breaks.
37
+ reset_features
38
+ rescue Interrupt
39
+ break if wants_to_quit
40
+ reset
41
+ reset_features
44
42
  end
45
43
  hook :quit
46
44
  end
@@ -9,6 +9,7 @@ require 'cucumber/core/test/result'
9
9
  module Cucumber
10
10
  module Cli
11
11
  class Options
12
+ CUCUMBER_PUBLISH_URL = ENV['CUCUMBER_PUBLISH_URL'] || 'https://messages.cucumber.io/api/reports'
12
13
  INDENT = ' ' * 53
13
14
  BUILTIN_FORMATS = {
14
15
  'pretty' => ['Cucumber::Formatter::Pretty', 'Prints the feature as is - in colours.'],
@@ -93,6 +94,10 @@ module Cucumber
93
94
 
94
95
  @args.options do |opts| # rubocop:disable Metrics/BlockLength
95
96
  opts.banner = banner
97
+ opts.on('--publish', 'Publish a report to https://reports.cucumber.io') do
98
+ set_option :publish_enabled, true
99
+ end
100
+ opts.on('--publish-quiet', 'Don\'t print information banner about publishing reports') { set_option :publish_quiet }
96
101
  opts.on('-r LIBRARY|DIR', '--require LIBRARY|DIR', *require_files_msg) { |lib| require_files(lib) }
97
102
 
98
103
  opts.on('-j DIR', '--jars DIR', 'Load all the jars under DIR') { |jars| load_jars(jars) } if Cucumber::JRUBY
@@ -117,7 +122,7 @@ module Cucumber
117
122
  opts.on('-s', '--no-source', "Don't print the file and line of the step definition with the steps.") { set_option :source, false }
118
123
  opts.on('-i', '--no-snippets', "Don't print snippets for pending steps.") { set_option :snippets, false }
119
124
  opts.on('-I', '--snippet-type TYPE', *snippet_type_msg) { |v| set_option :snippet_type, v.to_sym }
120
- opts.on('-q', '--quiet', 'Alias for --no-snippets --no-source.') { shut_up }
125
+ opts.on('-q', '--quiet', 'Alias for --no-snippets --no-source --no-duration --publish-quiet.') { shut_up }
121
126
  opts.on('--no-duration', "Don't print the duration at the end of the summary") { set_option :duration, false }
122
127
  opts.on('-b', '--backtrace', 'Show full backtrace for all errors.') { Cucumber.use_full_backtrace = true }
123
128
  opts.on('-S', '--[no-]strict', *strict_msg) { |setting| set_strict(setting) }
@@ -145,6 +150,8 @@ Specify SEED to reproduce the shuffling from a previous run.
145
150
  opts.on_tail('-h', '--help', "You're looking at it.") { exit_ok(opts.help) }
146
151
  end.parse!
147
152
 
153
+ process_publish_options
154
+
148
155
  @args.map! { |a| "#{a}:#{@options[:lines]}" } if @options[:lines]
149
156
 
150
157
  extract_environment_variables
@@ -182,6 +189,18 @@ Specify SEED to reproduce the shuffling from a previous run.
182
189
 
183
190
  private
184
191
 
192
+ def process_publish_options
193
+ @options[:publish_enabled] = true if truthy_string?(ENV['CUCUMBER_PUBLISH_ENABLED']) || ENV['CUCUMBER_PUBLISH_TOKEN']
194
+ @options[:formats] << publisher if @options[:publish_enabled]
195
+
196
+ @options[:publish_quiet] = true if truthy_string?(ENV['CUCUMBER_PUBLISH_QUIET'])
197
+ end
198
+
199
+ def truthy_string?(str)
200
+ return false if str.nil?
201
+ str !~ /^(false|no|0)$/i
202
+ end
203
+
185
204
  def color_msg
186
205
  [
187
206
  'Whether or not to use ANSI color in the output. Cucumber decides',
@@ -302,9 +321,8 @@ Specify SEED to reproduce the shuffling from a previous run.
302
321
  "formatter's docs to see whether to pass a file, dir or URL.",
303
322
  "\n",
304
323
  'When using a URL, the output of the formatter will be sent as the HTTP request body.',
305
- 'HTTP headers and request method can be set with http- prefixed query parameters,',
306
- 'for example ?http-content-type=application/json&http-method=PUT.',
307
- 'All http- prefixed query parameters will be removed from the sent query parameters.'
324
+ 'HTTP headers and request method can be set with cURL like options.',
325
+ 'Example: --out "http://example.com -X POST -H Content-Type:text/json"'
308
326
  ]
309
327
  end
310
328
 
@@ -352,6 +370,12 @@ Specify SEED to reproduce the shuffling from a previous run.
352
370
  Dir["#{jars}/**/*.jar"].each { |jar| require jar }
353
371
  end
354
372
 
373
+ def publisher
374
+ url = CUCUMBER_PUBLISH_URL
375
+ url += %( -H "Authorization: Bearer #{ENV['CUCUMBER_PUBLISH_TOKEN']}") if ENV['CUCUMBER_PUBLISH_TOKEN']
376
+ ['message', {}, url]
377
+ end
378
+
355
379
  def language(lang)
356
380
  require 'gherkin/dialect'
357
381
 
@@ -416,6 +440,7 @@ Specify SEED to reproduce the shuffling from a previous run.
416
440
  end
417
441
 
418
442
  def shut_up
443
+ @options[:publish_quiet] = true
419
444
  @options[:snippets] = false
420
445
  @options[:source] = false
421
446
  @options[:duration] = false
@@ -62,6 +62,14 @@ module Cucumber
62
62
  @options[:dry_run]
63
63
  end
64
64
 
65
+ def publish_enabled?
66
+ @options[:publish_enabled]
67
+ end
68
+
69
+ def publish_quiet?
70
+ @options[:publish_quiet]
71
+ end
72
+
65
73
  def fail_fast?
66
74
  @options[:fail_fast]
67
75
  end
@@ -197,14 +205,12 @@ module Cucumber
197
205
 
198
206
  def formatter_factories
199
207
  formats.map do |format, formatter_options, path_or_io|
200
- begin
201
- factory = formatter_class(format)
202
- yield factory,
203
- formatter_options,
204
- path_or_io
205
- rescue Exception => e # rubocop:disable Lint/RescueException
206
- raise e, "#{e.message}\nError creating formatter: #{format}", e.backtrace
207
- end
208
+ factory = formatter_class(format)
209
+ yield factory,
210
+ formatter_options,
211
+ path_or_io
212
+ rescue Exception => e # rubocop:disable Lint/RescueException
213
+ raise e, "#{e.message}\nError creating formatter: #{format}", e.backtrace
208
214
  end
209
215
  end
210
216
 
@@ -256,6 +262,7 @@ module Cucumber
256
262
  strict: Cucumber::Core::Test::Result::StrictConfiguration.new,
257
263
  require: [],
258
264
  dry_run: false,
265
+ publish_quiet: false,
259
266
  fail_fast: false,
260
267
  formats: [],
261
268
  excludes: [],
@@ -49,7 +49,4 @@ module Cucumber
49
49
  super(messages.join("\n"))
50
50
  end
51
51
  end
52
-
53
- class LogTypeInvalid < StandardError
54
- end
55
52
  end
@@ -38,7 +38,8 @@ module Cucumber
38
38
  TestStepCreated,
39
39
  TestStepFinished,
40
40
  TestStepStarted,
41
- Envelope
41
+ Envelope,
42
+ UndefinedParameterType
42
43
  )
43
44
  end
44
45
  end
@@ -0,0 +1,10 @@
1
+ require 'cucumber/core/events'
2
+
3
+ module Cucumber
4
+ module Events
5
+ class UndefinedParameterType < Core::Event.new(:type_name, :expression)
6
+ attr_reader :type_name
7
+ attr_reader :expression
8
+ end
9
+ end
10
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Metrics/ModuleLength
4
+
3
5
  require 'cucumber/formatter/ansicolor'
4
6
  require 'cucumber/formatter/duration'
5
7
  require 'cucumber/gherkin/i18n'
@@ -116,14 +118,21 @@ module Cucumber
116
118
  @snippets_input << Console::SnippetData.new(keyword, test_step)
117
119
  end
118
120
 
121
+ def collect_undefined_parameter_type_names(undefined_parameter_type)
122
+ @undefined_parameter_types << undefined_parameter_type.type_name
123
+ end
124
+
119
125
  def print_snippets(options)
120
126
  return unless options[:snippets]
121
- return if @snippets_input.empty?
122
127
 
123
128
  snippet_text_proc = lambda do |step_keyword, step_name, multiline_arg|
124
129
  snippet_text(step_keyword, step_name, multiline_arg)
125
130
  end
126
- do_print_snippets(snippet_text_proc)
131
+ do_print_snippets(snippet_text_proc) unless @snippets_input.empty?
132
+
133
+ @undefined_parameter_types.map do |type_name|
134
+ do_print_undefined_parameter_type_snippet(type_name)
135
+ end
127
136
  end
128
137
 
129
138
  def do_print_snippets(snippet_text_proc)
@@ -181,6 +190,24 @@ module Cucumber
181
190
  @io.puts "Using the #{profiles_sentence} profile#{'s' if profiles.size > 1}..."
182
191
  end
183
192
 
193
+ def do_print_undefined_parameter_type_snippet(type_name)
194
+ camelized = type_name.split(/_|-/).collect(&:capitalize).join
195
+
196
+ @io.puts [
197
+ "The parameter #{type_name} is not defined. You can define a new one with:",
198
+ '',
199
+ 'ParameterType(',
200
+ " name: '#{type_name}',",
201
+ ' regexp: /some regexp here/,',
202
+ " type: #{camelized},",
203
+ ' # The transformer takes as many arguments as there are capture groups in the regexp,',
204
+ ' # or just one if there are none.',
205
+ " transformer: ->(s) { #{camelized}.new(s) }",
206
+ ')',
207
+ ''
208
+ ].join("\n")
209
+ end
210
+
184
211
  private
185
212
 
186
213
  FORMATS = Hash.new { |hash, format| hash[format] = method(format).to_proc }
@@ -219,3 +246,4 @@ module Cucumber
219
246
  end
220
247
  end
221
248
  end
249
+ # rubocop:enable Metrics/ModuleLength
@@ -1,92 +1,150 @@
1
1
  require 'net/http'
2
+ require 'tempfile'
2
3
 
3
4
  module Cucumber
4
5
  module Formatter
5
6
  class HTTPIO
6
7
  class << self
7
8
  # Returns an IO that will write to a HTTP request's body
8
- def open(url, https_verify_mode = nil)
9
+ # https_verify_mode can be set to OpenSSL::SSL::VERIFY_NONE
10
+ # to ignore unsigned certificate - setting to nil will verify the certificate
11
+ def open(url, https_verify_mode = nil, reporter = nil)
9
12
  @https_verify_mode = https_verify_mode
10
- uri, method, headers = build_uri_method_headers(url)
11
-
12
- @req = build_request(uri, method, headers)
13
- @http = build_client(uri, https_verify_mode)
14
-
15
- read_io, write_io = IO.pipe
16
- @req.body_stream = read_io
17
-
18
- class << write_io
19
- attr_writer :request_thread
20
-
21
- def start_request(http, req)
22
- @req_thread = Thread.new do
23
- begin
24
- res = http.request(req)
25
- raise StandardError, "request to #{req.uri} failed with status #{res.code}" if res.code.to_i >= 400
26
- rescue StandardError => e
27
- @http_error = e
28
- end
29
- end
30
- end
31
-
32
- def close
33
- super
34
- begin
35
- @req_thread.join
36
- rescue StandardError
37
- nil
38
- end
39
- raise @http_error unless @http_error.nil?
40
- end
13
+ uri, method, headers = CurlOptionParser.parse(url)
14
+ IOHTTPBuffer.new(uri, method, headers, https_verify_mode, reporter)
15
+ end
16
+ end
17
+ end
18
+
19
+ class CurlOptionParser
20
+ def self.parse(options)
21
+ chunks = options.split(/\s/).compact
22
+ http_method = 'PUT'
23
+ url = chunks[0]
24
+ headers = ''
25
+
26
+ last_flag = nil
27
+ chunks.each do |chunk|
28
+ if ['-X', '--request'].include?(chunk)
29
+ last_flag = '-X'
30
+ next
41
31
  end
42
- write_io.start_request(@http, @req)
43
32
 
44
- write_io
33
+ if chunk == '-H'
34
+ last_flag = '-H'
35
+ next
36
+ end
37
+
38
+ if last_flag == '-X'
39
+ http_method = chunk
40
+ last_flag = nil
41
+ end
42
+
43
+ headers += chunk if last_flag == '-H'
45
44
  end
46
45
 
47
- def build_uri_method_headers(url)
48
- uri = URI(url)
49
- query_pairs = uri.query ? URI.decode_www_form(uri.query) : []
50
-
51
- # Build headers from query parameters prefixed with http- and extract HTTP method
52
- http_query_pairs = query_pairs.select { |pair| pair[0] =~ /^http-/ }
53
- http_query_hash_without_prefix = Hash[http_query_pairs.map do |pair|
54
- [
55
- pair[0][5..-1].downcase, # remove http- prefix
56
- pair[1]
57
- ]
58
- end]
59
- method = http_query_hash_without_prefix.delete('method') || 'POST'
60
- headers = {
61
- 'transfer-encoding' => 'chunked'
62
- }.merge(http_query_hash_without_prefix)
63
-
64
- # Update the query with the http-* parameters removed
65
- remaining_query_pairs = query_pairs - http_query_pairs
66
- new_query_hash = Hash[remaining_query_pairs]
67
- uri.query = URI.encode_www_form(new_query_hash) unless new_query_hash.empty?
68
- [uri, method, headers]
46
+ [
47
+ url,
48
+ http_method,
49
+ make_headers(headers)
50
+ ]
51
+ end
52
+
53
+ def self.make_headers(headers)
54
+ hash_headers = {}
55
+ str_scanner = /("(?<key>[^":]+)\s*:\s*(?<value>[^":]+)")|('(?<key1>[^':]+)\s*:\s*(?<value1>[^':]+)')/
56
+
57
+ headers.scan(str_scanner) do |header|
58
+ header = header.compact!
59
+ hash_headers[header[0]] = header[1]&.strip
69
60
  end
70
61
 
71
- private
62
+ hash_headers
63
+ end
64
+ end
72
65
 
73
- def build_request(uri, method, headers)
74
- method_class_name = "#{method[0].upcase}#{method[1..-1].downcase}"
75
- req = Net::HTTP.const_get(method_class_name).new(uri)
76
- headers.each do |header, value|
77
- req[header] = value
78
- end
79
- req
66
+ class IOHTTPBuffer
67
+ attr_reader :uri, :method, :headers
68
+
69
+ def initialize(uri, method, headers = {}, https_verify_mode = nil, reporter = nil)
70
+ @uri = URI(uri)
71
+ @method = method
72
+ @headers = headers
73
+ @write_io = Tempfile.new('cucumber', encoding: 'UTF-8')
74
+ @https_verify_mode = https_verify_mode
75
+ @reporter = reporter || NoReporter.new
76
+ end
77
+
78
+ def close
79
+ resource_uri = send_content(@uri, @method, @headers)
80
+
81
+ @reporter.report(resource_uri)
82
+ @write_io.close
83
+ end
84
+
85
+ def write(data)
86
+ @write_io.write(data)
87
+ end
88
+
89
+ def flush
90
+ @write_io.flush
91
+ end
92
+
93
+ def closed?
94
+ @write_io.closed?
95
+ end
96
+
97
+ private
98
+
99
+ def send_content(uri, method, headers, attempt = 10)
100
+ content = @write_io
101
+ http = build_client(uri, @https_verify_mode)
102
+
103
+ raise StandardError, "request to #{uri} failed (too many redirections)" if attempt <= 0
104
+ req = build_request(
105
+ uri,
106
+ method,
107
+ headers.merge(
108
+ 'Content-Length' => content.size.to_s
109
+ )
110
+ )
111
+
112
+ content.rewind
113
+ req.body_stream = content
114
+
115
+ begin
116
+ response = http.request(req)
117
+ rescue SystemCallError
118
+ # We may get the redirect response before pushing the file.
119
+ response = http.request(build_request(uri, method, headers))
80
120
  end
81
121
 
82
- def build_client(uri, https_verify_mode)
83
- http = Net::HTTP.new(uri.hostname, uri.port)
84
- if uri.scheme == 'https'
85
- http.use_ssl = true
86
- http.verify_mode = https_verify_mode if https_verify_mode
87
- end
88
- http
122
+ case response
123
+ when Net::HTTPSuccess
124
+ uri
125
+ when Net::HTTPRedirection
126
+ send_content(URI(response['Location']), method, headers, attempt - 1)
127
+ else
128
+ raise StandardError, "request to #{uri} failed with status #{response.code}"
129
+ end
130
+ end
131
+
132
+ def build_request(uri, method, headers)
133
+ method_class_name = "#{method[0].upcase}#{method[1..-1].downcase}"
134
+ req = Net::HTTP.const_get(method_class_name).new(uri)
135
+ headers.each do |header, value|
136
+ req[header] = value
137
+ end
138
+ req
139
+ end
140
+
141
+ def build_client(uri, https_verify_mode)
142
+ http = Net::HTTP.new(uri.hostname, uri.port)
143
+ if uri.scheme == 'https'
144
+ http.use_ssl = true
145
+ http.verify_mode = https_verify_mode if https_verify_mode
89
146
  end
147
+ http
90
148
  end
91
149
  end
92
150
  end