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 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.
@@ -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
@@ -0,0 +1,6 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+
3
+ require 'rubygems'
4
+ require 'rspec'
5
+
6
+ require File.dirname(__FILE__) + '/../lib/anticipate'
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
+