attempt_this 0.9.0 → 0.9.1
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.
- data/Gemfile +1 -5
- data/Gemfile.lock +22 -5
- data/README.md +13 -10
- data/attempt_this.gemspec +16 -9
- data/lib/attempt_this/attempt_object.rb +121 -129
- data/lib/attempt_this/attempt_this.rb +11 -11
- data/lib/attempt_this/binary_backoff_policy.rb +12 -12
- data/lib/attempt_this/exception_type_filter.rb +11 -11
- data/spec/attempt_spec.rb +292 -287
- data/spec/scenarios_spec.rb +47 -47
- data/spec/spec_helper.rb +1 -0
- metadata +98 -12
- checksums.yaml +0 -7
@@ -1,15 +1,15 @@
|
|
1
1
|
module AttemptThis
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
2
|
+
# Implementation of binary backoff policy. Internal use only.
|
3
|
+
class BinaryBackoffPolicy
|
4
|
+
# Initializer.
|
5
|
+
def initialize(initial_delay)
|
6
|
+
@delay = initial_delay
|
7
|
+
end
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
# Calls the policy.
|
10
|
+
def call
|
11
|
+
Kernel.sleep(@delay)
|
12
|
+
@delay *= 2
|
13
|
+
end
|
14
|
+
end
|
15
15
|
end
|
@@ -1,14 +1,14 @@
|
|
1
1
|
module AttemptThis
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
2
|
+
# Type-based exception filter.
|
3
|
+
class ExceptionTypeFilter
|
4
|
+
# Initializer.
|
5
|
+
def initialize(exception_classes)
|
6
|
+
@exception_classes = Array.new(exception_classes)
|
7
|
+
end
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
# Tells whether the given exception satisfies the filter.
|
10
|
+
def include?(exception)
|
11
|
+
@exception_classes.any?{|klass| exception.is_a?(klass)}
|
12
|
+
end
|
13
|
+
end
|
14
14
|
end
|
data/spec/attempt_spec.rb
CHANGED
@@ -1,291 +1,296 @@
|
|
1
1
|
require 'spec_helper.rb'
|
2
2
|
|
3
3
|
describe AttemptThis do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
4
|
+
include AttemptThis
|
5
|
+
|
6
|
+
context 'attempt' do
|
7
|
+
it 'should reject nil enumerator' do
|
8
|
+
->{attempt(nil)}.should raise_error(ArgumentError)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should allow execution without a code block' do
|
12
|
+
->{attempt(3.times)}.should_not raise_error
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should execute code block' do
|
16
|
+
was_called = false
|
17
|
+
attempt(1.times) {was_called = true}
|
18
|
+
was_called.should be_true
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should execute the code block only once' do
|
22
|
+
call_count = 0
|
23
|
+
attempt(3.times) { call_count += 1 }
|
24
|
+
call_count.should eql(1)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should not execute the code' do
|
28
|
+
call_count = 0
|
29
|
+
attempt(0.times) { call_count += 1 }
|
30
|
+
|
31
|
+
call_count.should eql(0)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should re-throw the original exception' do
|
35
|
+
->{attempt(2.times){raise 'Test'}}.should raise_error('Test')
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should attempt 3 times' do
|
39
|
+
call_count = 0
|
40
|
+
->{attempt(3.times) { call_count += 1; raise 'Test'}}.should raise_error('Test')
|
41
|
+
call_count.should eql(3)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should stop trying after a successful attempt' do
|
45
|
+
attempt_count = 0
|
46
|
+
attempt(3.times) do
|
47
|
+
attempt_count += 1
|
48
|
+
raise 'Test' if attempt_count < 2
|
49
|
+
end
|
50
|
+
|
51
|
+
attempt_count.should eql(2)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should return block\'s value' do
|
55
|
+
expected = UUID.generate.to_s
|
56
|
+
attempt(3.times) { expected }.should eql(expected)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'with_delay' do
|
61
|
+
it 'should reject nil delay' do
|
62
|
+
->{attempt(3.times).with_delay(nil)}.should raise_error(ArgumentError)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should reject negative delay' do
|
66
|
+
->{attempt(3.times).with_delay(-1)}.should raise_error(ArgumentError)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should reject non-number delay' do
|
70
|
+
->{attempt(3.times).with_delay('foo')}.should raise_error(ArgumentError)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should accept floating point delay' do
|
74
|
+
->{attempt(3.times).with_delay(1.5)}.should_not raise_error
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should accept zero delay' do
|
78
|
+
->{attempt(3.times).with_delay(0)}.should_not raise_error
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should accept calls without a code block' do
|
82
|
+
->{attempt(3.times).with_delay(3)}.should_not raise_error
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should call the code block' do
|
86
|
+
was_called = false
|
87
|
+
attempt(3.times).with_delay(1) do
|
88
|
+
was_called = true
|
89
|
+
end
|
90
|
+
was_called.should be_true
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'should not sleep on success' do
|
94
|
+
Kernel.should_not_receive(:sleep)
|
95
|
+
attempt(3.times).with_delay(3) {}
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should sleep for given number of seconds between failed attempts' do
|
99
|
+
Kernel.should_receive(:sleep).with(5).exactly(2).times
|
100
|
+
->{attempt(3.times).with_delay(5) {raise 'Test'}}.should raise_error('Test')
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should not fail on zero delay' do
|
104
|
+
->{attempt(3.times).with_delay(0) { raise 'Test' }}.should raise_error('Test')
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'should reject negative start' do
|
108
|
+
->{attempt(3.times).with_delay(-1..1)}.should raise_error(ArgumentError)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should reject negative end' do
|
112
|
+
->{attempt(3.times).with_delay(1..-1)}.should raise_error(ArgumentError)
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'should reject non-number range' do
|
116
|
+
->{attempt(3.times).with_delay('x'..'y')}.should raise_error(ArgumentError)
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'should accept floating point range' do
|
120
|
+
->{attempt(3.times).with_delay(1.5..3)}.should_not raise_error
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'should reject inverse range' do
|
124
|
+
->{attempt(2.times).with_delay(3..1)}.should raise_error(ArgumentError)
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'should accept zero seconds interval' do
|
128
|
+
->{attempt(3.times).with_delay(0..0)}.should_not raise_error
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'should wait for specified number of seconds' do
|
132
|
+
Kernel.should_receive(:sleep).with(5).exactly(2).times
|
133
|
+
->{attempt(3.times).with_delay(5..5){raise 'Test'}}.should raise_error('Test')
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'should reject multiple delay policies' do
|
137
|
+
->{attempt(3.times).with_delay(1).with_delay(1)}.should raise_error(ArgumentError)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context 'with_reset' do
|
142
|
+
it 'should reject nil reset proc' do
|
143
|
+
->{attempt(3.times).with_reset(nil)}.should raise_error(ArgumentError)
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'should accept calls without a code block' do
|
147
|
+
->{attempt(3.times).with_reset(->{})}.should_not raise_error
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'should call the code block' do
|
151
|
+
was_called = false
|
152
|
+
attempt(1.times).with_reset(->{}) { was_called = true }
|
153
|
+
|
154
|
+
was_called.should be_true
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'should reject multiple reset procs' do
|
158
|
+
->{attempt(3.times).with_reset(->{}).with_reset(->{})}.should raise_error(ArgumentError)
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'should not be called on successful calls' do
|
162
|
+
was_called = false
|
163
|
+
|
164
|
+
attempt(1.times).with_reset(->{ was_called = true }) {}
|
165
|
+
was_called.should be_false
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'should be called on each failure' do
|
169
|
+
reset_count = 0
|
170
|
+
|
171
|
+
->{attempt(3.times).with_reset(->{ reset_count += 1 }) { raise 'Test' }}.should raise_error('Test')
|
172
|
+
reset_count.should eql(3)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
context 'and_default_to' do
|
177
|
+
it 'should reject nil default method' do
|
178
|
+
->{attempt(3.times).and_default_to(nil)}.should raise_error(ArgumentError)
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'should reject duplicate default methods' do
|
182
|
+
->{attempt(3.times).and_default_to(->{}).and_default_to(->{})}.should raise_error(ArgumentError)
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'should allow calls without a code block' do
|
186
|
+
->{attempt(3.times).and_default_to(->{})}.should_not raise_error
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'should call the code block' do
|
190
|
+
was_called = false
|
191
|
+
attempt(3.times).and_default_to(->{}){ was_called = true }
|
192
|
+
|
193
|
+
was_called.should be_true
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'should not be called on success' do
|
197
|
+
was_called = false
|
198
|
+
attempt(3.times).and_default_to(->{ was_called = true }) {}
|
199
|
+
was_called.should be_false
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'should be called once on the failure' do
|
203
|
+
call_count = 0
|
204
|
+
attempt(3.times).and_default_to(->{ call_count += 1 }){ raise 'Test'}
|
205
|
+
|
206
|
+
call_count.should eql(1)
|
207
|
+
end
|
208
|
+
|
209
|
+
it 'should not be called if code block stopped failing' do
|
210
|
+
call_count = 0
|
211
|
+
was_called = false
|
212
|
+
|
213
|
+
attempt(3.times).and_default_to(->{ was_called = true }) { call_count += 1; raise 'Test' if call_count < 2 }
|
214
|
+
was_called.should be_false
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
context 'with_binary_backoff' do
|
219
|
+
it 'should reject nil initial delay' do
|
220
|
+
->{attempt(3.times).with_binary_backoff(nil)}.should raise_error(ArgumentError)
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'should reject non-integer initial delay' do
|
224
|
+
->{attempt(3.times).with_binary_backoff('foo')}.should raise_error(ArgumentError)
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'should reject zero initial delay' do
|
228
|
+
->{attempt(3.times).with_binary_backoff(0)}.should raise_error(ArgumentError)
|
229
|
+
end
|
230
|
+
|
231
|
+
it 'should reject negative initial delay' do
|
232
|
+
->{attempt(3.times).with_binary_backoff(-1)}.should raise_error(ArgumentError)
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'should reject multiple policies' do
|
236
|
+
->{attempt(3.times).with_binary_backoff(1).with_binary_backoff(2)}.should raise_error(ArgumentError)
|
237
|
+
end
|
238
|
+
|
239
|
+
it 'should accept calls without a code block' do
|
240
|
+
->{attempt(3.times).with_binary_backoff(1)}.should_not raise_error
|
241
|
+
end
|
242
|
+
|
243
|
+
it 'should call the code block' do
|
244
|
+
was_called = false
|
245
|
+
|
246
|
+
attempt(3.times).with_binary_backoff(1) { was_called = true }
|
247
|
+
was_called.should be_true
|
248
|
+
end
|
249
|
+
|
250
|
+
it 'should double delay on each failure' do
|
251
|
+
Kernel.should_receive(:sleep).ordered.with(1)
|
252
|
+
Kernel.should_receive(:sleep).ordered.with(2)
|
253
|
+
Kernel.should_receive(:sleep).ordered.with(4)
|
254
|
+
|
255
|
+
attempt(4.times).with_binary_backoff(1).and_default_to(->{}) { raise 'Test' }
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
context 'with_filter' do
|
260
|
+
it 'should reject empty exceptions list' do
|
261
|
+
->{attempt.with_filter}.should raise_error(ArgumentError)
|
262
|
+
end
|
263
|
+
|
264
|
+
it 'should reject non-exceptions' do
|
265
|
+
->{attempt.with_filter(1)}.should raise_error(ArgumentError)
|
266
|
+
end
|
267
|
+
|
268
|
+
it 'should accept calls without a block' do
|
269
|
+
->{attempt(2.times).with_filter(Exception)}.should_not raise_error
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'should call code within the block' do
|
273
|
+
was_called = false
|
274
|
+
attempt(2.times).with_filter(Exception){ was_called = true }
|
275
|
+
was_called.should be_true
|
276
|
+
end
|
277
|
+
|
278
|
+
it 'should ignore other exceptions' do
|
279
|
+
count = 0
|
280
|
+
->{attempt(3.times).with_filter(StandardError){ count += 1; raise(Exception, 'Test')}}.should raise_error(Exception)
|
281
|
+
count.should eql(1)
|
282
|
+
end
|
283
|
+
|
284
|
+
it 'should not ignore specified exceptions' do
|
285
|
+
count = 0
|
286
|
+
->{attempt(3.times).with_filter(RuntimeError){ count += 1; raise 'Test'}}.should raise_error(RuntimeError)
|
287
|
+
count.should eql(3)
|
288
|
+
end
|
289
|
+
|
290
|
+
it 'should not ignore derived exceptions' do
|
291
|
+
count = 0
|
292
|
+
->{attempt(3.times).with_filter(Exception){ count += 1; raise(StandardError, 'Test')}}.should raise_error(StandardError)
|
293
|
+
count.should eql(3)
|
294
|
+
end
|
295
|
+
end
|
291
296
|
end
|