retriable 3.7.0 → 3.8.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: a5f3739e08167965e6f60cc6104d78f46709c323c2b1a80a839afcd5049ba3ca
4
- data.tar.gz: f9d5f2fe821d7629ceb6954c0afd1a99f63d8b6c99cecc9575e9b5672489be31
3
+ metadata.gz: 25948e6545b8d98b0c7b08416bdf23b017ced352eb2a4eb1f2e9fdac45912031
4
+ data.tar.gz: 4a92afb17f79a4ad173b046158ca634b0728fc6ccceedc1b7789c9106342b513
5
5
  SHA512:
6
- metadata.gz: f5d91b6ffed2805e0da09e307da9cddf018647c1ff89e040da96c9bffd26b5929465694d251c33fc19927288568b2ada4a52f3c1c50b384423038c0eb578634a
7
- data.tar.gz: 1f1e5f0f352a34831f2e71eda177b88336a4d7571be1f4370d98a151e7d06b65468b7d05f02310087e322611149d751e8a271de2c6931bdc8efef9a5f1cb335f
6
+ metadata.gz: e43731b305a64c806bf57c13d78da410412d8ea4adaec9f255bd434b2d190ac53b6fac29172b72ed6bcc7329a3d5378c4075d72f6dede791f1e0bd16f400e58e
7
+ data.tar.gz: 4259468487738b9ae5bcc817ac992af3341f15bcfe604ab0a61ddf1f8793b444775aa57c06ce5f95561e179186e7360e86a877afd02f3b346b68377ffb3ecaf9
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # HEAD
2
2
 
3
+ ## 3.8.0
4
+
5
+ ### Deprecations
6
+
7
+ - Deprecated the `timeout:` option ahead of its removal in Retriable 4.0. Non-nil timeout values supplied through `Retriable.configure`, `Retriable.retriable(...)`, or `Retriable.with_override(...)` now emit a deprecation warning while keeping the existing runtime behavior unchanged. On Ruby 2.7+ the warning is emitted via `Kernel.warn(..., category: :deprecated)`, so callers can silence it through the standard Ruby controls (`Warning[:deprecated] = false`, `ruby -W:no-deprecated`, or a custom `Warning.warn`). To keep the notice from drowning busy applications, it is emitted at most once per process; suppression via `Warning[:deprecated]` leaves the warner armed for the next call that re-enables the category. Prefer library-native timeout settings, or wrap the retried block in `Timeout.timeout(...)` directly if you still need that behavior. See the README migration guidance for details.
8
+
3
9
  ## 3.7.0
4
10
 
5
11
  - Feature: Opt-in unbounded retries via `tries: Float::INFINITY`. Requires a finite `max_elapsed_time` as a safety bound and is incompatible with custom `intervals:`. Both invalid configurations raise `ArgumentError` from `Config#validate!`.
data/README.md CHANGED
@@ -17,6 +17,7 @@ Retriable is a simple DSL to retry failed code blocks with randomized [exponenti
17
17
  - [Configuration](#configuration)
18
18
  - [Override](#override)
19
19
  - [Example Usage](#example-usage)
20
+ - [Migrating off `timeout:`](#migrating-off-timeout)
20
21
  - [Custom Interval Array](#custom-interval-array)
21
22
  - [Turn off Exponential Backoff](#turn-off-exponential-backoff)
22
23
  - [Callbacks](#callbacks)
@@ -55,7 +56,7 @@ require 'retriable'
55
56
  In your Gemfile:
56
57
 
57
58
  ```ruby
58
- gem 'retriable', '~> 3.6'
59
+ gem 'retriable', '~> 3.8'
59
60
  ```
60
61
 
61
62
  ## Usage
@@ -102,20 +103,20 @@ The default interval table with 10 tries looks like this (in seconds, rounded to
102
103
 
103
104
  Here are the available options, in some vague order of relevance to most common use patterns:
104
105
 
105
- | Option | Default | Definition |
106
- | ---------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
107
- | **`tries`** | `3` | Number of attempts to make at running your code block (includes initial attempt). Pass `Float::INFINITY` to keep retrying until success or until `max_elapsed_time` is reached. |
108
- | **`on`** | `[StandardError]` | Type of exceptions to retry. [Read more](#configuring-which-options-to-retry-with-on). |
109
- | **`retry_if`** | `nil` | Callable (for example a `Proc` or lambda) that receives the rescued exception and returns true/false to decide whether to retry. [Read more](#advanced-retry-matching-with-retry_if). |
110
- | **`on_retry`** | `nil` | `Proc` to call after each try is rescued. Pass `false` to disable a callback set in `#configure` for a single call. [Read more](#callbacks). |
111
- | **`sleep_disabled`** | `false` | When true, disable exponential backoff and attempt retries immediately. |
112
- | **`base_interval`** | `0.5` | The initial interval in seconds between tries. |
113
- | **`max_elapsed_time`** | `900` (15 min) | The maximum amount of total time in seconds that code is allowed to keep being retried. Set to `nil` to disable the time limit and retry based solely on `tries`. |
114
- | **`max_interval`** | `60` | The maximum interval in seconds that any individual retry can reach. |
115
- | **`multiplier`** | `1.5` | Each successive interval grows by this factor. A multipler of 1.5 means the next interval will be 1.5x the current interval. |
116
- | **`rand_factor`** | `0.5` | The percentage to randomize the next retry interval time. The next interval calculation is `randomized_interval = retry_interval * (random value in range [1 - randomization_factor, 1 + randomization_factor])` |
117
- | **`intervals`** | `nil` | Skip generated intervals and provide your own array of intervals in seconds. [Read more](#custom-interval-array). |
118
- | **`timeout`** | `nil` | Number of seconds to allow the code block to run before raising a `Timeout::Error` inside each try. `nil` means the code block can run forever without raising error. The implementation uses `Timeout::timeout`, which may be [unsafe](https://jvns.ca/blog/2015/11/27/why-rubys-timeout-is-dangerous-and-thread-dot-raise-is-terrifying/) [and](http://blog.headius.com/2008/02/ruby-threadraise-threadkill-timeoutrb.html) [even](https://adamhooper.medium.com/in-ruby-dont-use-timeout-77d9d4e5a001) [dangerous](https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/). Proceed with caution. |
106
+ | Option | Default | Definition |
107
+ | ---------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
108
+ | **`tries`** | `3` | Number of attempts to make at running your code block (includes initial attempt). Pass `Float::INFINITY` to keep retrying until success or until `max_elapsed_time` is reached. |
109
+ | **`on`** | `[StandardError]` | Type of exceptions to retry. [Read more](#configuring-which-options-to-retry-with-on). |
110
+ | **`retry_if`** | `nil` | Callable (for example a `Proc` or lambda) that receives the rescued exception and returns true/false to decide whether to retry. [Read more](#advanced-retry-matching-with-retry_if). |
111
+ | **`on_retry`** | `nil` | `Proc` to call after each try is rescued. Pass `false` to disable a callback set in `#configure` for a single call. [Read more](#callbacks). |
112
+ | **`sleep_disabled`** | `false` | When true, disable exponential backoff and attempt retries immediately. |
113
+ | **`base_interval`** | `0.5` | The initial interval in seconds between tries. |
114
+ | **`max_elapsed_time`** | `900` (15 min) | The maximum amount of total time in seconds that code is allowed to keep being retried. Set to `nil` to disable the time limit and retry based solely on `tries`. |
115
+ | **`max_interval`** | `60` | The maximum interval in seconds that any individual retry can reach. |
116
+ | **`multiplier`** | `1.5` | Each successive interval grows by this factor. A multipler of 1.5 means the next interval will be 1.5x the current interval. |
117
+ | **`rand_factor`** | `0.5` | The percentage to randomize the next retry interval time. The next interval calculation is `randomized_interval = retry_interval * (random value in range [1 - randomization_factor, 1 + randomization_factor])` |
118
+ | **`intervals`** | `nil` | Skip generated intervals and provide your own array of intervals in seconds. [Read more](#custom-interval-array). |
119
+ | **`timeout`** | `nil` | Deprecated in 3.8.0 and removed in 4.0. Number of seconds to allow the code block to run before raising a `Timeout::Error` inside each try. `nil` means the code block can run forever without raising error. Non-nil values emit a deprecation warning. See [Migrating off `timeout:`](#migrating-off-timeout). |
119
120
 
120
121
  Timing options are validated before retrying. `tries` must be a positive integer when Retriable generates intervals, or `Float::INFINITY` for unbounded retries. `base_interval`, `max_interval`, `multiplier`, `max_elapsed_time`, and `timeout` must be non-negative numbers, with `max_elapsed_time` and `timeout` also accepting `nil`. `rand_factor` must be a number from `0` through `1`. If provided, `intervals` must be an array of non-negative numbers; because it replaces generated intervals, it also overrides `tries`, `base_interval`, `max_interval`, `rand_factor`, and `multiplier` validation. `intervals` cannot be combined with `tries: Float::INFINITY`.
121
122
 
@@ -243,20 +244,32 @@ Retriable.retriable(on: {
243
244
  end
244
245
  ```
245
246
 
246
- You can also specify a timeout if you want the code block to only try for X amount of seconds. This timeout is per try.
247
+ #### Migrating off `timeout:`
247
248
 
248
- The implementation uses `Timeout::timeout`, which may be [unsafe](https://jvns.ca/blog/2015/11/27/why-rubys-timeout-is-dangerous-and-thread-dot-raise-is-terrifying/) [and](http://blog.headius.com/2008/02/ruby-threadraise-threadkill-timeoutrb.html) [even](https://adamhooper.medium.com/in-ruby-dont-use-timeout-77d9d4e5a001) [dangerous](https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/). You can use this option, but you need to be very careful because the code in the block, including libraries or other code it calls, could be interrupted by the timeout at any line. You must ensure you have the right rescue logic and guards in place ([Thread.handle_interrupt](https://www.rubydoc.info/stdlib/core/Thread.handle_interrupt)) to handle that possible behavior. If that's not possible, the recommendation is that you're better off impelenting your own timeout methods depending on what your code is doing than use this feature.
249
+ The `timeout:` option is deprecated in Retriable 3.8.0 and will be removed in Retriable 4.0. It still works in 3.x, but any non-nil value supplied through `Retriable.configure`, `Retriable.retriable(...)`, or `Retriable.with_override(...)` emits a deprecation warning. In Retriable 4.0, passing `timeout:` will raise `ArgumentError` because it will no longer be a valid option.
250
+
251
+ `timeout:` is deprecated because it is a thin wrapper around `Timeout.timeout`, which may be [unsafe](https://jvns.ca/blog/2015/11/27/why-rubys-timeout-is-dangerous-and-thread-dot-raise-is-terrifying/) [and](http://blog.headius.com/2008/02/ruby-threadraise-threadkill-timeoutrb.html) [even](https://adamhooper.medium.com/in-ruby-dont-use-timeout-77d9d4e5a001) [dangerous](https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/). It can interrupt the retried block at any line, including inside libraries that are not interrupt-safe.
252
+
253
+ Prefer timeout settings from the library you are calling, such as `Net::HTTP#read_timeout`, `Net::HTTP#open_timeout`, or Faraday's request timeout options. If you still need `Timeout.timeout`, wrap the retried block explicitly so the risk is visible at the call site:
249
254
 
250
255
  ```ruby
251
- Retriable.retriable(timeout: 60) do
252
- # code here...
256
+ require "timeout"
257
+
258
+ Retriable.retriable(on: Timeout::Error, tries: 3) do
259
+ Timeout.timeout(5) do
260
+ # code here...
261
+ end
253
262
  end
254
263
  ```
255
264
 
256
- If you need millisecond units of time for the sleep or the timeout:
265
+ Like the deprecated `timeout:` option, `Timeout.timeout(5)` inside the block is per-try — each retry gets a fresh 5-second budget. If you want an overall cap across all retries instead, prefer `max_elapsed_time:`.
266
+
267
+ The deprecation warning is emitted under the `:deprecated` warning category and at most once per process. To silence it (for example, in tests), use the standard Ruby controls — set `Warning[:deprecated] = false`, run with `ruby -W:no-deprecated`, or override `Warning.warn` to filter the message.
268
+
269
+ If you need millisecond units of time for the sleep interval:
257
270
 
258
271
  ```ruby
259
- Retriable.retriable(base_interval: (200 / 1000.0), timeout: (500 / 1000.0)) do
272
+ Retriable.retriable(base_interval: (200 / 1000.0)) do
260
273
  # code here...
261
274
  end
262
275
  ```
@@ -18,6 +18,19 @@ module Retriable
18
18
  contexts
19
19
  ]).freeze
20
20
 
21
+ TIMEOUT_DEPRECATION_MESSAGE = "NOTE: Retriable's `timeout:` option is deprecated and will be removed in " \
22
+ "Retriable 4.0. It is a thin wrapper around `Timeout.timeout`, which " \
23
+ "can interrupt execution at arbitrary lines and corrupt internal state " \
24
+ "in libraries that are not interrupt-safe. Prefer your library's native " \
25
+ "timeout, or wrap your block in `Timeout.timeout(...)` yourself."
26
+ private_constant :TIMEOUT_DEPRECATION_MESSAGE
27
+
28
+ @timeout_deprecation_warned = false
29
+
30
+ class << self
31
+ attr_accessor :timeout_deprecation_warned
32
+ end
33
+
21
34
  attr_accessor(*ATTRIBUTES)
22
35
 
23
36
  def initialize(opts = {})
@@ -53,6 +66,7 @@ module Retriable
53
66
  end
54
67
 
55
68
  def validate!
69
+ warn_timeout_deprecation
56
70
  validate_optional_non_negative_number(:timeout, timeout)
57
71
  validate_on(on)
58
72
  validate_intervals
@@ -70,6 +84,43 @@ module Retriable
70
84
 
71
85
  private
72
86
 
87
+ # Emits the `timeout:` deprecation notice at most once per process.
88
+ #
89
+ # On Rubies that support `Kernel#warn(category: :deprecated)` (2.7+), the
90
+ # notice is emitted under the `:deprecated` category, so callers can use the
91
+ # standard controls (`Warning[:deprecated] = false`, `-W:no-deprecated`,
92
+ # `Warning.warn` override) to silence it. On older Rubies the kwarg is not
93
+ # available and we fall back to plain `Kernel.warn`.
94
+ #
95
+ # When the warning is suppressed (either because `Warning[:deprecated]` is
96
+ # false or the runtime has otherwise muted the category), we deliberately
97
+ # leave the once-per-process flag unset so a future call with the category
98
+ # re-enabled still surfaces the notice.
99
+ def warn_timeout_deprecation
100
+ return if timeout.nil?
101
+ return if self.class.timeout_deprecation_warned
102
+
103
+ category_supported = deprecated_warning_category_supported?
104
+ return if category_supported && !deprecated_warnings_enabled?
105
+
106
+ self.class.timeout_deprecation_warned = true
107
+ if category_supported
108
+ Kernel.warn(TIMEOUT_DEPRECATION_MESSAGE, category: :deprecated)
109
+ else
110
+ Kernel.warn(TIMEOUT_DEPRECATION_MESSAGE)
111
+ end
112
+ end
113
+
114
+ def deprecated_warning_category_supported?
115
+ defined?(Warning) && Kernel.method(:warn).parameters.any? { |type, name| type == :key && name == :category }
116
+ end
117
+
118
+ def deprecated_warnings_enabled?
119
+ return true unless defined?(Warning) && Warning.respond_to?(:[])
120
+
121
+ Warning[:deprecated]
122
+ end
123
+
73
124
  def validate_backoff_options
74
125
  validate_non_negative_number(:base_interval, base_interval)
75
126
  validate_non_negative_number(:multiplier, multiplier)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Retriable
4
- VERSION = "3.7.0"
4
+ VERSION = "3.8.0"
5
5
  end
data/spec/config_spec.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "stringio"
4
+
3
5
  describe Retriable::Config do
4
6
  let(:default_config) { described_class.new }
5
7
 
@@ -59,7 +61,97 @@ describe Retriable::Config do
59
61
 
60
62
  it "raises errors on invalid timing configuration" do
61
63
  expect { described_class.new(rand_factor: 1.1) }.to raise_error(ArgumentError, /rand_factor/)
62
- expect { described_class.new(timeout: -1) }.to raise_error(ArgumentError, /timeout/)
64
+ expect do
65
+ expect { described_class.new(timeout: -1) }.to raise_error(ArgumentError, /timeout/)
66
+ end.to output(/timeout.*deprecated.*Retriable 4\.0/i).to_stderr
67
+ end
68
+
69
+ context "timeout deprecation" do
70
+ it "warns when timeout is configured" do
71
+ expect do
72
+ described_class.new(timeout: 5)
73
+ end.to output(/timeout.*deprecated.*Retriable 4\.0/i).to_stderr
74
+ end
75
+
76
+ it "warns when timeout is set before validation" do
77
+ config = described_class.new
78
+ config.timeout = 5
79
+
80
+ expect do
81
+ config.validate!
82
+ end.to output(/timeout.*deprecated.*Retriable 4\.0/i).to_stderr
83
+ end
84
+
85
+ it "does not warn when timeout is nil" do
86
+ expect do
87
+ described_class.new(timeout: nil)
88
+ end.not_to output.to_stderr
89
+ end
90
+
91
+ it "does not warn when timeout is omitted" do
92
+ expect do
93
+ described_class.new
94
+ end.not_to output.to_stderr
95
+ end
96
+
97
+ it "warns at most once per process" do
98
+ original_stderr = $stderr
99
+ stderr = StringIO.new
100
+ begin
101
+ $stderr = stderr
102
+
103
+ described_class.new(timeout: 5)
104
+ described_class.new(timeout: 5)
105
+
106
+ config = described_class.new
107
+ config.timeout = 5
108
+ config.validate!
109
+ ensure
110
+ $stderr = original_stderr
111
+ end
112
+
113
+ expect(stderr.string.scan("timeout:` option is deprecated").size).to eq(1)
114
+ end
115
+
116
+ it "emits the warning under the :deprecated category when supported", if: WARN_CATEGORY_SUPPORTED do
117
+ captured = []
118
+ allow(Warning).to receive(:warn) do |message, category: nil|
119
+ captured << [message, category]
120
+ end
121
+
122
+ described_class.new(timeout: 5)
123
+
124
+ expect(captured.size).to eq(1)
125
+ message, category = captured.first
126
+ expect(message).to match(/timeout.*deprecated.*Retriable 4\.0/i)
127
+ expect(category).to eq(:deprecated)
128
+ end
129
+
130
+ it "is silenced by Warning[:deprecated] = false", if: WARN_CATEGORY_SUPPORTED do
131
+ original = Warning[:deprecated]
132
+ begin
133
+ Warning[:deprecated] = false
134
+ expect do
135
+ described_class.new(timeout: 5)
136
+ end.not_to output.to_stderr
137
+ ensure
138
+ Warning[:deprecated] = original
139
+ end
140
+ end
141
+
142
+ it "remains armed when silenced via Warning[:deprecated]", if: WARN_CATEGORY_SUPPORTED do
143
+ original = Warning[:deprecated]
144
+ begin
145
+ Warning[:deprecated] = false
146
+ described_class.new(timeout: 5)
147
+ ensure
148
+ Warning[:deprecated] = original
149
+ end
150
+
151
+ expect do
152
+ described_class.new(timeout: 5)
153
+ end.to output(/timeout.*deprecated.*Retriable 4\.0/i).to_stderr
154
+ end
63
155
  end
64
156
 
65
157
  it "raises errors when intervals is not an array" do
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rbconfig"
4
+ require "stringio"
4
5
 
5
6
  describe Retriable do
6
7
  let(:time_table_handler) do
@@ -50,7 +51,9 @@ describe Retriable do
50
51
 
51
52
  it "raises a LocalJumpError if not given a block" do
52
53
  expect { described_class.retriable }.to raise_error(LocalJumpError)
53
- expect { described_class.retriable(timeout: 2) }.to raise_error(LocalJumpError)
54
+ expect do
55
+ expect { described_class.retriable(timeout: 2) }.to raise_error(LocalJumpError)
56
+ end.to output(/timeout.*deprecated.*Retriable 4\.0/i).to_stderr
54
57
  end
55
58
 
56
59
  it "stops at first try if the block does not raise an exception" do
@@ -169,7 +172,85 @@ describe Retriable do
169
172
  end
170
173
 
171
174
  it "will timeout after 1 second" do
172
- expect { described_class.retriable(timeout: 1) { sleep(1.1) } }.to raise_error(Timeout::Error)
175
+ expect do
176
+ expect { described_class.retriable(timeout: 1) { sleep(1.1) } }.to raise_error(Timeout::Error)
177
+ end.to output(/timeout.*deprecated.*Retriable 4\.0/i).to_stderr
178
+ end
179
+
180
+ context "timeout: deprecation" do
181
+ it "warns at most once per process across repeated retriable calls" do
182
+ original_stderr = $stderr
183
+ stderr = StringIO.new
184
+ begin
185
+ $stderr = stderr
186
+
187
+ described_class.retriable(timeout: 5) { :noop }
188
+ described_class.retriable(timeout: 5) { :noop }
189
+ described_class.retriable(timeout: 5) { :noop }
190
+
191
+ expect(stderr.string.scan("timeout:` option is deprecated").size).to eq(1)
192
+ ensure
193
+ $stderr = original_stderr
194
+ end
195
+ end
196
+
197
+ it "warns when timeout is passed to retriable" do
198
+ expect do
199
+ described_class.retriable(timeout: 5) { :noop }
200
+ end.to output(/timeout.*deprecated.*Retriable 4\.0/i).to_stderr
201
+ end
202
+
203
+ it "keeps applying timeout while deprecated" do
204
+ original_stderr = $stderr
205
+ begin
206
+ $stderr = StringIO.new
207
+ expect do
208
+ described_class.retriable(timeout: 0.05, tries: 1) { sleep(0.5) }
209
+ end.to raise_error(Timeout::Error)
210
+ ensure
211
+ $stderr = original_stderr
212
+ end
213
+ end
214
+
215
+ it "warns when timeout is supplied through with_override" do
216
+ expect do
217
+ described_class.with_override(timeout: 5) do
218
+ described_class.retriable { :noop }
219
+ end
220
+ end.to output(/timeout.*deprecated.*Retriable 4\.0/i).to_stderr
221
+ end
222
+
223
+ it "warns when timeout is supplied through configure" do
224
+ original_config = described_class.config
225
+ begin
226
+ expect do
227
+ described_class.configure { |config| config.timeout = 5 }
228
+ described_class.retriable { :noop }
229
+ end.to output(/timeout.*deprecated.*Retriable 4\.0/i).to_stderr
230
+ ensure
231
+ described_class.configure do |config|
232
+ original_config.to_h.each { |key, value| config.public_send("#{key}=", value) }
233
+ end
234
+ end
235
+ end
236
+
237
+ it "is silenced by Warning[:deprecated] = false", if: WARN_CATEGORY_SUPPORTED do
238
+ original = Warning[:deprecated]
239
+ begin
240
+ Warning[:deprecated] = false
241
+ expect do
242
+ described_class.retriable(timeout: 5) { :noop }
243
+ end.not_to output.to_stderr
244
+ ensure
245
+ Warning[:deprecated] = original
246
+ end
247
+ end
248
+
249
+ it "does not warn when timeout is absent" do
250
+ expect do
251
+ described_class.retriable { :noop }
252
+ end.not_to output.to_stderr
253
+ end
173
254
  end
174
255
 
175
256
  it "applies a randomized exponential backoff to each try" do
data/spec/spec_helper.rb CHANGED
@@ -7,8 +7,21 @@ require "pry"
7
7
  require_relative "../lib/retriable"
8
8
  require_relative "support/exceptions"
9
9
 
10
+ # Make Retriable's deprecation notices observable to RSpec's
11
+ # `output().to_stderr` matcher. On Ruby 3.0+ the `:deprecated` warning category
12
+ # is suppressed by default, which would hide the notices we want to assert on.
13
+ WARNING_DEPRECATION_SUPPORTED = defined?(Warning) && Warning.respond_to?(:[])
14
+ Warning[:deprecated] = true if WARNING_DEPRECATION_SUPPORTED
15
+
16
+ # Used by deprecation specs that only make sense on Rubies where `Kernel#warn`
17
+ # supports the `category:` keyword (added in Ruby 2.7).
18
+ WARN_CATEGORY_SUPPORTED = WARNING_DEPRECATION_SUPPORTED &&
19
+ Kernel.method(:warn).parameters.include?(%i[key category])
20
+
10
21
  RSpec.configure do |config|
11
22
  config.before(:each) do
12
23
  srand(0)
24
+ Retriable::Config.timeout_deprecation_warned = false
25
+ Warning[:deprecated] = true if WARNING_DEPRECATION_SUPPORTED
13
26
  end
14
27
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: retriable
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.7.0
4
+ version: 3.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jack Chu