attempt_this 0.8.1 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +25 -0
- data/README.md +84 -0
- data/Rakefile +5 -0
- data/attempt_this.gemspec +15 -0
- data/lib/attempt_this/attempt_object.rb +22 -0
- data/lib/attempt_this/attempt_this.rb +18 -0
- data/lib/attempt_this.rb +1 -10
- data/spec/attempt_spec.rb +291 -0
- data/spec/scenarios_spec.rb +51 -0
- data/spec/spec_helper.rb +4 -0
- metadata +19 -14
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 99adc9216b884599d6cdf76e1232bd76ac60f58f
|
4
|
+
data.tar.gz: f284433eefff3989b31ffefe6438264c19b7c6ee
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5b26edd0f899dd2d1f37ca0ea35a888b4a246e565b190a8e970cff6c85bc78ab81177c5f5f1687e8fcf7f776c58c5944a800e639d81e21a387aefd08f3cc7827
|
7
|
+
data.tar.gz: 8938c34e49c4ea02ed9123c655900e62914b72895a84d75d24bc656268340be9ca66e5ebf9430984870e050f1ad9178b05aca70aa12cf593d5a1a4972e121dd1
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
diff-lcs (1.2.4)
|
5
|
+
multi_json (1.7.7)
|
6
|
+
rspec (2.13.0)
|
7
|
+
rspec-core (~> 2.13.0)
|
8
|
+
rspec-expectations (~> 2.13.0)
|
9
|
+
rspec-mocks (~> 2.13.0)
|
10
|
+
rspec-core (2.13.1)
|
11
|
+
rspec-expectations (2.13.0)
|
12
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
13
|
+
rspec-mocks (2.13.1)
|
14
|
+
simplecov (0.7.1)
|
15
|
+
multi_json (~> 1.0)
|
16
|
+
simplecov-html (~> 0.7.1)
|
17
|
+
simplecov-html (0.7.1)
|
18
|
+
|
19
|
+
PLATFORMS
|
20
|
+
ruby
|
21
|
+
x86-mingw32
|
22
|
+
|
23
|
+
DEPENDENCIES
|
24
|
+
rspec
|
25
|
+
simplecov
|
data/README.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
attempt_this
|
2
|
+
============
|
3
|
+
|
4
|
+
Exception-based retry policy mix-in for Ruby project. Its purpose it to retry a code block given number of times if it throws an exception.
|
5
|
+
```ruby
|
6
|
+
|
7
|
+
attempt(3.times) do
|
8
|
+
# Do something
|
9
|
+
end
|
10
|
+
```
|
11
|
+
This will retry the code block up to three times. If the last attempt will result in an exception, that exception will be thrown outside of the attempt block.
|
12
|
+
If you don't like that behavior, you can specify a function to be called after all attempts have failed.
|
13
|
+
```ruby
|
14
|
+
is_failed = false
|
15
|
+
attempt(3.times)
|
16
|
+
.and_default_to(->{is_failed = true}) do
|
17
|
+
# Do something
|
18
|
+
end
|
19
|
+
```
|
20
|
+
|
21
|
+
You may want to retry on specific exception types:
|
22
|
+
```ruby
|
23
|
+
attempt(3.times)
|
24
|
+
.with_filter(RecoverableError1, RecoverableError2) do
|
25
|
+
# Do something
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
You may chose how to reset the environment between failed attempts. This is useful for transactions:
|
30
|
+
```ruby
|
31
|
+
attempt(3.times)
|
32
|
+
.with_reset(->{rollback}) do
|
33
|
+
start_transaction
|
34
|
+
# Do something
|
35
|
+
commit_transaction
|
36
|
+
end
|
37
|
+
```
|
38
|
+
You can specify delay between failed attempts:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
|
42
|
+
# Wait for 5 seconds between failures.
|
43
|
+
attempt(3.times)
|
44
|
+
.with_delay(5) do
|
45
|
+
# Do something
|
46
|
+
end
|
47
|
+
|
48
|
+
# Random delay between 30 and 60 seconds.
|
49
|
+
attempt(3.times)
|
50
|
+
.with_delay([30..60]) do
|
51
|
+
# Do something
|
52
|
+
end
|
53
|
+
|
54
|
+
# Start with 10 seconds delay and double it after each failed attempt.
|
55
|
+
attempt(5.times)
|
56
|
+
.with_binary_backoff(10) do
|
57
|
+
# Do something
|
58
|
+
end
|
59
|
+
```
|
60
|
+
|
61
|
+
Of course, you can combine multiple options together:
|
62
|
+
```ruby
|
63
|
+
attempt(3.times)
|
64
|
+
.with_delay(30)
|
65
|
+
.with_reset(->{rollback})
|
66
|
+
.and_default_to(->{is_failed = true}) do
|
67
|
+
# Do something
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
71
|
+
Finally, you can store various configurations as scenarios and use them by name. This is useful when you need different approaches for specific cases (handling HTTP failures, for example). But remember that you should register your scenarios before using them:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
# Run this code when the application starts
|
75
|
+
AttemptThis.attempt(5.times).with_filter(*RECOVERABLE_HTTP_ERRORS).scenario(:http)
|
76
|
+
|
77
|
+
|
78
|
+
# And run this from your method:
|
79
|
+
attempt(:http) do
|
80
|
+
# Make an HTTP call
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
Enjoy! And feel free to contribute; just make sure you haven't broken any tests by running 'rake' from project's root.
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'attempt_this'
|
3
|
+
s.version = '0.9.0'
|
4
|
+
s.date = '2013-05-05'
|
5
|
+
s.summary = 'Retry policy mix-in'
|
6
|
+
s.description = <<EOM
|
7
|
+
Retry policy mix-in with configurable number of attempts, delays, exception filters, and fall back strategies.
|
8
|
+
|
9
|
+
See project's home page for usage examples and more information.
|
10
|
+
EOM
|
11
|
+
s.authors = ['Aliaksei Baturytski']
|
12
|
+
s.email = 'abaturytski@gmail.com'
|
13
|
+
s.files = `git ls-files`.split($/)
|
14
|
+
s.homepage = 'https://github.com/aliakb/attempt_this'
|
15
|
+
end
|
@@ -5,6 +5,19 @@ module AttemptThis
|
|
5
5
|
# Retry policy implementation.
|
6
6
|
# This class is internal and is not supposed to be used outside of the module.
|
7
7
|
class AttemptObject
|
8
|
+
@@scenarios = {} # All registered scenarios
|
9
|
+
|
10
|
+
# Resets all static data.
|
11
|
+
def self.reset
|
12
|
+
@@scenarios = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.get_object(id_or_enumerator)
|
16
|
+
impl = @@scenarios[id_or_enumerator]
|
17
|
+
impl ||= AttemptObject.new(id_or_enumerator)
|
18
|
+
impl
|
19
|
+
end
|
20
|
+
|
8
21
|
# Initializes object with enumerator.
|
9
22
|
def initialize(enumerator)
|
10
23
|
@enumerator = enumerator
|
@@ -20,6 +33,7 @@ module AttemptThis
|
|
20
33
|
@reset_method = ->{} unless @reset_method
|
21
34
|
@exception_filter = ExceptionTypeFilter.new([StandardError]) unless @exception_filter
|
22
35
|
|
36
|
+
@enumerator.rewind
|
23
37
|
@enumerator.each do
|
24
38
|
@delay_policy.call unless first_time
|
25
39
|
last_exception = nil
|
@@ -108,5 +122,13 @@ module AttemptThis
|
|
108
122
|
@exception_filter = ExceptionTypeFilter.new(exceptions)
|
109
123
|
attempt(block)
|
110
124
|
end
|
125
|
+
|
126
|
+
# Creates a scenario with the given id.
|
127
|
+
def scenario(id)
|
128
|
+
raise(ArgumentError, 'Blank id!') if id.nil? || id.empty?
|
129
|
+
raise(ArgumentError, "There is already a scenario with id #{id}") if @@scenarios.has_key?(id)
|
130
|
+
|
131
|
+
@@scenarios[id] = self
|
132
|
+
end
|
111
133
|
end
|
112
134
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'attempt_this/attempt_object.rb'
|
2
|
+
|
3
|
+
module AttemptThis
|
4
|
+
extend self
|
5
|
+
|
6
|
+
# Attempts code block until it doesn't throw an exception or the end of enumerator has been reached.
|
7
|
+
def attempt(enumerator, &block)
|
8
|
+
raise(ArgumentError, 'Nil enumerator!') if enumerator.nil?
|
9
|
+
|
10
|
+
impl = AttemptObject::get_object(enumerator)
|
11
|
+
impl.attempt(block)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Resets all static data (scenarios). This is intended to use by tests only (to reset scenarios)
|
15
|
+
def self.reset
|
16
|
+
AttemptObject.reset
|
17
|
+
end
|
18
|
+
end
|
data/lib/attempt_this.rb
CHANGED
@@ -1,11 +1,2 @@
|
|
1
|
-
require 'attempt_this/
|
1
|
+
require 'attempt_this/attempt_this'
|
2
2
|
|
3
|
-
module AttemptThis
|
4
|
-
# Attempts code block until it doesn't throw an exception or the end of enumerator has been reached.
|
5
|
-
def attempt(enumerator, &block)
|
6
|
-
raise(ArgumentError, 'Nil enumerator!') if enumerator.nil?
|
7
|
-
|
8
|
-
impl = AttemptObject.new(enumerator)
|
9
|
-
impl.attempt(block)
|
10
|
-
end
|
11
|
-
end
|
@@ -0,0 +1,291 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
|
3
|
+
describe AttemptThis do
|
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
|
+
end
|
54
|
+
|
55
|
+
context 'with_delay' do
|
56
|
+
it 'should reject nil delay' do
|
57
|
+
->{attempt(3.times).with_delay(nil)}.should raise_error(ArgumentError)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'should reject negative delay' do
|
61
|
+
->{attempt(3.times).with_delay(-1)}.should raise_error(ArgumentError)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should reject non-number delay' do
|
65
|
+
->{attempt(3.times).with_delay('foo')}.should raise_error(ArgumentError)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should accept floating point delay' do
|
69
|
+
->{attempt(3.times).with_delay(1.5)}.should_not raise_error
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should accept zero delay' do
|
73
|
+
->{attempt(3.times).with_delay(0)}.should_not raise_error
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should accept calls without a code block' do
|
77
|
+
->{attempt(3.times).with_delay(3)}.should_not raise_error
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should call the code block' do
|
81
|
+
was_called = false
|
82
|
+
attempt(3.times).with_delay(1) do
|
83
|
+
was_called = true
|
84
|
+
end
|
85
|
+
was_called.should be_true
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'should not sleep on success' do
|
89
|
+
Kernel.should_not_receive(:sleep)
|
90
|
+
attempt(3.times).with_delay(3) {}
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'should sleep for given number of seconds between failed attempts' do
|
94
|
+
Kernel.should_receive(:sleep).with(5).exactly(2).times
|
95
|
+
->{attempt(3.times).with_delay(5) {raise 'Test'}}.should raise_error('Test')
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should not fail on zero delay' do
|
99
|
+
->{attempt(3.times).with_delay(0) { raise 'Test' }}.should raise_error('Test')
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'should reject negative start' do
|
103
|
+
->{attempt(3.times).with_delay(-1..1)}.should raise_error(ArgumentError)
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should reject negative end' do
|
107
|
+
->{attempt(3.times).with_delay(1..-1)}.should raise_error(ArgumentError)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should reject non-number range' do
|
111
|
+
->{attempt(3.times).with_delay('x'..'y')}.should raise_error(ArgumentError)
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'should accept floating point range' do
|
115
|
+
->{attempt(3.times).with_delay(1.5..3)}.should_not raise_error
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'should reject inverse range' do
|
119
|
+
->{attempt(2.times).with_delay(3..1)}.should raise_error(ArgumentError)
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'should accept zero seconds interval' do
|
123
|
+
->{attempt(3.times).with_delay(0..0)}.should_not raise_error
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'should wait for specified number of seconds' do
|
127
|
+
Kernel.should_receive(:sleep).with(5).exactly(2).times
|
128
|
+
->{attempt(3.times).with_delay(5..5){raise 'Test'}}.should raise_error('Test')
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'should reject multiple delay policies' do
|
132
|
+
->{attempt(3.times).with_delay(1).with_delay(1)}.should raise_error(ArgumentError)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
context 'with_reset' do
|
137
|
+
it 'should reject nil reset proc' do
|
138
|
+
->{attempt(3.times).with_reset(nil)}.should raise_error(ArgumentError)
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'should accept calls without a code block' do
|
142
|
+
->{attempt(3.times).with_reset(->{})}.should_not raise_error
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'should call the code block' do
|
146
|
+
was_called = false
|
147
|
+
attempt(1.times).with_reset(->{}) { was_called = true }
|
148
|
+
|
149
|
+
was_called.should be_true
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'should reject multiple reset procs' do
|
153
|
+
->{attempt(3.times).with_reset(->{}).with_reset(->{})}.should raise_error(ArgumentError)
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'should not be called on successful calls' do
|
157
|
+
was_called = false
|
158
|
+
|
159
|
+
attempt(1.times).with_reset(->{ was_called = true }) {}
|
160
|
+
was_called.should be_false
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'should be called on each failure' do
|
164
|
+
reset_count = 0
|
165
|
+
|
166
|
+
->{attempt(3.times).with_reset(->{ reset_count += 1 }) { raise 'Test' }}.should raise_error('Test')
|
167
|
+
reset_count.should eql(3)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
context 'and_default_to' do
|
172
|
+
it 'should reject nil default method' do
|
173
|
+
->{attempt(3.times).and_default_to(nil)}.should raise_error(ArgumentError)
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'should reject duplicate default methods' do
|
177
|
+
->{attempt(3.times).and_default_to(->{}).and_default_to(->{})}.should raise_error(ArgumentError)
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'should allow calls without a code block' do
|
181
|
+
->{attempt(3.times).and_default_to(->{})}.should_not raise_error
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'should call the code block' do
|
185
|
+
was_called = false
|
186
|
+
attempt(3.times).and_default_to(->{}){ was_called = true }
|
187
|
+
|
188
|
+
was_called.should be_true
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'should not be called on success' do
|
192
|
+
was_called = false
|
193
|
+
attempt(3.times).and_default_to(->{ was_called = true }) {}
|
194
|
+
was_called.should be_false
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'should be called once on the failure' do
|
198
|
+
call_count = 0
|
199
|
+
attempt(3.times).and_default_to(->{ call_count += 1 }){ raise 'Test'}
|
200
|
+
|
201
|
+
call_count.should eql(1)
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'should not be called if code block stopped failing' do
|
205
|
+
call_count = 0
|
206
|
+
was_called = false
|
207
|
+
|
208
|
+
attempt(3.times).and_default_to(->{ was_called = true }) { call_count += 1; raise 'Test' if call_count < 2 }
|
209
|
+
was_called.should be_false
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
context 'with_binary_backoff' do
|
214
|
+
it 'should reject nil initial delay' do
|
215
|
+
->{attempt(3.times).with_binary_backoff(nil)}.should raise_error(ArgumentError)
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'should reject non-integer initial delay' do
|
219
|
+
->{attempt(3.times).with_binary_backoff('foo')}.should raise_error(ArgumentError)
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'should reject zero initial delay' do
|
223
|
+
->{attempt(3.times).with_binary_backoff(0)}.should raise_error(ArgumentError)
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'should reject negative initial delay' do
|
227
|
+
->{attempt(3.times).with_binary_backoff(-1)}.should raise_error(ArgumentError)
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'should reject multiple policies' do
|
231
|
+
->{attempt(3.times).with_binary_backoff(1).with_binary_backoff(2)}.should raise_error(ArgumentError)
|
232
|
+
end
|
233
|
+
|
234
|
+
it 'should accept calls without a code block' do
|
235
|
+
->{attempt(3.times).with_binary_backoff(1)}.should_not raise_error
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'should call the code block' do
|
239
|
+
was_called = false
|
240
|
+
|
241
|
+
attempt(3.times).with_binary_backoff(1) { was_called = true }
|
242
|
+
was_called.should be_true
|
243
|
+
end
|
244
|
+
|
245
|
+
it 'should double delay on each failure' do
|
246
|
+
Kernel.should_receive(:sleep).ordered.with(1)
|
247
|
+
Kernel.should_receive(:sleep).ordered.with(2)
|
248
|
+
Kernel.should_receive(:sleep).ordered.with(4)
|
249
|
+
|
250
|
+
attempt(4.times).with_binary_backoff(1).and_default_to(->{}) { raise 'Test' }
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
context 'with_filter' do
|
255
|
+
it 'should reject empty exceptions list' do
|
256
|
+
->{attempt.with_filter}.should raise_error(ArgumentError)
|
257
|
+
end
|
258
|
+
|
259
|
+
it 'should reject non-exceptions' do
|
260
|
+
->{attempt.with_filter(1)}.should raise_error(ArgumentError)
|
261
|
+
end
|
262
|
+
|
263
|
+
it 'should accept calls without a block' do
|
264
|
+
->{attempt(2.times).with_filter(Exception)}.should_not raise_error
|
265
|
+
end
|
266
|
+
|
267
|
+
it 'should call code within the block' do
|
268
|
+
was_called = false
|
269
|
+
attempt(2.times).with_filter(Exception){ was_called = true }
|
270
|
+
was_called.should be_true
|
271
|
+
end
|
272
|
+
|
273
|
+
it 'should ignore other exceptions' do
|
274
|
+
count = 0
|
275
|
+
->{attempt(3.times).with_filter(StandardError){ count += 1; raise(Exception, 'Test')}}.should raise_error(Exception)
|
276
|
+
count.should eql(1)
|
277
|
+
end
|
278
|
+
|
279
|
+
it 'should not ignore specified exceptions' do
|
280
|
+
count = 0
|
281
|
+
->{attempt(3.times).with_filter(RuntimeError){ count += 1; raise 'Test'}}.should raise_error(RuntimeError)
|
282
|
+
count.should eql(3)
|
283
|
+
end
|
284
|
+
|
285
|
+
it 'should not ignore derived exceptions' do
|
286
|
+
count = 0
|
287
|
+
->{attempt(3.times).with_filter(Exception){ count += 1; raise(StandardError, 'Test')}}.should raise_error(StandardError)
|
288
|
+
count.should eql(3)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AttemptThis do
|
4
|
+
include AttemptThis
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
AttemptThis.reset
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'validation' do
|
11
|
+
it 'should reject nil scenario id' do
|
12
|
+
->{AttemptThis.attempt(3.times).scenario(nil)}.should raise_error(ArgumentError)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should reject empty scenario id' do
|
16
|
+
->{AttemptThis.attempt(3.times).scenario('')}.should raise_error(ArgumentError)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should accept string ids' do
|
20
|
+
->{AttemptThis.attempt(3.times).scenario('uploads')}.should_not raise_error
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should accept symbol ids' do
|
24
|
+
->{AttemptThis.attempt(3.times).scenario(:uploads)}.should_not raise_error
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should reject duplicate names' do
|
28
|
+
AttemptThis.attempt(3.times).scenario(:uploads)
|
29
|
+
->{AttemptThis.attempt(3.times).scenario(:uploads)}.should raise_error(ArgumentError)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'operation' do
|
34
|
+
it 'should attempt given number of times' do
|
35
|
+
AttemptThis.attempt(3.times).scenario(:test)
|
36
|
+
count = 0
|
37
|
+
|
38
|
+
->{attempt(:test) { count += 1; raise 'Test' }}.should raise_error('Test')
|
39
|
+
count.should eql(3)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should reuse scenario' do
|
43
|
+
AttemptThis.attempt(3.times).scenario(:test)
|
44
|
+
->{attempt(:test) { raise 'Test'}}.should raise_error('Test')
|
45
|
+
|
46
|
+
count = 0
|
47
|
+
->{attempt(:test) {count += 1; raise 'Test'}}.should raise_error('Test')
|
48
|
+
count.should eql(3)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: attempt_this
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.9.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Aliaksei Baturytski
|
@@ -11,44 +10,50 @@ bindir: bin
|
|
11
10
|
cert_chain: []
|
12
11
|
date: 2013-05-05 00:00:00.000000000 Z
|
13
12
|
dependencies: []
|
14
|
-
description:
|
15
|
-
exception filters, and fall back strategies.
|
13
|
+
description: |
|
14
|
+
Retry policy mix-in with configurable number of attempts, delays, exception filters, and fall back strategies.
|
16
15
|
|
17
|
-
|
18
|
-
See project''s home page for usage examples and more information.
|
19
|
-
|
20
|
-
'
|
16
|
+
See project's home page for usage examples and more information.
|
21
17
|
email: abaturytski@gmail.com
|
22
18
|
executables: []
|
23
19
|
extensions: []
|
24
20
|
extra_rdoc_files: []
|
25
21
|
files:
|
22
|
+
- .gitignore
|
23
|
+
- Gemfile
|
24
|
+
- Gemfile.lock
|
25
|
+
- README.md
|
26
|
+
- Rakefile
|
27
|
+
- attempt_this.gemspec
|
26
28
|
- lib/attempt_this.rb
|
27
29
|
- lib/attempt_this/attempt_object.rb
|
30
|
+
- lib/attempt_this/attempt_this.rb
|
28
31
|
- lib/attempt_this/binary_backoff_policy.rb
|
29
32
|
- lib/attempt_this/exception_type_filter.rb
|
33
|
+
- spec/attempt_spec.rb
|
34
|
+
- spec/scenarios_spec.rb
|
35
|
+
- spec/spec_helper.rb
|
30
36
|
homepage: https://github.com/aliakb/attempt_this
|
31
37
|
licenses: []
|
38
|
+
metadata: {}
|
32
39
|
post_install_message:
|
33
40
|
rdoc_options: []
|
34
41
|
require_paths:
|
35
42
|
- lib
|
36
43
|
required_ruby_version: !ruby/object:Gem::Requirement
|
37
|
-
none: false
|
38
44
|
requirements:
|
39
|
-
- -
|
45
|
+
- - '>='
|
40
46
|
- !ruby/object:Gem::Version
|
41
47
|
version: '0'
|
42
48
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
43
|
-
none: false
|
44
49
|
requirements:
|
45
|
-
- -
|
50
|
+
- - '>='
|
46
51
|
- !ruby/object:Gem::Version
|
47
52
|
version: '0'
|
48
53
|
requirements: []
|
49
54
|
rubyforge_project:
|
50
|
-
rubygems_version:
|
55
|
+
rubygems_version: 2.0.3
|
51
56
|
signing_key:
|
52
|
-
specification_version:
|
57
|
+
specification_version: 4
|
53
58
|
summary: Retry policy mix-in
|
54
59
|
test_files: []
|