retriable 3.1.1 → 3.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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