retry_block 1.0.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/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Rakefile +5 -0
- data/lib/retry_block/version.rb +3 -0
- data/lib/retry_block.rb +38 -0
- data/retry_block.gemspec +20 -0
- data/test/test_retry_block.rb +174 -0
- metadata +72 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
data/lib/retry_block.rb
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require "retry_block/version"
|
|
2
|
+
|
|
3
|
+
module Kernel
|
|
4
|
+
def retry_block (opts={}, &block)
|
|
5
|
+
opts = {
|
|
6
|
+
:attempts => 1, # Number of times to try block. Nil means to retry forever until success
|
|
7
|
+
:sleep => 0, # Seconds to sleep between attempts
|
|
8
|
+
:catch => Exception, # An exception or array of exceptions to listen for
|
|
9
|
+
:fail_callback => nil # Proc/lambda that gets executed between attempts
|
|
10
|
+
}.merge(opts)
|
|
11
|
+
|
|
12
|
+
opts[:catch] = [ opts[:catch] ].flatten
|
|
13
|
+
attempts = 1
|
|
14
|
+
|
|
15
|
+
if (not opts[:attempts].nil?) and (not opts[:attempts].is_a? Integer or opts[:attempts] <= 0)
|
|
16
|
+
raise ArgumentError, "retry_block: :attempts must be an integer >= 0 or nil"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
begin
|
|
20
|
+
return yield attempts
|
|
21
|
+
rescue *opts[:catch] => exception
|
|
22
|
+
|
|
23
|
+
# If a callable object was given for :fail_callback then call it
|
|
24
|
+
if opts[:fail_callback].respond_to? :call
|
|
25
|
+
callback_opts = [attempts, exception].slice(0, opts[:fail_callback].arity)
|
|
26
|
+
opts[:fail_callback].call *callback_opts
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# If we've maxed out our attempts, raise the exception to the calling code
|
|
30
|
+
raise if (not opts[:attempts].nil?) and (attempts += 1) > opts[:attempts]
|
|
31
|
+
|
|
32
|
+
# Sleep before the next retry if the option was given
|
|
33
|
+
sleep opts[:sleep] if opts[:sleep].is_a? Numeric and opts[:sleep] > 0
|
|
34
|
+
|
|
35
|
+
retry
|
|
36
|
+
end #rescue
|
|
37
|
+
end # def
|
|
38
|
+
end # module
|
data/retry_block.gemspec
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
3
|
+
require "retry_block/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = "retry_block"
|
|
7
|
+
s.version = RetryBlock::VERSION
|
|
8
|
+
s.authors = ["Alfred J. Fazio"]
|
|
9
|
+
s.email = ["alfred.fazio@gmail.com"]
|
|
10
|
+
s.homepage = ""
|
|
11
|
+
s.summary = %q{Take control of unstable or indeterminate code with retry_block}
|
|
12
|
+
s.description = %q{Take control of unstable or indeterminate code with retry_block}
|
|
13
|
+
|
|
14
|
+
s.rubyforge_project = "retry_block"
|
|
15
|
+
|
|
16
|
+
s.files = `git ls-files`.split("\n")
|
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
19
|
+
s.require_paths = ["lib"]
|
|
20
|
+
end
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
require 'test/unit'
|
|
2
|
+
require 'lib/retry_block'
|
|
3
|
+
|
|
4
|
+
class TestRetryBlock < Test::Unit::TestCase
|
|
5
|
+
def setup
|
|
6
|
+
@fail_count = 0
|
|
7
|
+
@run_count = 0
|
|
8
|
+
@fail_callback = lambda do
|
|
9
|
+
@fail_count += 1
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_runs_block_once
|
|
14
|
+
assert_nothing_raised do
|
|
15
|
+
retry_block(:fail_callback => @fail_callback) do
|
|
16
|
+
@run_count += 1
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
assert_equal 1, @run_count
|
|
20
|
+
assert_equal 0, @fail_count
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_runs_block_twice
|
|
24
|
+
assert_nothing_raised do
|
|
25
|
+
retry_block(:attempts => 2, :fail_callback => @fail_callback) do
|
|
26
|
+
@run_count += 1
|
|
27
|
+
raise TestException if @run_count == 1
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
assert_equal 2, @run_count
|
|
31
|
+
assert_equal 1, @fail_count
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def test_runs_block_twice_and_fails
|
|
35
|
+
assert_raises(TestException) do
|
|
36
|
+
retry_block(:attempts => 2, :fail_callback => @fail_callback) do
|
|
37
|
+
@run_count += 1
|
|
38
|
+
raise TestException
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
assert_equal 2, @run_count
|
|
42
|
+
assert_equal 2, @fail_count
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def test_raises_unexpected_exception1
|
|
46
|
+
assert_raises(RuntimeError) do
|
|
47
|
+
retry_block(:attempts => 2, :catch => TestException, :fail_callback => @fail_callback) do
|
|
48
|
+
@run_count += 1
|
|
49
|
+
raise RuntimeError
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
assert_equal 1, @run_count
|
|
53
|
+
assert_equal 0, @fail_count
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def test_raises_unexpected_exception2
|
|
57
|
+
assert_raises(RuntimeError) do
|
|
58
|
+
retry_block(:attempts => 2, :catch => [TestException], :fail_callback => @fail_callback) do
|
|
59
|
+
@run_count += 1
|
|
60
|
+
raise RuntimeError
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
assert_equal 1, @run_count
|
|
64
|
+
assert_equal 0, @fail_count
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def test_catches_exception1
|
|
68
|
+
assert_nothing_raised do
|
|
69
|
+
retry_block(:attempts => 2, :catch => TestException, :fail_callback => @fail_callback) do
|
|
70
|
+
@run_count += 1
|
|
71
|
+
raise TestException unless @run_count == 2
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
assert_equal 2, @run_count
|
|
75
|
+
assert_equal 1, @fail_count
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_catches_multiple_exception
|
|
79
|
+
assert_nothing_raised do
|
|
80
|
+
retry_block(:attempts => 3, :catch => [TestException, RuntimeError], :fail_callback => @fail_callback) do
|
|
81
|
+
@run_count += 1
|
|
82
|
+
raise TestException if @run_count == 1
|
|
83
|
+
raise RuntimeError if @run_count == 2
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
assert_equal 3, @run_count
|
|
87
|
+
assert_equal 2, @fail_count
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def test_doesnt_catch_unexpected_exception_from_list
|
|
91
|
+
assert_raises(Exception) do
|
|
92
|
+
retry_block(:attempts => 4, :catch => [TestException, RuntimeError], :fail_callback => @fail_callback) do
|
|
93
|
+
@run_count += 1
|
|
94
|
+
raise TestException if @run_count == 1
|
|
95
|
+
raise RuntimeError if @run_count == 2
|
|
96
|
+
raise Exception if @run_count == 3
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
assert_equal 3, @run_count
|
|
100
|
+
assert_equal 2, @fail_count
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def test_fails_on_bad_argument1
|
|
104
|
+
assert_raises(ArgumentError) do
|
|
105
|
+
retry_block(:attempts => -1, :fail_callback => @fail_callback) do
|
|
106
|
+
@run_count += 1
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
assert_equal 0, @run_count
|
|
110
|
+
assert_equal 0, @fail_count
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def test_fails_on_bad_argument2
|
|
114
|
+
assert_raises(ArgumentError) do
|
|
115
|
+
retry_block(:attempts => 0.4, :fail_callback => @fail_callback) do
|
|
116
|
+
@run_count += 1
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
assert_equal 0, @run_count
|
|
120
|
+
assert_equal 0, @fail_count
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def test_sleeps
|
|
124
|
+
sleep_time = 0.1 # Sleep for 1/10 of a second
|
|
125
|
+
attempts = 3
|
|
126
|
+
# Doesn't sleep after last failure
|
|
127
|
+
total_sleep_time = (attempts-1) * sleep_time
|
|
128
|
+
begin_time = Time.new
|
|
129
|
+
assert_raises(TestException) do
|
|
130
|
+
retry_block(:attempts => attempts, :fail_callback => @fail_callback, :sleep => sleep_time) do
|
|
131
|
+
@run_count += 1
|
|
132
|
+
raise TestException
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end_time = Time.new
|
|
136
|
+
time_elapsed = end_time - begin_time
|
|
137
|
+
assert_equal attempts, @run_count
|
|
138
|
+
assert_equal attempts, @fail_count
|
|
139
|
+
# Things are never exact. Expect that time_elapsed in seconds to be
|
|
140
|
+
# equal to total_sleep_time plus or minus 5%
|
|
141
|
+
assert_in_delta total_sleep_time, time_elapsed, total_sleep_time*0.05
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def test_doesnt_sleep
|
|
145
|
+
begin_time = Time.new
|
|
146
|
+
assert_raises(TestException) do
|
|
147
|
+
retry_block(:attempts => 1, :fail_callback => @fail_callback, :sleep => 0.1) do
|
|
148
|
+
@run_count += 1
|
|
149
|
+
raise TestException
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end_time = Time.new
|
|
153
|
+
time_elapsed = end_time - begin_time
|
|
154
|
+
assert_equal 1, @run_count
|
|
155
|
+
assert_equal 1, @fail_count
|
|
156
|
+
# plus or minus 1/100 of a second ;)
|
|
157
|
+
assert_in_delta 0.0, time_elapsed, 0.01
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def test_runs_forever
|
|
161
|
+
assert_raises(RuntimeError) do
|
|
162
|
+
retry_block(:attempts => nil, :catch => TestException, :fail_callback => @fail_callback) do
|
|
163
|
+
@run_count += 1
|
|
164
|
+
raise TestException unless @run_count == 100
|
|
165
|
+
raise RuntimeError
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
assert_equal 100, @run_count
|
|
169
|
+
assert_equal 99, @fail_count
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
class TestException < Exception
|
|
174
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: retry_block
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
hash: 21
|
|
5
|
+
prerelease:
|
|
6
|
+
segments:
|
|
7
|
+
- 1
|
|
8
|
+
- 0
|
|
9
|
+
- 1
|
|
10
|
+
version: 1.0.1
|
|
11
|
+
platform: ruby
|
|
12
|
+
authors:
|
|
13
|
+
- Alfred J. Fazio
|
|
14
|
+
autorequire:
|
|
15
|
+
bindir: bin
|
|
16
|
+
cert_chain: []
|
|
17
|
+
|
|
18
|
+
date: 2011-11-04 00:00:00 Z
|
|
19
|
+
dependencies: []
|
|
20
|
+
|
|
21
|
+
description: Take control of unstable or indeterminate code with retry_block
|
|
22
|
+
email:
|
|
23
|
+
- alfred.fazio@gmail.com
|
|
24
|
+
executables: []
|
|
25
|
+
|
|
26
|
+
extensions: []
|
|
27
|
+
|
|
28
|
+
extra_rdoc_files: []
|
|
29
|
+
|
|
30
|
+
files:
|
|
31
|
+
- .gitignore
|
|
32
|
+
- Gemfile
|
|
33
|
+
- Rakefile
|
|
34
|
+
- lib/retry_block.rb
|
|
35
|
+
- lib/retry_block/version.rb
|
|
36
|
+
- retry_block.gemspec
|
|
37
|
+
- test/test_retry_block.rb
|
|
38
|
+
homepage: ""
|
|
39
|
+
licenses: []
|
|
40
|
+
|
|
41
|
+
post_install_message:
|
|
42
|
+
rdoc_options: []
|
|
43
|
+
|
|
44
|
+
require_paths:
|
|
45
|
+
- lib
|
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
47
|
+
none: false
|
|
48
|
+
requirements:
|
|
49
|
+
- - ">="
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
hash: 3
|
|
52
|
+
segments:
|
|
53
|
+
- 0
|
|
54
|
+
version: "0"
|
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
56
|
+
none: false
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
hash: 3
|
|
61
|
+
segments:
|
|
62
|
+
- 0
|
|
63
|
+
version: "0"
|
|
64
|
+
requirements: []
|
|
65
|
+
|
|
66
|
+
rubyforge_project: retry_block
|
|
67
|
+
rubygems_version: 1.8.5
|
|
68
|
+
signing_key:
|
|
69
|
+
specification_version: 3
|
|
70
|
+
summary: Take control of unstable or indeterminate code with retry_block
|
|
71
|
+
test_files:
|
|
72
|
+
- test/test_retry_block.rb
|