pester 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +1 -0
- data/.rubocop.yml +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +31 -0
- data/Rakefile +14 -0
- data/lib/pester/behaviors/sleep.rb +9 -0
- data/lib/pester/behaviors.rb +8 -0
- data/lib/pester/config.rb +16 -0
- data/lib/pester/version.rb +3 -0
- data/lib/pester.rb +92 -0
- data/pester.gemspec +28 -0
- data/spec/helpers/null_logger.rb +9 -0
- data/spec/helpers/scripted_failer.rb +18 -0
- data/spec/pester_spec.rb +204 -0
- data/spec/spec_helper.rb +8 -0
- metadata +111 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f5c9151d53d657939d6506baf8a27e5da2744b3b
|
4
|
+
data.tar.gz: 251a6215cfe9cd275f37bf7df458561116cb7401
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dd350670630f082f40af4ab8290e7eb5f8a75a2137ca61cacc63c72445c28baad2eda3bc595aec14bb2782bc3ac5778a38c5678661aaec77ab75fe9f1fcb7e9f
|
7
|
+
data.tar.gz: 4bd76629e817aca9981340564ec5426ab8e6c6619bbe222b6b7b0d33143e587f21c9b49a31d2dbfb87d9a06ac1863dcf192d550860cf7686ba56448dc38041e0
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.rubocop.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Marc Bollinger
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Pester
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'pester'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install pester
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
TODO: Write usage instructions here
|
24
|
+
|
25
|
+
## Contributing
|
26
|
+
|
27
|
+
1. Fork it ( https://github.com/[my-github-username]/pester/fork )
|
28
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
29
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
30
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
31
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
|
3
|
+
require 'rspec/core'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
6
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'rubocop/rake_task'
|
10
|
+
RuboCop::RakeTask.new
|
11
|
+
|
12
|
+
task prep: %w(spec rubocop)
|
13
|
+
|
14
|
+
task default: :spec
|
data/lib/pester.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'pester/behaviors'
|
2
|
+
require 'pester/behaviors/sleep'
|
3
|
+
require 'pester/config'
|
4
|
+
require 'pester/version'
|
5
|
+
|
6
|
+
module Pester
|
7
|
+
def self.configure(&block)
|
8
|
+
Config.configure(&block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.retry(options = {}, &block)
|
12
|
+
retry_action(options.merge(on_retry: ->(_, delay_interval) { sleep(delay_interval) }), &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.retry_with_backoff(options = {}, &block)
|
16
|
+
retry_action(options.merge(on_retry: Behaviors::Sleep::Linear), &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.retry_with_exponential_backoff(options = {}, &block)
|
20
|
+
retry_action({ delay_interval: 1 }.merge(options).merge(on_retry: Behaviors::Sleep::Exponential), &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
# This function executes a block and retries the block depending on
|
24
|
+
# which errors were thrown. Retries 4 times by default.
|
25
|
+
#
|
26
|
+
# Options:
|
27
|
+
# retry_error_classes - A single or array of exceptions to retry on. Thrown exceptions not in this list
|
28
|
+
# (including parent/sub-classes) will be reraised
|
29
|
+
# reraise_error_classes - A single or array of exceptions to always re-raiseon. Thrown exceptions not in
|
30
|
+
# this list (including parent/sub-classes) will be retried
|
31
|
+
# max_attempts - Max number of attempts to retry
|
32
|
+
# delay_interval - Second interval by which successive attempts will be incremented. A value of 2
|
33
|
+
# passed to retry_with_backoff will retry first after 2 seconds, then 4, then 6, et al.
|
34
|
+
# on_retry - A Proc to be called on each successive failure, before the next retry
|
35
|
+
# on_max_attempts_exceeded - A Proc to be called when attempt_num >= max_attempts - 1
|
36
|
+
# message - String or regex to look for in thrown exception messages. Matches will trigger retry
|
37
|
+
# logic, non-matches will cause the exception to be reraised
|
38
|
+
#
|
39
|
+
# Usage:
|
40
|
+
# retry_action do
|
41
|
+
# puts 'trying to remove a directory'
|
42
|
+
# FileUtils.rm_r(directory)
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# retryable(error_classes: Mysql2::Error, message: /^Lost connection to MySQL server/, max_attempts: 2) do
|
46
|
+
# ActiveRecord::Base.connection.execute("LONG MYSQL STATEMENT")
|
47
|
+
# end
|
48
|
+
def self.retry_action(opts = {}, &block)
|
49
|
+
merge_defaults(opts)
|
50
|
+
if opts[:retry_error_classes] && opts[:reraise_error_classes]
|
51
|
+
fail 'You can only have one of retry_error_classes or reraise_error_classes'
|
52
|
+
end
|
53
|
+
|
54
|
+
opts[:max_attempts].times do |attempt_num|
|
55
|
+
begin
|
56
|
+
result = yield block
|
57
|
+
return result
|
58
|
+
rescue => e
|
59
|
+
class_reraise = opts[:retry_error_classes] && !opts[:retry_error_classes].include?(e.class)
|
60
|
+
reraise_error = opts[:reraise_error_classes] && opts[:reraise_error_classes].include?(e.class)
|
61
|
+
message_reraise = opts[:message] && !e.message[opts[:message]]
|
62
|
+
|
63
|
+
if class_reraise || message_reraise || reraise_error
|
64
|
+
match_type = class_reraise ? 'class' : 'message'
|
65
|
+
opts[:logger].warn("Reraising exception from inside retry_action because provided #{match_type} was not matched.")
|
66
|
+
raise
|
67
|
+
end
|
68
|
+
|
69
|
+
if opts[:max_attempts] - 1 > attempt_num
|
70
|
+
attempts_left = opts[:max_attempts] - attempt_num - 1
|
71
|
+
trace = e.backtrace
|
72
|
+
opts[:logger].warn("Failure encountered: #{e}, backing off and trying again #{attempts_left} more times. Trace: #{trace}")
|
73
|
+
opts[:on_retry].call(attempt_num, opts[:delay_interval])
|
74
|
+
else
|
75
|
+
return opts[:on_max_attempts_exceeded].call(opts[:logger], opts[:max_attempts], e)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def self.merge_defaults(opts)
|
84
|
+
opts[:retry_error_classes] = opts[:retry_error_classes] ? Array(opts[:retry_error_classes]) : nil
|
85
|
+
opts[:reraise_error_classes] = opts[:reraise_error_classes] ? Array(opts[:reraise_error_classes]) : nil
|
86
|
+
opts[:max_attempts] ||= 4
|
87
|
+
opts[:delay_interval] ||= 30
|
88
|
+
opts[:on_retry] ||= ->(_, _) {}
|
89
|
+
opts[:on_max_attempts_exceeded] ||= Behaviors::WarnAndReraise
|
90
|
+
opts[:logger] ||= Config.logger
|
91
|
+
end
|
92
|
+
end
|
data/pester.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'pester/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'pester'
|
8
|
+
spec.version = Pester::VERSION
|
9
|
+
spec.authors = ['Marc Bollinger']
|
10
|
+
spec.email = ['marc@lumoslabs.com']
|
11
|
+
spec.summary = 'Common block-based retry for external calls.'
|
12
|
+
spec.description = <<-EOD
|
13
|
+
|We found ourselves constantly wrapping network-facing calls with all kinds of bespoke,
|
14
|
+
| copied, and rewritten retry logic. This gem is an attempt to unify common behaviors,
|
15
|
+
| like simple retry, retry with linear backoff, and retry with exponential backoff.
|
16
|
+
EOD
|
17
|
+
spec.homepage = 'https://github.com/lumoslabs/pester'
|
18
|
+
spec.license = 'MIT'
|
19
|
+
|
20
|
+
spec.files = `git ls-files -z`.split("\x0")
|
21
|
+
spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
|
22
|
+
spec.test_files = spec.files.grep(/^(test|spec|features)\//)
|
23
|
+
spec.require_paths = ['lib']
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
26
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
27
|
+
spec.add_development_dependency 'rspec', '~> 3.2'
|
28
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class ScriptedFailer
|
2
|
+
attr_accessor :fails, :result, :successes
|
3
|
+
|
4
|
+
def initialize(num_fails = 2, intended_result = 2)
|
5
|
+
@fails = num_fails
|
6
|
+
@result = intended_result
|
7
|
+
@successes = 0
|
8
|
+
end
|
9
|
+
|
10
|
+
def fail(error_class, msg)
|
11
|
+
if @fails > 0
|
12
|
+
@fails -= 1
|
13
|
+
raise error_class.new(msg)
|
14
|
+
end
|
15
|
+
@successes += 1
|
16
|
+
@result
|
17
|
+
end
|
18
|
+
end
|
data/spec/pester_spec.rb
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
class MatchedError < RuntimeError; end
|
5
|
+
class UnmatchedError < RuntimeError; end
|
6
|
+
|
7
|
+
shared_examples 'raises an error' do
|
8
|
+
it 'raises an error and succeeds 0 times' do
|
9
|
+
expect { Pester.retry_action(options) { action } }.to raise_error
|
10
|
+
expect(failer.successes).to eq(0)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
shared_examples "doesn't raise an error" do
|
15
|
+
it "doesn't raise an error" do
|
16
|
+
expect { Pester.retry_action(options) { action } }.to_not raise_error
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
shared_examples 'returns and succeeds' do
|
21
|
+
it 'returns the intended result' do
|
22
|
+
expect(Pester.retry_action(options) { action }).to eq(intended_result)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'succeeds exactly once' do
|
26
|
+
Pester.retry_action(options) { action }
|
27
|
+
expect(failer.successes).to eq(1)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
shared_examples 'raises an error only in the correct cases with a retry class' do
|
32
|
+
context 'when neither the class is in the retry list, nor is the message matched' do
|
33
|
+
let(:actual_error_class) { non_matching_error_class }
|
34
|
+
let(:actual_error_message) { non_matching_error_message }
|
35
|
+
|
36
|
+
it_has_behavior 'raises an error'
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'when the class is in the retry list, but the message is not matched' do
|
40
|
+
let(:actual_error_class) { matching_error_class }
|
41
|
+
let(:actual_error_message) { non_matching_error_message }
|
42
|
+
|
43
|
+
it_has_behavior 'raises an error'
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'when the class is not in the retry list, but the message is matched' do
|
47
|
+
let(:actual_error_class) { non_matching_error_class }
|
48
|
+
let(:actual_error_message) { matching_error_message }
|
49
|
+
|
50
|
+
it_has_behavior 'raises an error'
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'when the class is in the list, and the message is matched' do
|
54
|
+
let(:actual_error_class) { matching_error_class }
|
55
|
+
let(:actual_error_message) { matching_error_message }
|
56
|
+
|
57
|
+
it_has_behavior "doesn't raise an error"
|
58
|
+
|
59
|
+
it_has_behavior 'returns and succeeds'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
shared_examples 'raises an error only in the correct cases with a reraise class' do
|
64
|
+
context 'when the class is not in the reraise list' do
|
65
|
+
let(:actual_error_class) { non_matching_error_class }
|
66
|
+
let(:actual_error_message) { non_matching_error_message }
|
67
|
+
|
68
|
+
it_has_behavior "doesn't raise an error"
|
69
|
+
|
70
|
+
it_has_behavior 'returns and succeeds'
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'when the class is in the reraise list' do
|
74
|
+
let(:actual_error_class) { matching_error_class }
|
75
|
+
let(:actual_error_message) { matching_error_message }
|
76
|
+
|
77
|
+
it_has_behavior 'raises an error'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe 'retry_action' do
|
82
|
+
let(:intended_result) { 1000 }
|
83
|
+
let(:action) { failer.fail(UnmatchedError, 'Dying') }
|
84
|
+
let(:null_logger) { NullLogger.new }
|
85
|
+
|
86
|
+
context 'for non-failing block' do
|
87
|
+
let(:failer) { ScriptedFailer.new(0, intended_result) }
|
88
|
+
let(:options) { { delay_interval: 0, logger: null_logger } }
|
89
|
+
|
90
|
+
it_has_behavior "doesn't raise an error"
|
91
|
+
|
92
|
+
it_has_behavior 'returns and succeeds'
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'for block that fails less than threshold' do
|
96
|
+
let(:failer) { ScriptedFailer.new(2, intended_result) }
|
97
|
+
let(:options) { { max_attempts: 3, logger: null_logger } }
|
98
|
+
|
99
|
+
it_has_behavior "doesn't raise an error"
|
100
|
+
|
101
|
+
it_has_behavior 'returns and succeeds'
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'for block that fails more than threshold' do
|
105
|
+
let(:failer) { ScriptedFailer.new(6) }
|
106
|
+
let(:max_attempts) { 1 }
|
107
|
+
|
108
|
+
context 'without on_max_attempts_exceeded specified' do
|
109
|
+
let(:options) { { max_attempts: max_attempts, logger: null_logger } }
|
110
|
+
|
111
|
+
it_has_behavior 'raises an error'
|
112
|
+
end
|
113
|
+
|
114
|
+
context 'with on_max_attempts_exceeded specified (which does not raise)' do
|
115
|
+
let(:do_nothing_proc) { proc {} }
|
116
|
+
let(:options) do
|
117
|
+
{
|
118
|
+
max_attempts: max_attempts,
|
119
|
+
on_max_attempts_exceeded: do_nothing_proc,
|
120
|
+
logger: null_logger
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
it_has_behavior "doesn't raise an error"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
context 'for retry_action calls with provided retry classes and message strings' do
|
129
|
+
let(:intended_result) { 1000 }
|
130
|
+
let(:failer) { ScriptedFailer.new(2, intended_result) }
|
131
|
+
let(:action) { failer.fail(actual_error_class, actual_error_message) }
|
132
|
+
let(:matching_error_class) { MatchedError }
|
133
|
+
let(:non_matching_error_class) { UnmatchedError }
|
134
|
+
let(:matching_error_message) { 'Lost connection to MySQL server' }
|
135
|
+
let(:non_matching_error_message) { 'You have an error in your SQL syntax' }
|
136
|
+
|
137
|
+
context 'Using retry error classes' do
|
138
|
+
let(:options) do
|
139
|
+
{
|
140
|
+
retry_error_classes: expected_error_classes,
|
141
|
+
message: /^Lost connection to MySQL server/,
|
142
|
+
max_attempts: 10,
|
143
|
+
logger: null_logger
|
144
|
+
}
|
145
|
+
end
|
146
|
+
|
147
|
+
context 'when error_classes is a single error class' do
|
148
|
+
let(:expected_error_classes) { matching_error_class }
|
149
|
+
|
150
|
+
it_has_behavior 'raises an error only in the correct cases with a retry class'
|
151
|
+
end
|
152
|
+
|
153
|
+
context 'when error_classes is a list of error classes' do
|
154
|
+
let(:expected_error_classes) { [ArgumentError, matching_error_class] }
|
155
|
+
|
156
|
+
it_has_behavior 'raises an error only in the correct cases with a retry class'
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
context 'Using reraise error classes' do
|
161
|
+
let(:options) do
|
162
|
+
{
|
163
|
+
reraise_error_classes: expected_error_classes,
|
164
|
+
max_attempts: 10,
|
165
|
+
logger: null_logger
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
context 'when error_classes is a single error class' do
|
170
|
+
let(:expected_error_classes) { matching_error_class }
|
171
|
+
|
172
|
+
it_has_behavior 'raises an error only in the correct cases with a reraise class'
|
173
|
+
end
|
174
|
+
|
175
|
+
context 'when error_classes is a list of error classes' do
|
176
|
+
let(:expected_error_classes) { [ArgumentError, matching_error_class] }
|
177
|
+
|
178
|
+
it_has_behavior 'raises an error only in the correct cases with a reraise class'
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
describe 'logger' do
|
185
|
+
context 'when not otherwise configured' do
|
186
|
+
it 'defaults to the ruby logger' do
|
187
|
+
Pester.configure do |config|
|
188
|
+
config.logger = nil
|
189
|
+
end
|
190
|
+
expect(Pester::Config.logger).to_not be_nil
|
191
|
+
expect(Pester::Config.logger).to be_kind_of(Logger)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
context 'when configured to use a particular class' do
|
196
|
+
it 'users that class' do
|
197
|
+
Pester.configure do |config|
|
198
|
+
config.logger = NullLogger.new
|
199
|
+
end
|
200
|
+
expect(Pester::Config.logger).to_not be_nil
|
201
|
+
expect(Pester::Config.logger).to be_kind_of(NullLogger)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')))
|
2
|
+
require 'pester'
|
3
|
+
|
4
|
+
Dir.glob('./spec/helpers/**/*.rb').each { |f| require f }
|
5
|
+
|
6
|
+
RSpec.configure do |c|
|
7
|
+
c.alias_it_should_behave_like_to :it_has_behavior, 'has behavior:'
|
8
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pester
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Marc Bollinger
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-03-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.2'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.2'
|
55
|
+
description: |2
|
56
|
+
|We found ourselves constantly wrapping network-facing calls with all kinds of bespoke,
|
57
|
+
| copied, and rewritten retry logic. This gem is an attempt to unify common behaviors,
|
58
|
+
| like simple retry, retry with linear backoff, and retry with exponential backoff.
|
59
|
+
email:
|
60
|
+
- marc@lumoslabs.com
|
61
|
+
executables: []
|
62
|
+
extensions: []
|
63
|
+
extra_rdoc_files: []
|
64
|
+
files:
|
65
|
+
- ".gitignore"
|
66
|
+
- ".rspec"
|
67
|
+
- ".rubocop.yml"
|
68
|
+
- Gemfile
|
69
|
+
- LICENSE.txt
|
70
|
+
- README.md
|
71
|
+
- Rakefile
|
72
|
+
- lib/pester.rb
|
73
|
+
- lib/pester/behaviors.rb
|
74
|
+
- lib/pester/behaviors/sleep.rb
|
75
|
+
- lib/pester/config.rb
|
76
|
+
- lib/pester/version.rb
|
77
|
+
- pester.gemspec
|
78
|
+
- spec/helpers/null_logger.rb
|
79
|
+
- spec/helpers/scripted_failer.rb
|
80
|
+
- spec/pester_spec.rb
|
81
|
+
- spec/spec_helper.rb
|
82
|
+
homepage: https://github.com/lumoslabs/pester
|
83
|
+
licenses:
|
84
|
+
- MIT
|
85
|
+
metadata: {}
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
requirements: []
|
101
|
+
rubyforge_project:
|
102
|
+
rubygems_version: 2.2.2
|
103
|
+
signing_key:
|
104
|
+
specification_version: 4
|
105
|
+
summary: Common block-based retry for external calls.
|
106
|
+
test_files:
|
107
|
+
- spec/helpers/null_logger.rb
|
108
|
+
- spec/helpers/scripted_failer.rb
|
109
|
+
- spec/pester_spec.rb
|
110
|
+
- spec/spec_helper.rb
|
111
|
+
has_rdoc:
|