retriable 3.1.1 → 3.1.2

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.
@@ -28,7 +28,7 @@ module Retriable
28
28
  @timeout = nil
29
29
  @on = [StandardError]
30
30
  @on_retry = nil
31
- @contexts = {}
31
+ @contexts = {}
32
32
 
33
33
  opts.each do |k, v|
34
34
  raise ArgumentError, "#{k} is not a valid option" if !ATTRIBUTES.include?(k)
@@ -36,7 +36,6 @@ module Retriable
36
36
  private
37
37
 
38
38
  def randomize(interval)
39
- return interval if rand_factor.zero?
40
39
  delta = rand_factor * interval * 1.0
41
40
  min = interval - delta
42
41
  max = interval + delta
@@ -1,3 +1,3 @@
1
1
  module Retriable
2
- VERSION = "3.1.1".freeze
2
+ VERSION = "3.1.2".freeze
3
3
  end
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Retriable::VERSION
9
9
  spec.authors = ["Jack Chu"]
10
10
  spec.email = ["jack@jackchu.com"]
11
- spec.summary = "Retriable is an simple DSL to retry failed code blocks with randomized exponential backoff"
12
- spec.description = "Retriable is an simple DSL to retry failed code blocks with randomized exponential backoff. This is especially useful when interacting external api/services or file system calls."
11
+ spec.summary = "Retriable is a simple DSL to retry failed code blocks with randomized exponential backoff"
12
+ spec.description = "Retriable is a simple DSL to retry failed code blocks with randomized exponential backoff. This is especially useful when interacting external api/services or file system calls."
13
13
  spec.homepage = "http://github.com/kamui/retriable"
14
14
  spec.license = "MIT"
15
15
 
@@ -21,11 +21,7 @@ Gem::Specification.new do |spec|
21
21
  spec.required_ruby_version = ">= 2.0.0"
22
22
 
23
23
  spec.add_development_dependency "bundler"
24
- spec.add_development_dependency "rake", "~> 12.0"
25
-
26
- spec.add_development_dependency "minitest", "~> 5.10"
27
- spec.add_development_dependency "guard"
28
- spec.add_development_dependency "guard-minitest"
24
+ spec.add_development_dependency "rspec", "~> 3"
29
25
 
30
26
  if RUBY_VERSION < "2.3"
31
27
  spec.add_development_dependency "ruby_dep", "~> 1.3.1"
@@ -1,57 +1,53 @@
1
- require_relative "spec_helper"
2
-
3
1
  describe Retriable::Config do
4
- subject do
5
- Retriable::Config
6
- end
2
+ let(:default_config) { described_class.new }
7
3
 
8
- it "sleep defaults to enabled" do
9
- expect(subject.new.sleep_disabled).must_equal false
10
- end
4
+ context "defaults" do
5
+ it "sleep defaults to enabled" do
6
+ expect(default_config.sleep_disabled).to be_falsey
7
+ end
11
8
 
12
- it "tries defaults to 3" do
13
- expect(subject.new.tries).must_equal 3
14
- end
9
+ it "tries defaults to 3" do
10
+ expect(default_config.tries).to eq(3)
11
+ end
15
12
 
16
- it "max interval defaults to 60" do
17
- expect(subject.new.max_interval).must_equal 60
18
- end
13
+ it "max interval defaults to 60" do
14
+ expect(default_config.max_interval).to eq(60)
15
+ end
19
16
 
20
- it "randomization factor defaults to 0.5" do
21
- expect(subject.new.base_interval).must_equal 0.5
22
- end
17
+ it "randomization factor defaults to 0.5" do
18
+ expect(default_config.base_interval).to eq(0.5)
19
+ end
23
20
 
24
- it "multiplier defaults to 1.5" do
25
- expect(subject.new.multiplier).must_equal 1.5
26
- end
21
+ it "multiplier defaults to 1.5" do
22
+ expect(default_config.multiplier).to eq(1.5)
23
+ end
27
24
 
28
- it "max elapsed time defaults to 900" do
29
- expect(subject.new.max_elapsed_time).must_equal 900
30
- end
25
+ it "max elapsed time defaults to 900" do
26
+ expect(default_config.max_elapsed_time).to eq(900)
27
+ end
31
28
 
32
- it "intervals defaults to nil" do
33
- expect(subject.new.intervals).must_be_nil
34
- end
29
+ it "intervals defaults to nil" do
30
+ expect(default_config.intervals).to be_nil
31
+ end
35
32
 
36
- it "timeout defaults to nil" do
37
- expect(subject.new.timeout).must_be_nil
38
- end
33
+ it "timeout defaults to nil" do
34
+ expect(default_config.timeout).to be_nil
35
+ end
39
36
 
40
- it "on defaults to [StandardError]" do
41
- expect(subject.new.on).must_equal [StandardError]
42
- end
37
+ it "on defaults to [StandardError]" do
38
+ expect(default_config.on).to eq([StandardError])
39
+ end
43
40
 
44
- it "on retry handler defaults to nil" do
45
- expect(subject.new.on_retry).must_be_nil
46
- end
41
+ it "on_retry handler defaults to nil" do
42
+ expect(default_config.on_retry).to be_nil
43
+ end
47
44
 
48
- it "contexts defaults to {}" do
49
- expect(subject.new.contexts).must_equal Hash.new
45
+ it "contexts defaults to {}" do
46
+ expect(default_config.contexts).to eq({})
47
+ end
50
48
  end
51
49
 
52
50
  it "raises errors on invalid configuration" do
53
- assert_raises ArgumentError do
54
- subject.new(does_not_exist: 123)
55
- end
51
+ expect { described_class.new(does_not_exist: 123) }.to raise_error(ArgumentError, /not a valid option/)
56
52
  end
57
53
  end
@@ -1,32 +1,26 @@
1
- require_relative "spec_helper"
2
-
3
1
  describe Retriable::ExponentialBackoff do
4
- subject do
5
- Retriable::ExponentialBackoff
6
- end
2
+ context "defaults" do
3
+ let(:backoff_config) { described_class.new }
7
4
 
8
- before do
9
- srand 0
10
- end
5
+ it "tries defaults to 3" do
6
+ expect(backoff_config.tries).to eq(3)
7
+ end
11
8
 
12
- it "tries defaults to 3" do
13
- expect(subject.new.tries).must_equal 3
14
- end
9
+ it "max interval defaults to 60" do
10
+ expect(backoff_config.max_interval).to eq(60)
11
+ end
15
12
 
16
- it "max interval defaults to 60" do
17
- expect(subject.new.max_interval).must_equal 60
18
- end
13
+ it "randomization factor defaults to 0.5" do
14
+ expect(backoff_config.base_interval).to eq(0.5)
15
+ end
19
16
 
20
- it "randomization factor defaults to 0.5" do
21
- expect(subject.new.base_interval).must_equal 0.5
22
- end
23
-
24
- it "multiplier defaults to 1.5" do
25
- expect(subject.new.multiplier).must_equal 1.5
17
+ it "multiplier defaults to 1.5" do
18
+ expect(backoff_config.multiplier).to eq(1.5)
19
+ end
26
20
  end
27
21
 
28
22
  it "generates 10 randomized intervals" do
29
- expect(subject.new(tries: 9).intervals).must_equal([
23
+ expect(described_class.new(tries: 9).intervals).to eq([
30
24
  0.5244067512211441,
31
25
  0.9113920238761231,
32
26
  1.2406087918999114,
@@ -40,11 +34,11 @@ describe Retriable::ExponentialBackoff do
40
34
  end
41
35
 
42
36
  it "generates defined number of intervals" do
43
- expect(subject.new(tries: 5).intervals.size).must_equal 5
37
+ expect(described_class.new(tries: 5).intervals.size).to eq(5)
44
38
  end
45
39
 
46
40
  it "generates intervals with a defined base interval" do
47
- expect(subject.new(base_interval: 1).intervals).must_equal([
41
+ expect(described_class.new(base_interval: 1).intervals).to eq([
48
42
  1.0488135024422882,
49
43
  1.8227840477522461,
50
44
  2.4812175837998227,
@@ -52,7 +46,7 @@ describe Retriable::ExponentialBackoff do
52
46
  end
53
47
 
54
48
  it "generates intervals with a defined multiplier" do
55
- expect(subject.new(multiplier: 1).intervals).must_equal([
49
+ expect(described_class.new(multiplier: 1).intervals).to eq([
56
50
  0.5244067512211441,
57
51
  0.607594682584082,
58
52
  0.5513816852888495,
@@ -60,15 +54,11 @@ describe Retriable::ExponentialBackoff do
60
54
  end
61
55
 
62
56
  it "generates intervals with a defined max interval" do
63
- expect(subject.new(max_interval: 1.0, rand_factor: 0.0).intervals).must_equal([
64
- 0.5,
65
- 0.75,
66
- 1.0,
67
- ])
57
+ expect(described_class.new(max_interval: 1.0, rand_factor: 0.0).intervals).to eq([0.5, 0.75, 1.0])
68
58
  end
69
59
 
70
60
  it "generates intervals with a defined rand_factor" do
71
- expect(subject.new(rand_factor: 0.2).intervals).must_equal([
61
+ expect(described_class.new(rand_factor: 0.2).intervals).to eq([
72
62
  0.5097627004884576,
73
63
  0.8145568095504492,
74
64
  1.1712435167599646,
@@ -76,20 +66,7 @@ describe Retriable::ExponentialBackoff do
76
66
  end
77
67
 
78
68
  it "generates 10 non-randomized intervals" do
79
- expect(subject.new(
80
- tries: 10,
81
- rand_factor: 0.0,
82
- ).intervals).must_equal([
83
- 0.5,
84
- 0.75,
85
- 1.125,
86
- 1.6875,
87
- 2.53125,
88
- 3.796875,
89
- 5.6953125,
90
- 8.54296875,
91
- 12.814453125,
92
- 19.2216796875,
93
- ])
69
+ non_random_intervals = 9.times.inject([0.5]) { |memo, _i| memo + [memo.last * 1.5] }
70
+ expect(described_class.new(tries: 10, rand_factor: 0.0).intervals).to eq(non_random_intervals)
94
71
  end
95
72
  end
@@ -1,434 +1,265 @@
1
- require_relative "spec_helper"
1
+ describe Retriable do
2
+ let(:time_table_handler) do
3
+ ->(_exception, try, _elapsed_time, next_interval) { @next_interval_table[try] = next_interval }
4
+ end
2
5
 
3
- class TestError < Exception; end
6
+ before(:each) do
7
+ described_class.configure { |c| c.sleep_disabled = true }
8
+ @tries = 0
9
+ @next_interval_table = {}
10
+ end
4
11
 
5
- describe Retriable do
6
- subject do
7
- Retriable
12
+ def increment_tries
13
+ @tries += 1
8
14
  end
9
15
 
10
- before do
11
- srand 0
16
+ def increment_tries_with_exception(exception_class = nil)
17
+ exception_class ||= StandardError
18
+ increment_tries
19
+ raise exception_class, "#{exception_class} occurred"
12
20
  end
13
21
 
14
- describe "with sleep disabled" do
15
- before do
16
- Retriable.configure do |c|
17
- c.sleep_disabled = true
18
- end
22
+ context "global scope extension" do
23
+ it "cannot be called in the global scope without requiring the core_ext/kernel" do
24
+ expect { retriable { puts "should raise NoMethodError" } }.to raise_error(NoMethodError)
19
25
  end
20
26
 
21
- it "stops at first try if the block does not raise an exception" do
22
- tries = 0
23
- subject.retriable do
24
- tries += 1
25
- end
27
+ it "can be called once the kernel extension is required" do
28
+ require_relative "../lib/retriable/core_ext/kernel"
26
29
 
27
- expect(tries).must_equal 1
30
+ expect { retriable { increment_tries_with_exception } }.to raise_error(StandardError)
31
+ expect(@tries).to eq(3)
28
32
  end
33
+ end
29
34
 
30
- it "raises a LocalJumpError if #retriable is not given a block" do
31
- expect do
32
- subject.retriable on: StandardError
33
- end.must_raise LocalJumpError
35
+ context "#retriable" do
36
+ it "raises a LocalJumpError if not given a block" do
37
+ expect { described_class.retriable }.to raise_error(LocalJumpError)
38
+ expect { described_class.retriable(timeout: 2) }.to raise_error(LocalJumpError)
39
+ end
34
40
 
35
- expect do
36
- subject.retriable on: StandardError, timeout: 2
37
- end.must_raise LocalJumpError
41
+ it "stops at first try if the block does not raise an exception" do
42
+ described_class.retriable { increment_tries }
43
+ expect(@tries).to eq(1)
38
44
  end
39
45
 
40
46
  it "makes 3 tries when retrying block of code raising StandardError with no arguments" do
41
- tries = 0
42
-
43
- expect do
44
- subject.retriable do
45
- tries += 1
46
- raise StandardError.new, "StandardError occurred"
47
- end
48
- end.must_raise StandardError
49
-
50
- expect(tries).must_equal 3
47
+ expect { described_class.retriable { increment_tries_with_exception } }.to raise_error(StandardError)
48
+ expect(@tries).to eq(3)
51
49
  end
52
50
 
53
- it "makes only 1 try when exception raised is not ancestor of StandardError" do
54
- tries = 0
55
-
51
+ it "makes only 1 try when exception raised is not descendent of StandardError" do
56
52
  expect do
57
- subject.retriable do
58
- tries += 1
59
- raise TestError.new, "TestError occurred"
60
- end
61
- end.must_raise TestError
53
+ described_class.retriable { increment_tries_with_exception(NonStandardError) }
54
+ end.to raise_error(NonStandardError)
62
55
 
63
- expect(tries).must_equal 1
56
+ expect(@tries).to eq(1)
64
57
  end
65
58
 
66
- it "#retriable with custom exception tries 3 times and re-raises the exception" do
67
- tries = 0
68
-
59
+ it "with custom exception tries 3 times and re-raises the exception" do
69
60
  expect do
70
- subject.retriable on: TestError do
71
- tries += 1
72
- raise TestError.new, "TestError occurred"
73
- end
74
- end.must_raise TestError
61
+ described_class.retriable(on: NonStandardError) { increment_tries_with_exception(NonStandardError) }
62
+ end.to raise_error(NonStandardError)
75
63
 
76
- expect(tries).must_equal 3
64
+ expect(@tries).to eq(3)
77
65
  end
78
66
 
79
- it "#retriable tries 10 times" do
80
- tries = 0
81
-
82
- expect do
83
- subject.retriable(tries: 10) do
84
- tries += 1
85
- raise StandardError.new, "StandardError occurred"
86
- end
87
- end.must_raise StandardError
88
-
89
- expect(tries).must_equal 10
67
+ it "tries 10 times when specified" do
68
+ expect { described_class.retriable(tries: 10) { increment_tries_with_exception } }.to raise_error(StandardError)
69
+ expect(@tries).to eq(10)
90
70
  end
91
71
 
92
- it "#retriable will timeout after 1 second" do
93
- expect do
94
- subject.retriable timeout: 1 do
95
- sleep 1.1
96
- end
97
- end.must_raise Timeout::Error
72
+ it "will timeout after 1 second" do
73
+ expect { described_class.retriable(timeout: 1) { sleep(1.1) } }.to raise_error(Timeout::Error)
98
74
  end
99
75
 
100
76
  it "applies a randomized exponential backoff to each try" do
101
- tries = 0
102
- time_table = []
103
-
104
- handler = lambda do |exception, _try, _elapsed_time, next_interval|
105
- expect(exception.class).must_equal ArgumentError
106
- time_table << next_interval
107
- end
108
-
109
77
  expect do
110
- Retriable.retriable(
111
- on: [EOFError, ArgumentError],
112
- on_retry: handler,
113
- tries: 10,
114
- ) do
115
- tries += 1
116
- raise ArgumentError.new, "ArgumentError occurred"
117
- end
118
- end.must_raise ArgumentError
119
-
120
- expect(time_table).must_equal([
121
- 0.5244067512211441,
122
- 0.9113920238761231,
123
- 1.2406087918999114,
124
- 1.7632403621664823,
125
- 2.338001204738311,
126
- 4.350816718580626,
127
- 5.339852157217869,
128
- 11.889873261212443,
129
- 18.756037881636484,
130
- nil,
131
- ])
132
-
133
- expect(tries).must_equal(10)
78
+ described_class.retriable(on_retry: time_table_handler, tries: 10) { increment_tries_with_exception }
79
+ end.to raise_error(StandardError)
80
+
81
+ expect(@next_interval_table).to eq(
82
+ 1 => 0.5244067512211441,
83
+ 2 => 0.9113920238761231,
84
+ 3 => 1.2406087918999114,
85
+ 4 => 1.7632403621664823,
86
+ 5 => 2.338001204738311,
87
+ 6 => 4.350816718580626,
88
+ 7 => 5.339852157217869,
89
+ 8 => 11.889873261212443,
90
+ 9 => 18.756037881636484,
91
+ 10 => nil,
92
+ )
93
+
94
+ expect(@tries).to eq(10)
134
95
  end
135
96
 
136
- describe "retries with an on_#retriable handler, 6 max retries, and a 0.0 rand_factor" do
137
- before do
138
- tries = 6
139
- @try_count = 0
140
- @time_table = {}
97
+ context "with rand_factor 0.0 and an on_retry handler" do
98
+ let(:tries) { 6 }
99
+ let(:no_rand_timetable) { { 1 => 0.5, 2 => 0.75, 3 => 1.125 } }
100
+ let(:args) { { on_retry: time_table_handler, rand_factor: 0.0, tries: tries } }
141
101
 
142
- handler = lambda do |exception, try, _elapsed_time, next_interval|
143
- expect(exception.class).must_equal ArgumentError
144
- @time_table[try] = next_interval
102
+ it "applies a non-randomized exponential backoff to each try" do
103
+ described_class.retriable(args) do
104
+ increment_tries
105
+ raise StandardError if @tries < tries
145
106
  end
146
107
 
147
- Retriable.retriable(
148
- on: [EOFError, ArgumentError],
149
- on_retry: handler,
150
- rand_factor: 0.0,
151
- tries: tries,
152
- ) do
153
- @try_count += 1
154
- raise ArgumentError.new, "ArgumentError occurred" if @try_count < tries
155
- end
108
+ expect(@tries).to eq(tries)
109
+ expect(@next_interval_table).to eq(no_rand_timetable.merge(4 => 1.6875, 5 => 2.53125))
156
110
  end
157
111
 
158
- it "makes 6 tries" do
159
- expect(@try_count).must_equal 6
160
- end
112
+ it "obeys a max interval of 1.5 seconds" do
113
+ expect do
114
+ described_class.retriable(args.merge(max_interval: 1.5)) { increment_tries_with_exception }
115
+ end.to raise_error(StandardError)
161
116
 
162
- it "applies a non-randomized exponential backoff to each try" do
163
- expect(@time_table).must_equal(
164
- 1 => 0.5,
165
- 2 => 0.75,
166
- 3 => 1.125,
167
- 4 => 1.6875,
168
- 5 => 2.53125,
169
- )
117
+ expect(@next_interval_table).to eq(no_rand_timetable.merge(4 => 1.5, 5 => 1.5, 6 => nil))
170
118
  end
171
- end
172
-
173
- it "#retriable has a max interval of 1.5 seconds" do
174
- tries = 0
175
- time_table = {}
176
119
 
177
- handler = lambda do |_exception, try, _elapsed_time, next_interval|
178
- time_table[try] = next_interval
179
- end
120
+ it "obeys custom defined intervals" do
121
+ interval_hash = no_rand_timetable.merge(4 => 1.5, 5 => 1.5, 6 => nil)
122
+ intervals = interval_hash.values.compact.sort
180
123
 
181
- expect do
182
- subject.retriable(
183
- on: StandardError,
184
- on_retry: handler,
185
- rand_factor: 0.0,
186
- tries: 5,
187
- max_interval: 1.5,
188
- ) do
189
- tries += 1
190
- raise StandardError.new, "StandardError occurred"
191
- end
192
- end.must_raise StandardError
193
-
194
- expect(time_table).must_equal(
195
- 1 => 0.5,
196
- 2 => 0.75,
197
- 3 => 1.125,
198
- 4 => 1.5,
199
- 5 => nil,
200
- )
201
- end
124
+ expect do
125
+ described_class.retriable(on_retry: time_table_handler, intervals: intervals) do
126
+ increment_tries_with_exception
127
+ end
128
+ end.to raise_error(StandardError)
202
129
 
203
- it "#retriable with custom defined intervals" do
204
- intervals = [
205
- 0.5,
206
- 0.75,
207
- 1.125,
208
- 1.5,
209
- 1.5,
210
- ]
211
- time_table = {}
212
-
213
- handler = lambda do |_exception, try, _elapsed_time, next_interval|
214
- time_table[try] = next_interval
130
+ expect(@next_interval_table).to eq(interval_hash)
131
+ expect(@tries).to eq(intervals.size + 1)
215
132
  end
133
+ end
216
134
 
217
- try_count = 0
135
+ context "with an array :on parameter" do
136
+ it "handles both kinds of exceptions" do
137
+ described_class.retriable(on: [StandardError, NonStandardError]) do
138
+ increment_tries
218
139
 
219
- expect do
220
- subject.retriable(
221
- on_retry: handler,
222
- intervals: intervals,
223
- ) do
224
- try_count += 1
225
- raise StandardError.new, "StandardError occurred"
140
+ raise StandardError if @tries == 1
141
+ raise NonStandardError if @tries == 2
226
142
  end
227
- end.must_raise StandardError
228
-
229
- expect(time_table).must_equal(
230
- 1 => 0.5,
231
- 2 => 0.75,
232
- 3 => 1.125,
233
- 4 => 1.5,
234
- 5 => 1.5,
235
- 6 => nil,
236
- )
237
143
 
238
- expect(try_count).must_equal(6)
144
+ expect(@tries).to eq(3)
145
+ end
239
146
  end
240
147
 
241
- it "#retriable with a hash exception where the value is an exception message pattern" do
242
- e = expect do
243
- subject.retriable on: { TestError => /something went wrong/ } do
244
- raise TestError, "something went wrong"
245
- end
246
- end.must_raise TestError
247
-
248
- expect(e.message).must_equal "something went wrong"
249
- end
148
+ context "with a hash :on parameter" do
149
+ let(:on_hash) { { NonStandardError => /NonStandardError occurred/ } }
250
150
 
251
- it "#retriable with a hash exception list matches exception subclasses" do
252
- class SecondTestError < TestError; end
253
- class DifferentTestError < Exception; end
254
-
255
- tries = 0
256
- e = expect do
257
- subject.retriable on: {
258
- DifferentTestError => /should never happen/,
259
- TestError => /something went wrong/,
260
- DifferentTestError => /also should never happen/,
261
- }, tries: 4 do
262
- tries += 1
263
- raise SecondTestError, "something went wrong"
264
- end
265
- end.must_raise SecondTestError
151
+ it "where the value is an exception message pattern" do
152
+ expect do
153
+ described_class.retriable(on: on_hash) { increment_tries_with_exception(NonStandardError) }
154
+ end.to raise_error(NonStandardError, /NonStandardError occurred/)
266
155
 
267
- expect(e.message).must_equal "something went wrong"
268
- expect(tries).must_equal 4
269
- end
156
+ expect(@tries).to eq(3)
157
+ end
270
158
 
271
- it "#retriable with a hash exception list does not retry matching exception subclass but not message" do
272
- class SecondTestError < TestError; end
159
+ it "matches exception subclasses when message matches pattern" do
160
+ expect do
161
+ described_class.retriable(on: on_hash.merge(DifferentError => [/shouldn't happen/, /also not/])) do
162
+ increment_tries_with_exception(SecondNonStandardError)
163
+ end
164
+ end.to raise_error(SecondNonStandardError, /SecondNonStandardError occurred/)
273
165
 
274
- tries = 0
275
- expect do
276
- subject.retriable on: { TestError => /something went wrong/ }, tries: 4 do
277
- tries += 1
278
- raise SecondTestError, "not a match"
279
- end
280
- end.must_raise SecondTestError
166
+ expect(@tries).to eq(3)
167
+ end
281
168
 
282
- expect(tries).must_equal 1
283
- end
169
+ it "does not retry matching exception subclass but not message" do
170
+ expect do
171
+ described_class.retriable(on: on_hash) do
172
+ increment_tries
173
+ raise SecondNonStandardError, "not a match"
174
+ end
175
+ end.to raise_error(SecondNonStandardError, /not a match/)
284
176
 
285
- it "#retriable with a hash exception list where the values are exception message patterns" do
286
- tries = 0
287
- exceptions = []
288
- handler = lambda do |exception, try, _elapsed_time, _next_interval|
289
- exceptions[try] = exception
177
+ expect(@tries).to eq(1)
290
178
  end
291
179
 
292
- e = expect do
293
- subject.retriable tries: 4, on: { StandardError => nil, TestError => [/foo/, /bar/] }, on_retry: handler do
294
- tries += 1
295
- case tries
296
- when 1
297
- raise TestError, "foo"
298
- when 2
299
- raise TestError, "bar"
300
- when 3
301
- raise StandardError
302
- else
303
- raise TestError, "crash"
180
+ it "successfully retries when the values are arrays of exception message patterns" do
181
+ exceptions = []
182
+ handler = ->(exception, try, _elapsed_time, _next_interval) { exceptions[try] = exception }
183
+ on_hash = { StandardError => nil, NonStandardError => [/foo/, /bar/] }
184
+
185
+ expect do
186
+ described_class.retriable(tries: 4, on: on_hash, on_retry: handler) do
187
+ increment_tries
188
+
189
+ case @tries
190
+ when 1
191
+ raise NonStandardError, "foo"
192
+ when 2
193
+ raise NonStandardError, "bar"
194
+ when 3
195
+ raise StandardError
196
+ else
197
+ raise NonStandardError, "crash"
198
+ end
304
199
  end
305
- end
306
- end.must_raise TestError
307
-
308
- expect(e.message).must_equal "crash"
309
- expect(exceptions[1].class).must_equal TestError
310
- expect(exceptions[1].message).must_equal "foo"
311
- expect(exceptions[2].class).must_equal TestError
312
- expect(exceptions[2].message).must_equal "bar"
313
- expect(exceptions[3].class).must_equal StandardError
314
- end
200
+ end.to raise_error(NonStandardError, /crash/)
315
201
 
316
- it "#retriable can be called in the global scope" do
317
- expect do
318
- retriable do
319
- puts "should raise NoMethodError"
320
- end
321
- end.must_raise NoMethodError
322
-
323
- require_relative "../lib/retriable/core_ext/kernel"
202
+ expect(exceptions[1]).to be_a(NonStandardError)
203
+ expect(exceptions[1].message).to eq("foo")
204
+ expect(exceptions[2]).to be_a(NonStandardError)
205
+ expect(exceptions[2].message).to eq("bar")
206
+ expect(exceptions[3]).to be_a(StandardError)
207
+ end
208
+ end
324
209
 
325
- tries = 0
210
+ it "runs for a max elapsed time of 2 seconds" do
211
+ described_class.configure { |c| c.sleep_disabled = false }
326
212
 
327
213
  expect do
328
- retriable do
329
- tries += 1
330
- raise StandardError
214
+ described_class.retriable(base_interval: 1.0, multiplier: 1.0, rand_factor: 0.0, max_elapsed_time: 2.0) do
215
+ increment_tries_with_exception
331
216
  end
332
- end.must_raise StandardError
333
-
334
- expect(tries).must_equal 3
335
- end
336
- end
217
+ end.to raise_error(StandardError)
337
218
 
338
- it "#retriable runs for a max elapsed time of 2 seconds" do
339
- subject.configure do |c|
340
- c.sleep_disabled = false
219
+ expect(@tries).to eq(2)
341
220
  end
342
221
 
343
- expect(subject.config.sleep_disabled).must_equal false
344
-
345
- tries = 0
346
- time_table = {}
347
-
348
- handler = lambda do |_exception, try, elapsed_time, _next_interval|
349
- time_table[try] = elapsed_time
222
+ it "raises ArgumentError on invalid options" do
223
+ expect { described_class.retriable(does_not_exist: 123) { increment_tries } }.to raise_error(ArgumentError)
350
224
  end
351
-
352
- expect do
353
- subject.retriable(
354
- base_interval: 1.0,
355
- multiplier: 1.0,
356
- rand_factor: 0.0,
357
- max_elapsed_time: 2.0,
358
- on_retry: handler,
359
- ) do
360
- tries += 1
361
- raise EOFError
362
- end
363
- end.must_raise EOFError
364
-
365
- expect(tries).must_equal 2
366
225
  end
367
226
 
368
- it "raises NoMethodError on invalid configuration" do
369
- assert_raises NoMethodError do
370
- Retriable.configure { |c| c.does_not_exist = 123 }
227
+ context "#configure" do
228
+ it "raises NoMethodError on invalid configuration" do
229
+ expect { described_class.configure { |c| c.does_not_exist = 123 } }.to raise_error(NoMethodError)
371
230
  end
372
231
  end
373
232
 
374
- it "raises ArgumentError on invalid option on #retriable" do
375
- assert_raises ArgumentError do
376
- Retriable.retriable(does_not_exist: 123)
377
- end
378
- end
233
+ context "#with_context" do
234
+ let(:api_tries) { 4 }
379
235
 
380
- describe "#with_context" do
381
236
  before do
382
- Retriable.configure do |c|
383
- c.sleep_disabled = true
237
+ described_class.configure do |c|
384
238
  c.contexts[:sql] = { tries: 1 }
385
- c.contexts[:api] = { tries: 3 }
239
+ c.contexts[:api] = { tries: api_tries }
386
240
  end
387
241
  end
388
242
 
389
- it "sql context stops at first try if the block does not raise an exception" do
390
- tries = 0
391
- subject.with_context(:sql) do
392
- tries += 1
393
- end
394
-
395
- expect(tries).must_equal 1
243
+ it "stops at first try if the block does not raise an exception" do
244
+ described_class.with_context(:sql) { increment_tries }
245
+ expect(@tries).to eq(1)
396
246
  end
397
247
 
398
- it "with_context respects the context options" do
399
- tries = 0
400
-
401
- expect do
402
- subject.with_context(:api) do
403
- tries += 1
404
- raise StandardError.new, "StandardError occurred"
405
- end
406
- end.must_raise StandardError
407
-
408
- expect(tries).must_equal 3
248
+ it "respects the context options" do
249
+ expect { described_class.with_context(:api) { increment_tries_with_exception } }.to raise_error(StandardError)
250
+ expect(@tries).to eq(api_tries)
409
251
  end
410
252
 
411
- it "with_context allows override options" do
412
- tries = 0
413
-
253
+ it "allows override options" do
414
254
  expect do
415
- subject.with_context(:sql, tries: 5) do
416
- tries += 1
417
- raise StandardError.new, "StandardError occurred"
418
- end
419
- end.must_raise StandardError
255
+ described_class.with_context(:sql, tries: 5) { increment_tries_with_exception }
256
+ end.to raise_error(StandardError)
420
257
 
421
- expect(tries).must_equal 5
258
+ expect(@tries).to eq(5)
422
259
  end
423
260
 
424
261
  it "raises an ArgumentError when the context isn't found" do
425
- tries = 0
426
-
427
- expect do
428
- subject.with_context(:wtf) do
429
- tries += 1
430
- end
431
- end.must_raise ArgumentError
262
+ expect { described_class.with_context(:wtf) { increment_tries } }.to raise_error(ArgumentError, /wtf not found/)
432
263
  end
433
264
  end
434
265
  end