1
2
3
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
|
RSpec.describe RetryService do
subject { described_class.new delays: [2, 3], rescue_from: [NameError, ArgumentError] }
context 'no retry necessary' do
before do
expect( subject ).not_to receive(:sleep)
end
it 'returns a tuple :ok and the result' do
expect( subject.execute { 42 } ).to eq([:ok, 42])
end
it 'does not fail on nil' do
expect( subject.execute { nil } ).to eq([:ok, nil])
end
it 'fails wihout retries if raising un unregistered exception' do
expect{ subject.execute{ raise KeyError } }.to raise_error(KeyError)
end
end
context 'all retries fail' do
before do
expect( subject ).to receive(:sleep).with(2)
expect( subject ).to receive(:sleep).with(3)
end
it 'fails after raising a registered exception n times' do
result = subject.execute{ raise ArgumentError }
expect( result.first ).to eq(:error)
expect( result.last ).to be_kind_of(ArgumentError)
end
it 'fails with an explicit try again (automatically registered exception)' do
result = subject.execute{ raise RetryService::Retry }
expect( result.first ).to eq(:error)
expect( result.last ).to be_kind_of(RetryService::Retry)
end
end
context "if at first you don't succeed" do
before do
@count = 0
expect( subject ).to receive(:sleep).with(2)
end
it 'succeds the second time' do
expect( subject.execute{ succeed_later(ArgumentError){ 42 } } ).to eq([:ok, 42])
end
it 'succeeds the second time with try again (automatically registered exception)' do
expect( subject.execute{ succeed_later(RetryService::Retry){ 42 } } ).to eq([:ok, 42])
end
end
context 'last chance' do
before do
@count = 0
expect( subject ).to receive(:sleep).with(2)
expect( subject ).to receive(:sleep).with(3)
end
it 'succeeds the third time with try again (automatically registered exception)' do
expect( subject.execute{ succeed_later(RetryService::Retry, count: 2){ 42 } } ).to eq([:ok, 42])
end
end
context 'failure callback once' do
before do
@failures = 0
@count = 0
expect( subject ).to receive(:sleep).with(2)
subject.register_failure_callback { |reason, count| @reason=reason; @callback_count=count; @failures += 1 }
end
it 'succeeds the second time and calls the failure_callback once' do
subject.execute{ succeed_later(RetryService::Retry){ 42 } }
expect( @failures ).to eq(1)
end
it '... and the failure is passed into the callback' do
subject.execute{ succeed_later(RetryService::Retry){ 42 } }
expect( @reason ).to be_a(RetryService::Retry)
expect( @callback_count ).to eq(1)
end
end
context 'failure callback twice' do
before do
@failures = 0
@count = 0
expect( subject ).to receive(:sleep).with(2)
expect( subject ).to receive(:sleep).with(3)
subject.register_failure_callback { @failures += 1 }
end
it 'succeeds the third time and calls the failure_callback twice' do
subject.execute{ succeed_later(NameError, count: 2){ 42 } }
expect( @failures ).to eq(2)
end
end
context 'failure callback in constructor' do
subject do
described_class.new(delays: [1, 2], &method(:add2failures))
end
before do
@failures = []
@count = 0
expect( subject ).to receive(:sleep).with(1)
expect( subject ).to receive(:sleep).with(2)
end
it 'succeeds the second time and calls the failure_callback once' do
subject.execute{ succeed_later(RetryService::Retry, count: 2){ 42 } }
expect( @failures ).to eq([1,2])
end
end
def add2failures( e, c)
@failures << c
end
def succeed_later error, count: 1, &blk
return blk.() unless @count < count
@count += 1
raise error, 'error'
end
end
|