anticipate 0.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/README.rdoc +26 -0
- data/anticipate.gemspec +19 -0
- data/lib/anticipate.rb +74 -0
- data/lib/anticipate/anticipator.rb +23 -0
- data/lib/anticipate/timeout_error.rb +24 -0
- data/spec/anticipate/anticipate_spec.rb +88 -0
- data/spec/anticipate/anticipator_spec.rb +66 -0
- data/spec/anticipate/integration_spec.rb +72 -0
- data/spec/spec_helper.rb +6 -0
- metadata +75 -0
data/README.rdoc
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
= Anticipate
|
2
|
+
|
3
|
+
== About
|
4
|
+
|
5
|
+
Anticipate is a fluent interface for retrying blocks of code:
|
6
|
+
|
7
|
+
trying_every(3).seconds.failing_after(9).tries do
|
8
|
+
|
9
|
+
# Repeatedly calls block until it stops raising errors
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
The Anticipate module allows the following expressions:
|
14
|
+
|
15
|
+
trying_every(n).seconds {}
|
16
|
+
failing_after(n).tries {}
|
17
|
+
trying_every(x).seconds.failing_after(y).tries {}
|
18
|
+
|
19
|
+
Blocks should contain an assertion, i.e. raise a
|
20
|
+
descriptive error if some condition is unsatisfied. On
|
21
|
+
the last iteration, the error will be wrapped in a
|
22
|
+
TimeoutError and re-raised.
|
23
|
+
|
24
|
+
I'm aware of a couple of gems supporting the
|
25
|
+
begin-rescue-sleep-retry dance, namely 'retry' and 'attempt'.
|
26
|
+
The aim of this library is to offer more expressive syntax.
|
data/anticipate.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
3
|
+
require "anticipate"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'anticipate'
|
7
|
+
s.version = Anticipate::VERSION
|
8
|
+
s.authors = ['Josh Chisholm']
|
9
|
+
s.description = 'Fluent interface for try-rescue-sleep-retry-abort'
|
10
|
+
s.summary = "anticipate-#{s.version}"
|
11
|
+
s.email = 'joshuachisholm@gmail.com'
|
12
|
+
s.homepage = 'http://github.com/joshski/anticipate'
|
13
|
+
|
14
|
+
s.rubygems_version = "1.3.7"
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {spec}/*`.split("\n")
|
17
|
+
s.extra_rdoc_files = ["README.rdoc"]
|
18
|
+
s.require_path = "lib"
|
19
|
+
end
|
data/lib/anticipate.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
lib = File.dirname(__FILE__)
|
2
|
+
$:.unshift(lib) unless $:.include?(lib) || $:.include?(File.expand_path(lib))
|
3
|
+
|
4
|
+
require 'anticipate/anticipator'
|
5
|
+
|
6
|
+
module Anticipate
|
7
|
+
VERSION = '0.0.1'
|
8
|
+
|
9
|
+
def anticipator
|
10
|
+
Anticipator.new(Kernel)
|
11
|
+
end
|
12
|
+
|
13
|
+
def default_tries
|
14
|
+
@default_tries ||= 1
|
15
|
+
end
|
16
|
+
|
17
|
+
def default_interval
|
18
|
+
@default_interval ||= 0.1
|
19
|
+
end
|
20
|
+
|
21
|
+
def trying_every(amount)
|
22
|
+
anticipation.trying_every(amount)
|
23
|
+
end
|
24
|
+
|
25
|
+
def failing_after(amount)
|
26
|
+
anticipation.failing_after(amount)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def anticipation
|
32
|
+
Anticipation.new(anticipator, default_interval, default_tries)
|
33
|
+
end
|
34
|
+
|
35
|
+
class Term
|
36
|
+
def initialize(anticipator, interval, timeout)
|
37
|
+
@anticipator, @interval, @timeout = anticipator, interval, timeout
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def exec
|
43
|
+
@anticipator.anticipate(@interval, @timeout) do
|
44
|
+
yield
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def chain
|
49
|
+
Anticipation.new(@anticipator, @interval, @timeout)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class TimeUnit < Term
|
54
|
+
def seconds
|
55
|
+
block_given? ? exec { yield } : chain
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class CountUnit < Term
|
60
|
+
def tries
|
61
|
+
block_given? ? exec { yield } : chain
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class Anticipation < Term
|
66
|
+
def trying_every(amount)
|
67
|
+
TimeUnit.new(@anticipator, amount, @timeout)
|
68
|
+
end
|
69
|
+
|
70
|
+
def failing_after(amount)
|
71
|
+
CountUnit.new(@anticipator, @interval, amount)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'anticipate/timeout_error'
|
2
|
+
|
3
|
+
module Anticipate
|
4
|
+
class Anticipator
|
5
|
+
def initialize(sleeper)
|
6
|
+
@sleeper = sleeper
|
7
|
+
end
|
8
|
+
|
9
|
+
def anticipate(interval, tries)
|
10
|
+
count = -1
|
11
|
+
begin
|
12
|
+
yield
|
13
|
+
return
|
14
|
+
rescue => e
|
15
|
+
if (count += 1) == tries
|
16
|
+
raise TimeoutError.new(interval, tries, e)
|
17
|
+
end
|
18
|
+
@sleeper.sleep interval
|
19
|
+
retry
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Anticipate
|
2
|
+
class TimeoutError < RuntimeError
|
3
|
+
def initialize(interval, tries, last_error)
|
4
|
+
@interval, @tries, @last_error =
|
5
|
+
interval, tries, last_error
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
"Timed out after #{@tries} tries" +
|
10
|
+
" (tried every #{seconds(@interval)})" +
|
11
|
+
"\n#{@last_error}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def backtrace
|
15
|
+
@last_error.backtrace
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def seconds(amount)
|
21
|
+
amount == 1 ? "1 second" : "#{amount} seconds"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Anticipate
|
4
|
+
describe Anticipate do
|
5
|
+
include Anticipate
|
6
|
+
|
7
|
+
before do
|
8
|
+
@anticipator = mock("anticipator")
|
9
|
+
@anticipator.stub!(:anticipate).and_yield
|
10
|
+
end
|
11
|
+
|
12
|
+
def anticipator
|
13
|
+
@anticipator
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "trying_every(n).seconds" do
|
17
|
+
it "yields" do
|
18
|
+
called = false
|
19
|
+
trying_every(1).seconds { called = true }
|
20
|
+
called.should be_true
|
21
|
+
end
|
22
|
+
|
23
|
+
it "overrides the interval" do
|
24
|
+
@anticipator.should_receive(:anticipate).with(55, anything)
|
25
|
+
trying_every(55).seconds {}
|
26
|
+
end
|
27
|
+
|
28
|
+
it "uses the default timeout" do
|
29
|
+
@anticipator.should_receive(:anticipate).with(anything, default_tries)
|
30
|
+
trying_every(66).seconds {}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "failing_after(n).tries" do
|
35
|
+
it "yields" do
|
36
|
+
called = false
|
37
|
+
failing_after(1).tries { called = true }
|
38
|
+
called.should be_true
|
39
|
+
end
|
40
|
+
|
41
|
+
it "overrides the timeout" do
|
42
|
+
@anticipator.should_receive(:anticipate).with(anything, 77)
|
43
|
+
failing_after(77).tries {}
|
44
|
+
end
|
45
|
+
|
46
|
+
it "uses the default interval" do
|
47
|
+
@anticipator.should_receive(:anticipate).with(default_interval, anything)
|
48
|
+
failing_after(88).tries {}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "failing_after(x).tries.trying_every(y).seconds" do
|
53
|
+
it "yields" do
|
54
|
+
called = false
|
55
|
+
failing_after(2).tries.trying_every(1).seconds { called = true }
|
56
|
+
called.should be_true
|
57
|
+
end
|
58
|
+
|
59
|
+
it "overrides the timeout" do
|
60
|
+
@anticipator.should_receive(:anticipate).with(anything, 222)
|
61
|
+
failing_after(222).tries.trying_every(111).seconds {}
|
62
|
+
end
|
63
|
+
|
64
|
+
it "overrides the interval" do
|
65
|
+
@anticipator.should_receive(:anticipate).with(333, anything)
|
66
|
+
failing_after(444).tries.trying_every(333).seconds {}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "trying_every(x).seconds.failing_after(y).tries" do
|
71
|
+
it "yields" do
|
72
|
+
called = false
|
73
|
+
trying_every(1).seconds.failing_after(2).tries { called = true }
|
74
|
+
called.should be_true
|
75
|
+
end
|
76
|
+
|
77
|
+
it "overrides the timeout" do
|
78
|
+
@anticipator.should_receive(:anticipate).with(anything, 666)
|
79
|
+
trying_every(555).seconds.failing_after(666).tries {}
|
80
|
+
end
|
81
|
+
|
82
|
+
it "overrides the interval" do
|
83
|
+
@anticipator.should_receive(:anticipate).with(777, anything)
|
84
|
+
trying_every(777).seconds.failing_after(888).tries {}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Anticipate
|
4
|
+
describe Anticipator do
|
5
|
+
before do
|
6
|
+
@sleeper = mock("sleeper")
|
7
|
+
@sleeper.stub!(:sleep)
|
8
|
+
@anticipator = Anticipator.new(@sleeper)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "#anticipate" do
|
12
|
+
|
13
|
+
describe "when the block eventually stops raising" do
|
14
|
+
it "sleeps the given interval between tries" do
|
15
|
+
@sleeper.should_receive(:sleep).with(1).exactly(8).times
|
16
|
+
tries = 0
|
17
|
+
@anticipator.anticipate(1,9) do
|
18
|
+
raise "fail" unless (tries += 1) == 9
|
19
|
+
end
|
20
|
+
tries.should == 9
|
21
|
+
end
|
22
|
+
|
23
|
+
it "does not raise" do
|
24
|
+
tries = 0
|
25
|
+
lambda {
|
26
|
+
@anticipator.anticipate(1,2) do
|
27
|
+
raise "fail" unless (tries += 1) == 2
|
28
|
+
end
|
29
|
+
}.should_not raise_error
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "when the block always raises" do
|
34
|
+
describe "when waiting for one second" do
|
35
|
+
it "raises a TimeoutError, with the last error message" do
|
36
|
+
tries = 0
|
37
|
+
lambda {
|
38
|
+
@anticipator.anticipate(1,2) { raise (tries += 1).to_s }
|
39
|
+
}.should raise_error(TimeoutError,
|
40
|
+
"Timed out after 2 tries (tried every 1 second)\n3")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "when waiting for any other number of seconds" do
|
45
|
+
it "raises a TimeoutError, with the last error message" do
|
46
|
+
tries = 0
|
47
|
+
lambda {
|
48
|
+
@anticipator.anticipate(2,3) { raise (tries += 1).to_s }
|
49
|
+
}.should raise_error(TimeoutError,
|
50
|
+
"Timed out after 3 tries (tried every 2 seconds)\n4")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "when the block never raises" do
|
56
|
+
it "does not raise" do
|
57
|
+
lambda {
|
58
|
+
@anticipator.anticipate(1,2) do
|
59
|
+
end
|
60
|
+
}.should_not raise_error
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Anticipate
|
4
|
+
describe Anticipate, "with default options" do
|
5
|
+
include Anticipate
|
6
|
+
|
7
|
+
it "raises when a block continually raises" do
|
8
|
+
raises = []
|
9
|
+
lambda {
|
10
|
+
trying_every(0.01).seconds.failing_after(5).tries {
|
11
|
+
raise (raises << Time.now).to_s
|
12
|
+
}
|
13
|
+
}.should raise_error(TimeoutError)
|
14
|
+
raises.size.should == 6
|
15
|
+
total_time = raises.last - raises.first
|
16
|
+
total_time.should > 0.05
|
17
|
+
total_time.should < 0.06
|
18
|
+
end
|
19
|
+
|
20
|
+
it "continues when a block stops raising" do
|
21
|
+
raises = 0
|
22
|
+
lambda {
|
23
|
+
trying_every(0.01).seconds.failing_after(3).tries {
|
24
|
+
raise (raises += 1).to_s unless raises == 2
|
25
|
+
}
|
26
|
+
}.should_not raise_error
|
27
|
+
raises.should == 2
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe Anticipate, "with overridden tries" do
|
32
|
+
include Anticipate
|
33
|
+
|
34
|
+
def default_tries
|
35
|
+
5
|
36
|
+
end
|
37
|
+
|
38
|
+
it "uses the overridden tries" do
|
39
|
+
raises = []
|
40
|
+
lambda {
|
41
|
+
trying_every(0.01).seconds {
|
42
|
+
raise (raises << Time.now).to_s
|
43
|
+
}
|
44
|
+
}.should raise_error(TimeoutError)
|
45
|
+
raises.size.should == 6
|
46
|
+
total_time = raises.last - raises.first
|
47
|
+
total_time.should > 0.05
|
48
|
+
total_time.should < 0.06
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe Anticipate, "with overridden interval" do
|
53
|
+
include Anticipate
|
54
|
+
|
55
|
+
def default_interval
|
56
|
+
0.01
|
57
|
+
end
|
58
|
+
|
59
|
+
it "uses the overridden interval" do
|
60
|
+
raises = []
|
61
|
+
lambda {
|
62
|
+
failing_after(5).tries {
|
63
|
+
raise (raises << Time.now).to_s
|
64
|
+
}
|
65
|
+
}.should raise_error(TimeoutError)
|
66
|
+
raises.size.should == 6
|
67
|
+
total_time = raises.last - raises.first
|
68
|
+
total_time.should > 0.05
|
69
|
+
total_time.should < 0.06
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: anticipate
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Josh Chisholm
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-01-31 00:00:00 +00:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Fluent interface for try-rescue-sleep-retry-abort
|
23
|
+
email: joshuachisholm@gmail.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- README.rdoc
|
30
|
+
files:
|
31
|
+
- README.rdoc
|
32
|
+
- anticipate.gemspec
|
33
|
+
- lib/anticipate.rb
|
34
|
+
- lib/anticipate/anticipator.rb
|
35
|
+
- lib/anticipate/timeout_error.rb
|
36
|
+
- spec/anticipate/anticipate_spec.rb
|
37
|
+
- spec/anticipate/anticipator_spec.rb
|
38
|
+
- spec/anticipate/integration_spec.rb
|
39
|
+
- spec/spec_helper.rb
|
40
|
+
has_rdoc: true
|
41
|
+
homepage: http://github.com/joshski/anticipate
|
42
|
+
licenses: []
|
43
|
+
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
hash: 3
|
55
|
+
segments:
|
56
|
+
- 0
|
57
|
+
version: "0"
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
hash: 3
|
64
|
+
segments:
|
65
|
+
- 0
|
66
|
+
version: "0"
|
67
|
+
requirements: []
|
68
|
+
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 1.4.1
|
71
|
+
signing_key:
|
72
|
+
specification_version: 3
|
73
|
+
summary: anticipate-0.0.1
|
74
|
+
test_files: []
|
75
|
+
|