faraday 0.9.1 → 0.17.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +212 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +203 -28
  5. data/Rakefile +6 -64
  6. data/lib/faraday/adapter/em_http.rb +17 -11
  7. data/lib/faraday/adapter/em_http_ssl_patch.rb +1 -1
  8. data/lib/faraday/adapter/em_synchrony.rb +19 -5
  9. data/lib/faraday/adapter/excon.rb +13 -11
  10. data/lib/faraday/adapter/httpclient.rb +31 -9
  11. data/lib/faraday/adapter/net_http.rb +37 -14
  12. data/lib/faraday/adapter/net_http_persistent.rb +37 -17
  13. data/lib/faraday/adapter/patron.rb +44 -21
  14. data/lib/faraday/adapter/rack.rb +1 -1
  15. data/lib/faraday/adapter/test.rb +79 -28
  16. data/lib/faraday/adapter/typhoeus.rb +4 -115
  17. data/lib/faraday/adapter.rb +10 -1
  18. data/lib/faraday/autoload.rb +1 -1
  19. data/lib/faraday/connection.rb +72 -20
  20. data/lib/faraday/deprecate.rb +109 -0
  21. data/lib/faraday/error.rb +132 -27
  22. data/lib/faraday/options.rb +45 -22
  23. data/lib/faraday/parameters.rb +56 -39
  24. data/lib/faraday/rack_builder.rb +29 -4
  25. data/lib/faraday/request/authorization.rb +1 -2
  26. data/lib/faraday/request/multipart.rb +7 -2
  27. data/lib/faraday/request/retry.rb +84 -19
  28. data/lib/faraday/request.rb +22 -0
  29. data/lib/faraday/response/logger.rb +29 -8
  30. data/lib/faraday/response/raise_error.rb +7 -3
  31. data/lib/faraday/response.rb +9 -5
  32. data/lib/faraday/utils.rb +32 -3
  33. data/lib/faraday.rb +15 -36
  34. data/spec/faraday/deprecate_spec.rb +147 -0
  35. data/spec/faraday/error_spec.rb +102 -0
  36. data/spec/faraday/response/raise_error_spec.rb +106 -0
  37. data/spec/spec_helper.rb +105 -0
  38. data/test/adapters/em_http_test.rb +10 -0
  39. data/test/adapters/em_synchrony_test.rb +22 -10
  40. data/test/adapters/excon_test.rb +10 -0
  41. data/test/adapters/httpclient_test.rb +14 -1
  42. data/test/adapters/integration.rb +17 -8
  43. data/test/adapters/logger_test.rb +65 -11
  44. data/test/adapters/net_http_persistent_test.rb +96 -2
  45. data/test/adapters/net_http_test.rb +67 -2
  46. data/test/adapters/patron_test.rb +28 -8
  47. data/test/adapters/rack_test.rb +8 -1
  48. data/test/adapters/test_middleware_test.rb +46 -3
  49. data/test/adapters/typhoeus_test.rb +19 -9
  50. data/test/composite_read_io_test.rb +16 -18
  51. data/test/connection_test.rb +294 -78
  52. data/test/env_test.rb +55 -5
  53. data/test/helper.rb +11 -17
  54. data/test/middleware/retry_test.rb +115 -10
  55. data/test/middleware_stack_test.rb +97 -10
  56. data/test/options_test.rb +97 -16
  57. data/test/parameters_test.rb +94 -1
  58. data/test/request_middleware_test.rb +24 -40
  59. data/test/response_middleware_test.rb +4 -4
  60. data/test/utils_test.rb +40 -0
  61. metadata +21 -66
  62. data/.document +0 -6
  63. data/CONTRIBUTING.md +0 -36
  64. data/Gemfile +0 -25
  65. data/faraday.gemspec +0 -34
  66. data/script/cached-bundle +0 -46
  67. data/script/console +0 -7
  68. data/script/generate_certs +0 -42
  69. data/script/package +0 -7
  70. data/script/proxy-server +0 -42
  71. data/script/release +0 -17
  72. data/script/s3-put +0 -71
  73. data/script/server +0 -36
  74. data/script/test +0 -172
@@ -1,3 +1,5 @@
1
+ require 'date'
2
+
1
3
  module Faraday
2
4
  # Catches exceptions and retries each request a limited number of times.
3
5
  #
@@ -10,7 +12,7 @@ module Faraday
10
12
  #
11
13
  # Faraday.new do |conn|
12
14
  # conn.request :retry, max: 2, interval: 0.05,
13
- # interval_randomness: 0.5, backoff_factor: 2
15
+ # interval_randomness: 0.5, backoff_factor: 2,
14
16
  # exceptions: [CustomException, 'Timeout::Error']
15
17
  # conn.adapter ...
16
18
  # end
@@ -19,15 +21,19 @@ module Faraday
19
21
  # interval that is random between 0.1 and 0.15
20
22
  #
21
23
  class Request::Retry < Faraday::Middleware
22
-
24
+ DEFAULT_EXCEPTIONS = [Errno::ETIMEDOUT, 'Timeout::Error',
25
+ Faraday::TimeoutError, Faraday::RetriableResponse
26
+ ].freeze
23
27
  IDEMPOTENT_METHODS = [:delete, :get, :head, :options, :put]
24
28
 
25
- class Options < Faraday::Options.new(:max, :interval, :interval_randomness, :backoff_factor,
26
- :exceptions, :methods, :retry_if)
29
+ class Options < Faraday::Options.new(:max, :interval, :max_interval, :interval_randomness,
30
+ :backoff_factor, :exceptions, :methods, :retry_if, :retry_block,
31
+ :retry_statuses)
32
+
27
33
  DEFAULT_CHECK = lambda { |env,exception| false }
28
34
 
29
35
  def self.from(value)
30
- if Fixnum === value
36
+ if Integer === value
31
37
  new(value)
32
38
  else
33
39
  super(value)
@@ -42,8 +48,12 @@ module Faraday
42
48
  (self[:interval] ||= 0).to_f
43
49
  end
44
50
 
51
+ def max_interval
52
+ (self[:max_interval] ||= Float::MAX).to_f
53
+ end
54
+
45
55
  def interval_randomness
46
- (self[:interval_randomness] ||= 0).to_i
56
+ (self[:interval_randomness] ||= 0).to_f
47
57
  end
48
58
 
49
59
  def backoff_factor
@@ -51,8 +61,7 @@ module Faraday
51
61
  end
52
62
 
53
63
  def exceptions
54
- Array(self[:exceptions] ||= [Errno::ETIMEDOUT, 'Timeout::Error',
55
- Error::TimeoutError])
64
+ Array(self[:exceptions] ||= DEFAULT_EXCEPTIONS)
56
65
  end
57
66
 
58
67
  def methods
@@ -63,6 +72,13 @@ module Faraday
63
72
  self[:retry_if] ||= DEFAULT_CHECK
64
73
  end
65
74
 
75
+ def retry_block
76
+ self[:retry_block] ||= Proc.new {}
77
+ end
78
+
79
+ def retry_statuses
80
+ Array(self[:retry_statuses] ||= [])
81
+ end
66
82
  end
67
83
 
68
84
  # Public: Initialize middleware
@@ -73,13 +89,14 @@ module Faraday
73
89
  # interval_randomness - The maximum random interval amount expressed
74
90
  # as a float between 0 and 1 to use in addition to the
75
91
  # interval. (default: 0)
92
+ # max_interval - An upper limit for the interval (default: Float::MAX)
76
93
  # backoff_factor - The amount to multiple each successive retry's
77
94
  # interval amount by in order to provide backoff
78
95
  # (default: 1)
79
96
  # exceptions - The list of exceptions to handle. Exceptions can be
80
97
  # given as Class, Module, or String. (default:
81
- # [Errno::ETIMEDOUT, Timeout::Error,
82
- # Error::TimeoutError])
98
+ # [Errno::ETIMEDOUT, 'Timeout::Error',
99
+ # Faraday::TimeoutError, Faraday::RetriableResponse])
83
100
  # methods - A list of HTTP methods to retry without calling retry_if. Pass
84
101
  # an empty Array to call retry_if for all exceptions.
85
102
  # (defaults to the idempotent HTTP methods in IDEMPOTENT_METHODS)
@@ -89,17 +106,21 @@ module Faraday
89
106
  # if the exception produced is non-recoverable or if the
90
107
  # the HTTP method called is not idempotent.
91
108
  # (defaults to return false)
109
+ # retry_block - block that is executed after every retry. Request environment, middleware options,
110
+ # current number of retries and the exception is passed to the block as parameters.
92
111
  def initialize(app, options = nil)
93
112
  super(app)
94
113
  @options = Options.from(options)
95
114
  @errmatch = build_exception_matcher(@options.exceptions)
96
115
  end
97
116
 
98
- def sleep_amount(retries)
99
- retry_index = @options.max - retries
100
- current_interval = @options.interval * (@options.backoff_factor ** retry_index)
101
- random_interval = rand * @options.interval_randomness.to_f * @options.interval
102
- current_interval + random_interval
117
+ def calculate_sleep_amount(retries, env)
118
+ retry_after = calculate_retry_after(env)
119
+ retry_interval = calculate_retry_interval(retries)
120
+
121
+ return if retry_after && retry_after > @options.max_interval
122
+
123
+ retry_after && retry_after >= retry_interval ? retry_after : retry_interval
103
124
  end
104
125
 
105
126
  def call(env)
@@ -107,14 +128,25 @@ module Faraday
107
128
  request_body = env[:body]
108
129
  begin
109
130
  env[:body] = request_body # after failure env[:body] is set to the response body
110
- @app.call(env)
131
+ @app.call(env).tap do |resp|
132
+ raise Faraday::RetriableResponse.new(nil, resp) if @options.retry_statuses.include?(resp.status)
133
+ end
111
134
  rescue @errmatch => exception
112
135
  if retries > 0 && retry_request?(env, exception)
113
136
  retries -= 1
114
- sleep sleep_amount(retries + 1)
115
- retry
137
+ rewind_files(request_body)
138
+ @options.retry_block.call(env, @options, retries, exception)
139
+ if (sleep_amount = calculate_sleep_amount(retries + 1, env))
140
+ sleep sleep_amount
141
+ retry
142
+ end
143
+ end
144
+
145
+ if exception.is_a?(Faraday::RetriableResponse)
146
+ exception.response
147
+ else
148
+ raise
116
149
  end
117
- raise
118
150
  end
119
151
  end
120
152
 
@@ -144,5 +176,38 @@ module Faraday
144
176
  @options.methods.include?(env[:method]) || @options.retry_if.call(env, exception)
145
177
  end
146
178
 
179
+ def rewind_files(body)
180
+ return unless body.is_a?(Hash)
181
+ body.each do |_, value|
182
+ if value.is_a? UploadIO
183
+ value.rewind
184
+ end
185
+ end
186
+ end
187
+
188
+ # MDN spec for Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
189
+ def calculate_retry_after(env)
190
+ response_headers = env[:response_headers]
191
+ return unless response_headers
192
+
193
+ retry_after_value = env[:response_headers]["Retry-After"]
194
+
195
+ # Try to parse date from the header value
196
+ begin
197
+ datetime = DateTime.rfc2822(retry_after_value)
198
+ datetime.to_time - Time.now.utc
199
+ rescue ArgumentError
200
+ retry_after_value.to_f
201
+ end
202
+ end
203
+
204
+ def calculate_retry_interval(retries)
205
+ retry_index = @options.max - retries
206
+ current_interval = @options.interval * (@options.backoff_factor ** retry_index)
207
+ current_interval = [current_interval, @options.max_interval].min
208
+ random_interval = rand * @options.interval_randomness.to_f * @options.interval
209
+
210
+ current_interval + random_interval
211
+ end
147
212
  end
148
213
  end
@@ -52,6 +52,8 @@ module Faraday
52
52
  path.query = nil
53
53
  end
54
54
  else
55
+ anchor_index = path.index('#')
56
+ path = path.slice(0, anchor_index) unless anchor_index.nil?
55
57
  path, query = path.split('?', 2)
56
58
  end
57
59
  self.path = path
@@ -67,6 +69,26 @@ module Faraday
67
69
  headers[key] = value
68
70
  end
69
71
 
72
+ def marshal_dump
73
+ {
74
+ :method => method,
75
+ :body => body,
76
+ :headers => headers,
77
+ :path => path,
78
+ :params => params,
79
+ :options => options
80
+ }
81
+ end
82
+
83
+ def marshal_load(serialised)
84
+ self.method = serialised[:method]
85
+ self.body = serialised[:body]
86
+ self.headers = serialised[:headers]
87
+ self.path = serialised[:path]
88
+ self.params = serialised[:params]
89
+ self.options = serialised[:options]
90
+ end
91
+
70
92
  # ENV Keys
71
93
  # :method - a symbolized request method (:get, :post)
72
94
  # :body - the request body that will eventually be converted to a string.
@@ -4,30 +4,36 @@ module Faraday
4
4
  class Response::Logger < Response::Middleware
5
5
  extend Forwardable
6
6
 
7
- DEFAULT_OPTIONS = { :bodies => false }
7
+ DEFAULT_OPTIONS = { :headers => true, :bodies => false }
8
8
 
9
9
  def initialize(app, logger = nil, options = {})
10
10
  super(app)
11
11
  @logger = logger || begin
12
12
  require 'logger'
13
- ::Logger.new(STDOUT)
13
+ ::Logger.new($stdout)
14
14
  end
15
+ @filter = []
15
16
  @options = DEFAULT_OPTIONS.merge(options)
17
+ yield self if block_given?
16
18
  end
17
19
 
18
20
  def_delegators :@logger, :debug, :info, :warn, :error, :fatal
19
21
 
20
22
  def call(env)
21
- info "#{env.method} #{env.url.to_s}"
22
- debug('request') { dump_headers env.request_headers }
23
- debug('request') { dump_body(env[:body]) } if env[:body] && log_body?(:request)
23
+ info('request') { "#{env.method.upcase} #{apply_filters(env.url.to_s)}" }
24
+ debug('request') { apply_filters( dump_headers env.request_headers ) } if log_headers?(:request)
25
+ debug('request') { apply_filters( dump_body(env[:body]) ) } if env[:body] && log_body?(:request)
24
26
  super
25
27
  end
26
28
 
27
29
  def on_complete(env)
28
- info('Status') { env.status.to_s }
29
- debug('response') { dump_headers env.response_headers }
30
- debug('response') { dump_body env[:body] } if env[:body] && log_body?(:response)
30
+ info('response') { "Status #{env.status.to_s}" }
31
+ debug('response') { apply_filters( dump_headers env.response_headers ) } if log_headers?(:response)
32
+ debug('response') { apply_filters( dump_body env[:body] ) } if env[:body] && log_body?(:response)
33
+ end
34
+
35
+ def filter(filter_word, filter_replacement)
36
+ @filter.push([ filter_word, filter_replacement ])
31
37
  end
32
38
 
33
39
  private
@@ -49,11 +55,26 @@ module Faraday
49
55
  body.pretty_inspect
50
56
  end
51
57
 
58
+ def log_headers?(type)
59
+ case @options[:headers]
60
+ when Hash then @options[:headers][type]
61
+ else @options[:headers]
62
+ end
63
+ end
64
+
52
65
  def log_body?(type)
53
66
  case @options[:bodies]
54
67
  when Hash then @options[:bodies][type]
55
68
  else @options[:bodies]
56
69
  end
57
70
  end
71
+
72
+ def apply_filters(output)
73
+ @filter.each do |pattern, replacement|
74
+ output = output.to_s.gsub(pattern, replacement)
75
+ end
76
+ output
77
+ end
78
+
58
79
  end
59
80
  end
@@ -5,12 +5,16 @@ module Faraday
5
5
  def on_complete(env)
6
6
  case env[:status]
7
7
  when 404
8
- raise Faraday::Error::ResourceNotFound, response_values(env)
8
+ raise Faraday::ResourceNotFound, response_values(env)
9
9
  when 407
10
10
  # mimic the behavior that we get with proxy requests with HTTPS
11
- raise Faraday::Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
11
+ raise Faraday::ConnectionFailed.new(
12
+ %{407 "Proxy Authentication Required "},
13
+ response_values(env))
12
14
  when ClientErrorStatuses
13
- raise Faraday::Error::ClientError, response_values(env)
15
+ raise Faraday::ClientError, response_values(env)
16
+ when nil
17
+ raise Faraday::NilStatusError, response_values(env)
14
18
  end
15
19
  end
16
20
 
@@ -37,6 +37,10 @@ module Faraday
37
37
  finished? ? env.status : nil
38
38
  end
39
39
 
40
+ def reason_phrase
41
+ finished? ? env.reason_phrase : nil
42
+ end
43
+
40
44
  def headers
41
45
  finished? ? env.response_headers : {}
42
46
  end
@@ -50,9 +54,9 @@ module Faraday
50
54
  !!env
51
55
  end
52
56
 
53
- def on_complete
54
- if not finished?
55
- @on_complete_callbacks << Proc.new
57
+ def on_complete(&block)
58
+ if !finished?
59
+ @on_complete_callbacks << block
56
60
  else
57
61
  yield(env)
58
62
  end
@@ -61,8 +65,8 @@ module Faraday
61
65
 
62
66
  def finish(env)
63
67
  raise "response already finished" if finished?
64
- @on_complete_callbacks.each { |callback| callback.call(env) }
65
- @env = Env.from(env)
68
+ @env = env.is_a?(Env) ? env : Env.from(env)
69
+ @on_complete_callbacks.each { |callback| callback.call(@env) }
66
70
  return self
67
71
  end
68
72
 
data/lib/faraday/utils.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'thread'
2
- Faraday.require_libs 'parameters'
3
2
 
4
3
  module Faraday
5
4
  module Utils
@@ -11,12 +10,28 @@ module Faraday
11
10
  new(value)
12
11
  end
13
12
 
13
+ def self.allocate
14
+ new_self = super
15
+ new_self.initialize_names
16
+ new_self
17
+ end
18
+
14
19
  def initialize(hash = nil)
15
20
  super()
16
21
  @names = {}
17
22
  self.update(hash || {})
18
23
  end
19
24
 
25
+ def initialize_names
26
+ @names = {}
27
+ end
28
+
29
+ # on dup/clone, we need to duplicate @names hash
30
+ def initialize_copy(other)
31
+ super
32
+ @names = other.names.dup
33
+ end
34
+
20
35
  # need to synchronize concurrent writes to the shared KeyMap
21
36
  keymap_mutex = Mutex.new
22
37
 
@@ -81,6 +96,7 @@ module Faraday
81
96
 
82
97
  def replace(other)
83
98
  clear
99
+ @names.clear
84
100
  self.update other
85
101
  self
86
102
  end
@@ -89,9 +105,16 @@ module Faraday
89
105
 
90
106
  def parse(header_string)
91
107
  return unless header_string && !header_string.empty?
92
- header_string.split(/\r\n/).
108
+
109
+ headers = header_string.split(/\r\n/)
110
+
111
+ # Find the last set of response headers.
112
+ start_index = headers.rindex { |x| x.match(/^HTTP\//) } || 0
113
+ last_response = headers.slice(start_index, headers.size)
114
+
115
+ last_response.
93
116
  tap { |a| a.shift if a.first.index('HTTP/') == 0 }. # drop the HTTP status line
94
- map { |h| h.split(/:\s+/, 2) }.reject { |p| p[0].nil? }. # split key and value, ignore blank lines
117
+ map { |h| h.split(/:\s*/, 2) }.reject { |p| p[0].nil? }. # split key and value, ignore blank lines
95
118
  each { |key, value|
96
119
  # join multiple values with a comma
97
120
  if self[key]
@@ -101,6 +124,12 @@ module Faraday
101
124
  end
102
125
  }
103
126
  end
127
+
128
+ protected
129
+
130
+ def names
131
+ @names
132
+ end
104
133
  end
105
134
 
106
135
  # hash with stringified keys
data/lib/faraday.rb CHANGED
@@ -14,7 +14,7 @@ require 'forwardable'
14
14
  # conn.get '/'
15
15
  #
16
16
  module Faraday
17
- VERSION = "0.9.1"
17
+ VERSION = "0.17.4"
18
18
 
19
19
  class << self
20
20
  # Public: Gets or sets the root path that Faraday is being loaded from.
@@ -34,8 +34,8 @@ module Faraday
34
34
  # Faraday.get "https://faraday.com"
35
35
  attr_writer :default_connection
36
36
 
37
- # Public: Sets the default options used when calling Faraday#new.
38
- attr_writer :default_connection_options
37
+ # Public: Tells faraday to ignore the environment proxy (http_proxy).
38
+ attr_accessor :ignore_env_proxy
39
39
 
40
40
  # Public: Initializes a new Faraday::Connection.
41
41
  #
@@ -64,9 +64,8 @@ module Faraday
64
64
  # :params => {:page => 1}
65
65
  #
66
66
  # Returns a Faraday::Connection.
67
- def new(url = nil, options = nil)
68
- block = block_given? ? Proc.new : nil
69
- options = options ? default_connection_options.merge(options) : default_connection_options.dup
67
+ def new(url = nil, options = nil, &block)
68
+ options = options ? default_connection_options.merge(options) : default_connection_options
70
69
  Faraday::Connection.new(url, options, &block)
71
70
  end
72
71
 
@@ -92,6 +91,10 @@ module Faraday
92
91
 
93
92
  alias require_lib require_libs
94
93
 
94
+ def respond_to?(symbol, include_private = false)
95
+ default_connection.respond_to?(symbol, include_private) || super
96
+ end
97
+
95
98
  private
96
99
  # Internal: Proxies method calls on the Faraday constant to
97
100
  # #default_connection.
@@ -100,6 +103,7 @@ module Faraday
100
103
  end
101
104
  end
102
105
 
106
+ self.ignore_env_proxy = false
103
107
  self.root_path = File.expand_path "..", __FILE__
104
108
  self.lib_path = File.expand_path "../faraday", __FILE__
105
109
  self.default_adapter = :net_http
@@ -108,7 +112,7 @@ module Faraday
108
112
  #
109
113
  # Returns a Faraday::Connection, configured with the #default_adapter.
110
114
  def self.default_connection
111
- @default_connection ||= Connection.new
115
+ @default_connection ||= Connection.new(default_connection_options)
112
116
  end
113
117
 
114
118
  # Gets the default connection options used when calling Faraday#new.
@@ -118,13 +122,10 @@ module Faraday
118
122
  @default_connection_options ||= ConnectionOptions.new
119
123
  end
120
124
 
121
- if (!defined?(RUBY_ENGINE) || "ruby" == RUBY_ENGINE) && RUBY_VERSION < '1.9'
122
- begin
123
- require 'system_timer'
124
- Timer = SystemTimer
125
- rescue LoadError
126
- warn "Faraday: you may want to install system_timer for reliable timeouts"
127
- end
125
+ # Public: Sets the default options used when calling Faraday#new.
126
+ def self.default_connection_options=(options)
127
+ @default_connection = nil
128
+ @default_connection_options = ConnectionOptions.from(options)
128
129
  end
129
130
 
130
131
  unless const_defined? :Timer
@@ -244,25 +245,3 @@ module Faraday
244
245
  require_lib 'autoload'
245
246
  end
246
247
  end
247
-
248
- # not pulling in active-support JUST for this method. And I love this method.
249
- class Object
250
- # The primary purpose of this method is to "tap into" a method chain,
251
- # in order to perform operations on intermediate results within the chain.
252
- #
253
- # Examples
254
- #
255
- # (1..10).tap { |x| puts "original: #{x.inspect}" }.to_a.
256
- # tap { |x| puts "array: #{x.inspect}" }.
257
- # select { |x| x%2 == 0 }.
258
- # tap { |x| puts "evens: #{x.inspect}" }.
259
- # map { |x| x*x }.
260
- # tap { |x| puts "squares: #{x.inspect}" }
261
- #
262
- # Yields self.
263
- # Returns self.
264
- def tap
265
- yield(self)
266
- self
267
- end unless Object.respond_to?(:tap)
268
- end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Faraday::DeprecatedClass do
4
+ class SampleClass < StandardError
5
+ attr_accessor :foo
6
+
7
+ def initialize(foo = nil)
8
+ @foo = foo || :foo
9
+ end
10
+ end
11
+
12
+ SampleDeprecatedClass = Faraday::DeprecatedClass.proxy_class(SampleClass)
13
+
14
+ it 'does not raise error for deprecated classes but prints an error message' do
15
+ error_message, foobar = with_warn_squelching { SampleDeprecatedClass.new(:foo_bar) }
16
+ expect(foobar).to be_a(SampleClass)
17
+ expect(foobar.foo).to eq(:foo_bar)
18
+ expect(error_message).to match(
19
+ Regexp.new(
20
+ 'NOTE: SampleDeprecatedClass.new is deprecated; '\
21
+ 'use SampleClass.new instead. It will be removed in or after version 1.0'
22
+ )
23
+ )
24
+ end
25
+
26
+ it 'does not raise an error for inherited error-namespaced classes but prints an error message' do
27
+ error_message, = with_warn_squelching { Class.new(SampleDeprecatedClass) }
28
+
29
+ expect(error_message).to match(
30
+ Regexp.new(
31
+ 'NOTE: Inheriting SampleDeprecatedClass is deprecated; '\
32
+ 'use SampleClass instead. It will be removed in or after version 1.0'
33
+ )
34
+ )
35
+ end
36
+
37
+ it 'allows backward-compatible class to be subclassed' do
38
+ expect {
39
+ with_warn_squelching { Class.new(SampleDeprecatedClass) }
40
+ }.not_to raise_error
41
+ end
42
+
43
+ it 'allows rescuing of a current error with a deprecated error' do
44
+ expect { raise SampleClass, nil }.to raise_error(SampleDeprecatedClass)
45
+ end
46
+
47
+ it 'allows rescuing of a current error with a current error' do
48
+ expect { raise SampleClass, nil }.to raise_error(SampleClass)
49
+ end
50
+
51
+ it 'allows rescuing of a deprecated error with a deprecated error' do
52
+ expect { raise SampleDeprecatedClass, nil }.to raise_error(SampleDeprecatedClass)
53
+ end
54
+
55
+ it 'allows rescuing of a deprecated error with a current error' do
56
+ expect { raise SampleDeprecatedClass, nil }.to raise_error(SampleClass)
57
+ end
58
+
59
+ describe 'match behavior' do
60
+ class SampleDeprecatedClassA < SampleDeprecatedClass; end
61
+ class SampleDeprecatedClassB < SampleDeprecatedClass; end
62
+
63
+ class SampleDeprecatedClassAX < SampleDeprecatedClassA; end
64
+
65
+ class SampleClassA < SampleClass; end
66
+
67
+ describe 'undeprecated class' do
68
+ it 'is === to instance of deprecated class' do
69
+ expect(SampleClass === SampleDeprecatedClass.new).to be true
70
+ end
71
+
72
+ it 'is === to instance of subclass of deprecated class' do
73
+ expect(SampleClass === SampleDeprecatedClassA.new).to be true
74
+ end
75
+
76
+ it 'is === to instance of subclass of subclass of deprecated class' do
77
+ expect(SampleClass === SampleDeprecatedClassAX.new).to be true
78
+ end
79
+ end
80
+
81
+ describe 'subclass of undeprecated class' do
82
+ it 'is not === to instance of undeprecated class' do
83
+ expect(SampleClassA === SampleClass.new).to be false
84
+ end
85
+
86
+ it 'is not === to instance of deprecated class' do
87
+ expect(SampleClassA === SampleDeprecatedClass.new).to be false
88
+ end
89
+ end
90
+
91
+ describe 'deprecated class' do
92
+ it 'is === to instance of undeprecated class' do
93
+ expect(SampleDeprecatedClass === SampleClass.new).to be true
94
+ end
95
+
96
+ it 'is === to instance of subclass of undeprecated class' do
97
+ expect(SampleDeprecatedClass === SampleClassA.new).to be true
98
+ end
99
+
100
+ it 'is === to instance of subclass of deprecated class' do
101
+ expect(SampleDeprecatedClass === SampleDeprecatedClassA.new).to be true
102
+ end
103
+
104
+ it 'is === to instance of subclass of subclass of deprecated class' do
105
+ expect(SampleDeprecatedClass === SampleDeprecatedClassAX.new).to be true
106
+ end
107
+ end
108
+
109
+ describe 'subclass of deprecated class' do
110
+ it 'is not === to instance of subclass of undeprecated class' do
111
+ expect(SampleDeprecatedClassA === SampleClass.new).to be false
112
+ end
113
+
114
+ it 'is not === to instance of another subclass of deprecated class' do
115
+ expect(SampleDeprecatedClassA === SampleDeprecatedClassB.new).to be false
116
+ end
117
+
118
+ it 'is === to instance of its subclass' do
119
+ expect(SampleDeprecatedClassA === SampleDeprecatedClassAX.new).to be true
120
+ end
121
+
122
+ it 'is === to instance of deprecated class' do
123
+ expect(SampleDeprecatedClass === SampleDeprecatedClassB.new).to be true
124
+ end
125
+ end
126
+
127
+ describe 'subclass of subclass of deprecated class' do
128
+ it 'is not === to instance of subclass of another subclass of deprecated class' do
129
+ expect(SampleDeprecatedClassAX === SampleDeprecatedClassB.new).to be false
130
+ end
131
+
132
+ it 'is not === to instance of its superclass' do
133
+ expect(SampleDeprecatedClassA === SampleDeprecatedClass.new).to be false
134
+ end
135
+ end
136
+ end
137
+
138
+ def with_warn_squelching
139
+ stderr_catcher = StringIO.new
140
+ original_stderr = $stderr
141
+ $stderr = stderr_catcher
142
+ result = yield if block_given?
143
+ [stderr_catcher.tap(&:rewind).string, result]
144
+ ensure
145
+ $stderr = original_stderr
146
+ end
147
+ end