resilient 0.4.0.beta1 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/ci.yml +28 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +7 -2
- data/README.md +20 -4
- data/examples/binary.rb +114 -0
- data/lib/resilient/circuit_breaker.rb +26 -40
- data/lib/resilient/circuit_breaker/metrics.rb +13 -15
- data/lib/resilient/circuit_breaker/properties.rb +12 -0
- data/lib/resilient/circuit_breaker/registry.rb +10 -2
- data/lib/resilient/test/circuit_breaker_registry_interface.rb +8 -9
- data/lib/resilient/test/metrics_interface.rb +10 -16
- data/lib/resilient/version.rb +1 -1
- data/resilient.gemspec +1 -1
- metadata +12 -11
- data/.travis.yml +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2e439a74385ae133b7d375d90c75dcc302a1cd77a56d69facda7b04f2de07124
|
4
|
+
data.tar.gz: bceca2780a2b2b71ff1b0084db9e2c6ed206345114d2849d1da1c957e1995365
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a58ea0dce1f90dc61f4a3d34d0fa6f7ad7ca64fd246776a6696ee2e7ae1f2df9447b9f1ba394a914503320b40a1d3a2b326fd27e0e954e826fb985209233330
|
7
|
+
data.tar.gz: '088e66a7f364869da5da43f926dead2cd62af861baaa07b3a4615e521ac1ab75ecdeb4e6c7aee0b1f40ce2a692e66cca25cd2737bfe2c3fdc1635142fb96fb3a'
|
@@ -0,0 +1,28 @@
|
|
1
|
+
name: CI
|
2
|
+
on: [push, pull_request]
|
3
|
+
jobs:
|
4
|
+
build:
|
5
|
+
runs-on: ubuntu-latest
|
6
|
+
strategy:
|
7
|
+
matrix:
|
8
|
+
ruby: ['2.5', '2.6', '2.7']
|
9
|
+
steps:
|
10
|
+
- name: Check out repository code
|
11
|
+
uses: actions/checkout@v2
|
12
|
+
- name: Do some action caching
|
13
|
+
uses: actions/cache@v1
|
14
|
+
with:
|
15
|
+
path: vendor/bundle
|
16
|
+
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
|
17
|
+
restore-keys: |
|
18
|
+
${{ runner.os }}-gem-
|
19
|
+
- name: Set up Ruby
|
20
|
+
uses: actions/setup-ruby@v1
|
21
|
+
with:
|
22
|
+
ruby-version: ${{ matrix.ruby }}
|
23
|
+
- name: Install bundler
|
24
|
+
run: gem install bundler
|
25
|
+
- name: Run bundler
|
26
|
+
run: bundle install --jobs 4 --retry 3
|
27
|
+
- name: Run Tests
|
28
|
+
run: script/test
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
## 0.5.1
|
2
|
+
|
3
|
+
* [Added two new events open_circuit and close_circuit](https://github.com/jnunemaker/resilient/pull/16) and [renamed them here](https://github.com/jnunemaker/resilient/commit/407e673eb5268912814398bebdeb173793d0af05).
|
4
|
+
|
5
|
+
## 0.5.0
|
6
|
+
|
7
|
+
* [tweaked interface for metrics](https://github.com/jnunemaker/resilient/pull/15)
|
8
|
+
* [stop rounding error percentage](https://github.com/jnunemaker/resilient/commit/6cdade5c3fc5f75a6ffde87082771608d582026c)
|
9
|
+
|
10
|
+
## prior to 0.5.0
|
11
|
+
|
12
|
+
* No changelog kept so just checkout the commits. There aren't too terrible many.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Some tools to aid in resiliency in Ruby. For now, just a circuit breaker (~~stolen from~~ based on [hystrix](https://github.com/netflix/hystrix)). Soon much more...
|
4
4
|
|
5
|
-
Nothing asynchronous or thread safe yet either, but open to it and would like to see more around it in the future.
|
5
|
+
Nothing asynchronous or thread safe yet either, but open to it and would like to see more around it in the future. See more here: [jnunemaker/resilient#18](https://github.com/jnunemaker/resilient/issues/18).
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
@@ -29,7 +29,7 @@ require "resilient/circuit_breaker"
|
|
29
29
|
# CircuitBreaker.new as `get` keeps a registry of circuits by key to prevent
|
30
30
|
# creating multiple instances of the same circuit breaker for a key; not using
|
31
31
|
# `get` means you would have multiple instances of the circuit breaker and thus
|
32
|
-
# separate state and metrics; you can read more in examples/
|
32
|
+
# separate state and metrics; you can read more in examples/get_vs_new.rb
|
33
33
|
circuit_breaker = Resilient::CircuitBreaker.get("example")
|
34
34
|
if circuit_breaker.allow_request?
|
35
35
|
begin
|
@@ -82,14 +82,30 @@ circuit_breaker = Resilient::CircuitBreaker.get("example", {
|
|
82
82
|
# etc etc etc
|
83
83
|
```
|
84
84
|
|
85
|
+
## Default Properties
|
86
|
+
|
87
|
+
Property | Default | Notes
|
88
|
+
--------------------------------|------------------------|--------
|
89
|
+
**:force_open** | `false` | allows forcing the circuit open (stopping all requests)
|
90
|
+
**:force_closed** | `false` | allows ignoring errors and therefore never trip "open" (e.g. allow all traffic through); normal instrumentation will still happen, thus allowing you to "test" configuration live without impact
|
91
|
+
**:instrumenter** | `Instrumenters::Noop` | what to use to instrument all events that happen (e.g. `ActiveSupport::Notifications`)
|
92
|
+
**:sleep_window_seconds** | `5` | seconds after tripping circuit before allowing retry
|
93
|
+
**:request_volume_threshold** | `20` | number of requests that must be made within a statistical window before open/close decisions are made using stats
|
94
|
+
**:error_threshold_percentage** | `50` | % of "marks" that must be failed to trip the circuit
|
95
|
+
**:window_size_in_seconds** | `60` | number of seconds in the statistical window
|
96
|
+
**:bucket_size_in_seconds** | `10` | size of buckets in statistical window
|
97
|
+
**:metrics** | `Resilient::Metrics.new` | metrics instance used to keep track of success and failure
|
98
|
+
|
85
99
|
## Tests
|
86
100
|
|
87
|
-
To ensure that you have
|
101
|
+
To ensure that you have clean circuit breakers for each test case, be sure to run the following in the setup for your tests (which resets the default registry and thus clears all the registered circuits) either before every test case or at a minimum each test case that uses circuit breakers.
|
88
102
|
|
89
103
|
```ruby
|
90
|
-
Resilient::CircuitBreaker.reset
|
104
|
+
Resilient::CircuitBreaker::Registry.reset
|
91
105
|
```
|
92
106
|
|
107
|
+
**Note**: If you use a non-default registry, you'll need to reset that on your own. If you don't know what I'm talking about, you are fine.
|
108
|
+
|
93
109
|
## Development
|
94
110
|
|
95
111
|
```bash
|
data/examples/binary.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
# setting up load path
|
2
|
+
require "pathname"
|
3
|
+
root_path = Pathname(__FILE__).dirname.join("..").expand_path
|
4
|
+
lib_path = root_path.join("lib")
|
5
|
+
$:.unshift(lib_path)
|
6
|
+
|
7
|
+
# requiring stuff for this example
|
8
|
+
require "pp"
|
9
|
+
require "minitest/autorun"
|
10
|
+
require "resilient/circuit_breaker"
|
11
|
+
require "resilient/test/metrics_interface"
|
12
|
+
|
13
|
+
# Metrics class that closes circuit on every success call and opens circuit for
|
14
|
+
# sleep_window_seconds on every failure.
|
15
|
+
class BinaryMetrics
|
16
|
+
def initialize(options = {})
|
17
|
+
reset
|
18
|
+
end
|
19
|
+
|
20
|
+
def success
|
21
|
+
reset
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def failure
|
26
|
+
@closed = false
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def reset
|
31
|
+
@closed = true
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def under_request_volume_threshold?(request_volume_threshold)
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
def under_error_threshold_percentage?(error_threshold_percentage)
|
40
|
+
@closed
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class BinaryMetricsTest < Minitest::Test
|
45
|
+
def setup
|
46
|
+
@object = BinaryMetrics.new
|
47
|
+
end
|
48
|
+
|
49
|
+
include Resilient::Test::MetricsInterface
|
50
|
+
end
|
51
|
+
|
52
|
+
circuit_breaker = Resilient::CircuitBreaker.get("example", {
|
53
|
+
sleep_window_seconds: 1,
|
54
|
+
metrics: BinaryMetrics.new,
|
55
|
+
})
|
56
|
+
|
57
|
+
# success
|
58
|
+
if circuit_breaker.allow_request?
|
59
|
+
begin
|
60
|
+
puts "do expensive thing"
|
61
|
+
circuit_breaker.success
|
62
|
+
rescue => boom
|
63
|
+
# won't get here in this example
|
64
|
+
circuit_breaker.failure
|
65
|
+
end
|
66
|
+
else
|
67
|
+
raise "will not get here"
|
68
|
+
end
|
69
|
+
|
70
|
+
# failure
|
71
|
+
if circuit_breaker.allow_request?
|
72
|
+
begin
|
73
|
+
raise
|
74
|
+
rescue => boom
|
75
|
+
circuit_breaker.failure
|
76
|
+
puts "failed slow, do fallback"
|
77
|
+
end
|
78
|
+
else
|
79
|
+
raise "will not get here"
|
80
|
+
end
|
81
|
+
|
82
|
+
# fail fast
|
83
|
+
if circuit_breaker.allow_request?
|
84
|
+
raise "will not get here"
|
85
|
+
else
|
86
|
+
puts "failed fast, do fallback"
|
87
|
+
end
|
88
|
+
|
89
|
+
start = Time.now
|
90
|
+
|
91
|
+
while (Time.now - start) < 3
|
92
|
+
if circuit_breaker.allow_request?
|
93
|
+
puts "doing a single attempt as we've failed fast for sleep_window_seconds"
|
94
|
+
break
|
95
|
+
else
|
96
|
+
puts "failed fast, do fallback"
|
97
|
+
end
|
98
|
+
sleep rand(0.1)
|
99
|
+
end
|
100
|
+
|
101
|
+
if circuit_breaker.allow_request?
|
102
|
+
raise "will not get here"
|
103
|
+
else
|
104
|
+
puts "request denied because single request has not been marked success yet"
|
105
|
+
end
|
106
|
+
|
107
|
+
puts "marking single request as success"
|
108
|
+
circuit_breaker.success
|
109
|
+
|
110
|
+
if circuit_breaker.allow_request?
|
111
|
+
puts "circuit reset and back closed now, allowing requests"
|
112
|
+
else
|
113
|
+
raise "will not get here"
|
114
|
+
end
|
@@ -2,15 +2,11 @@ require "resilient/key"
|
|
2
2
|
require "resilient/circuit_breaker/metrics"
|
3
3
|
require "resilient/circuit_breaker/properties"
|
4
4
|
require "resilient/circuit_breaker/registry"
|
5
|
+
require "forwardable"
|
5
6
|
|
6
7
|
module Resilient
|
7
8
|
class CircuitBreaker
|
8
|
-
|
9
|
-
# Useful for ensuring a clean environment between tests. If you are using a
|
10
|
-
# registry other than the default, you will need to handle resets on your own.
|
11
|
-
def self.reset
|
12
|
-
Registry.default.reset
|
13
|
-
end
|
9
|
+
extend Forwardable
|
14
10
|
|
15
11
|
# Public: Returns an instance of circuit breaker based on key and registry.
|
16
12
|
# Default registry is used if none is provided. If key does not exist, it is
|
@@ -18,11 +14,11 @@ module Resilient
|
|
18
14
|
# allocating a new instance in order to ensure that state/metrics are the
|
19
15
|
# same per key.
|
20
16
|
#
|
21
|
-
# See #initialize for docs on key
|
22
|
-
def self.get(key, properties = nil,
|
17
|
+
# See #initialize for docs on key and properties.
|
18
|
+
def self.get(key, properties = nil, registry = nil)
|
23
19
|
key = Key.wrap(key)
|
24
20
|
(registry || Registry.default).fetch(key) {
|
25
|
-
new(key, properties
|
21
|
+
new(key, properties)
|
26
22
|
}
|
27
23
|
end
|
28
24
|
|
@@ -33,10 +29,11 @@ module Resilient
|
|
33
29
|
end
|
34
30
|
end
|
35
31
|
|
32
|
+
def_delegator :@properties, :metrics
|
33
|
+
|
36
34
|
attr_reader :key
|
37
35
|
attr_reader :open
|
38
36
|
attr_reader :opened_or_last_checked_at_epoch
|
39
|
-
attr_reader :metrics
|
40
37
|
attr_reader :properties
|
41
38
|
|
42
39
|
# Private: Builds new instance of a CircuitBreaker.
|
@@ -48,32 +45,14 @@ module Resilient
|
|
48
45
|
# circuit breaker should behave. Optional. Defaults to new
|
49
46
|
# Resilient::CircuitBreaker::Properties instance.
|
50
47
|
#
|
51
|
-
# metrics - The object that stores successes and failures. Optional.
|
52
|
-
# Defaults to new Resilient::CircuitBreaker::Metrics instance
|
53
|
-
# based on window size and bucket size properties.
|
54
|
-
#
|
55
48
|
# Returns CircuitBreaker instance.
|
56
|
-
def initialize(key, properties = nil
|
49
|
+
def initialize(key, properties = nil)
|
57
50
|
raise ArgumentError, "key argument is required" if key.nil?
|
58
51
|
|
59
52
|
@key = Key.wrap(key)
|
53
|
+
@properties = Properties.wrap(properties)
|
60
54
|
@open = false
|
61
55
|
@opened_or_last_checked_at_epoch = 0
|
62
|
-
|
63
|
-
@properties = if properties
|
64
|
-
Properties.wrap(properties)
|
65
|
-
else
|
66
|
-
Properties.new
|
67
|
-
end
|
68
|
-
|
69
|
-
@metrics = if metrics
|
70
|
-
metrics
|
71
|
-
else
|
72
|
-
Metrics.new({
|
73
|
-
window_size_in_seconds: @properties.window_size_in_seconds,
|
74
|
-
bucket_size_in_seconds: @properties.bucket_size_in_seconds,
|
75
|
-
})
|
76
|
-
end
|
77
56
|
end
|
78
57
|
|
79
58
|
def allow_request?
|
@@ -102,7 +81,7 @@ module Resilient
|
|
102
81
|
payload[:closed_the_circuit] = true
|
103
82
|
close_circuit
|
104
83
|
else
|
105
|
-
|
84
|
+
metrics.success
|
106
85
|
end
|
107
86
|
nil
|
108
87
|
}
|
@@ -110,7 +89,7 @@ module Resilient
|
|
110
89
|
|
111
90
|
def failure
|
112
91
|
instrument("resilient.circuit_breaker.failure", key: @key) { |payload|
|
113
|
-
|
92
|
+
metrics.failure
|
114
93
|
nil
|
115
94
|
}
|
116
95
|
end
|
@@ -119,7 +98,7 @@ module Resilient
|
|
119
98
|
instrument("resilient.circuit_breaker.reset", key: @key) { |payload|
|
120
99
|
@open = false
|
121
100
|
@opened_or_last_checked_at_epoch = 0
|
122
|
-
|
101
|
+
metrics.reset
|
123
102
|
nil
|
124
103
|
}
|
125
104
|
end
|
@@ -127,22 +106,29 @@ module Resilient
|
|
127
106
|
private
|
128
107
|
|
129
108
|
def open_circuit
|
130
|
-
@
|
131
|
-
|
109
|
+
instrument("resilient.circuit_breaker.open_circuit", key: @key) { |payload|
|
110
|
+
@opened_or_last_checked_at_epoch = Time.now.to_i
|
111
|
+
@open = true
|
112
|
+
payload[:open] = @open
|
113
|
+
}
|
132
114
|
end
|
133
115
|
|
134
116
|
def close_circuit
|
135
|
-
@
|
136
|
-
|
137
|
-
|
117
|
+
instrument("resilient.circuit_breaker.close_circuit", key: @key) { |payload|
|
118
|
+
@open = false
|
119
|
+
payload[:open] = @open
|
120
|
+
payload[:interval] = Time.now.to_i - @opened_or_last_checked_at_epoch
|
121
|
+
@opened_or_last_checked_at_epoch = 0
|
122
|
+
}
|
123
|
+
metrics.reset
|
138
124
|
end
|
139
125
|
|
140
126
|
def under_request_volume_threshold?
|
141
|
-
|
127
|
+
metrics.under_request_volume_threshold?(@properties.request_volume_threshold)
|
142
128
|
end
|
143
129
|
|
144
130
|
def under_error_threshold_percentage?
|
145
|
-
|
131
|
+
metrics.under_error_threshold_percentage?(@properties.error_threshold_percentage)
|
146
132
|
end
|
147
133
|
|
148
134
|
def open?
|
@@ -30,6 +30,14 @@ module Resilient
|
|
30
30
|
@buckets = []
|
31
31
|
end
|
32
32
|
|
33
|
+
def under_request_volume_threshold?(request_volume_threshold)
|
34
|
+
requests < request_volume_threshold
|
35
|
+
end
|
36
|
+
|
37
|
+
def under_error_threshold_percentage?(error_threshold_percentage)
|
38
|
+
error_percentage < error_threshold_percentage
|
39
|
+
end
|
40
|
+
|
33
41
|
def success
|
34
42
|
@storage.increment(current_bucket, StorageSuccessKeys)
|
35
43
|
prune_buckets
|
@@ -42,15 +50,12 @@ module Resilient
|
|
42
50
|
nil
|
43
51
|
end
|
44
52
|
|
45
|
-
def
|
46
|
-
|
47
|
-
|
53
|
+
def reset
|
54
|
+
@storage.prune(@buckets, StorageKeys)
|
55
|
+
nil
|
48
56
|
end
|
49
57
|
|
50
|
-
|
51
|
-
prune_buckets
|
52
|
-
@storage.sum(@buckets, :failures)[:failures]
|
53
|
-
end
|
58
|
+
private
|
54
59
|
|
55
60
|
def requests
|
56
61
|
prune_buckets
|
@@ -71,16 +76,9 @@ module Resilient
|
|
71
76
|
requests = successes + failures
|
72
77
|
return 0 if failures == 0 || requests == 0
|
73
78
|
|
74
|
-
(
|
79
|
+
(failures / requests.to_f) * 100
|
75
80
|
end
|
76
81
|
|
77
|
-
def reset
|
78
|
-
@storage.prune(@buckets, StorageKeys)
|
79
|
-
nil
|
80
|
-
end
|
81
|
-
|
82
|
-
private
|
83
|
-
|
84
82
|
def current_bucket(timestamp = Time.now.to_i)
|
85
83
|
bucket = @buckets.detect { |bucket| bucket.include?(timestamp) }
|
86
84
|
return bucket if bucket
|
@@ -12,6 +12,8 @@ module Resilient
|
|
12
12
|
hash_or_instance
|
13
13
|
when Hash
|
14
14
|
new(hash_or_instance)
|
15
|
+
when NilClass
|
16
|
+
new
|
15
17
|
else
|
16
18
|
raise TypeError, "properties must be Hash or Resilient::Properties instance"
|
17
19
|
end
|
@@ -45,6 +47,9 @@ module Resilient
|
|
45
47
|
# size of buckets in statistical window
|
46
48
|
attr_reader :bucket_size_in_seconds
|
47
49
|
|
50
|
+
# metrics instance used to keep track of success and failure
|
51
|
+
attr_reader :metrics
|
52
|
+
|
48
53
|
def initialize(options = {})
|
49
54
|
@force_open = options.fetch(:force_open, false)
|
50
55
|
@force_closed = options.fetch(:force_closed, false)
|
@@ -64,6 +69,13 @@ module Resilient
|
|
64
69
|
"window_size_in_seconds must be perfectly divisible by" +
|
65
70
|
" bucket_size_in_seconds in order to evenly partition the buckets"
|
66
71
|
end
|
72
|
+
|
73
|
+
@metrics = options.fetch(:metrics) {
|
74
|
+
Metrics.new({
|
75
|
+
window_size_in_seconds: @window_size_in_seconds,
|
76
|
+
bucket_size_in_seconds: @bucket_size_in_seconds,
|
77
|
+
})
|
78
|
+
}
|
67
79
|
end
|
68
80
|
end
|
69
81
|
end
|
@@ -11,11 +11,19 @@ module Resilient
|
|
11
11
|
@default = value
|
12
12
|
end
|
13
13
|
|
14
|
+
# Public: Reset the default registry. This completely wipes all instances
|
15
|
+
# by swapping out the default registry for a new one and letting the old
|
16
|
+
# one get GC'd. Useful in tests to get a completely clean slate.
|
17
|
+
def self.reset
|
18
|
+
default.reset
|
19
|
+
end
|
20
|
+
|
14
21
|
def initialize(source = nil)
|
15
22
|
@source = source || {}
|
16
23
|
end
|
17
24
|
|
18
|
-
# Setup default to
|
25
|
+
# Setup new instance as default. Needs to be after initialize so hash gets
|
26
|
+
# initialize correctly.
|
19
27
|
@default = new
|
20
28
|
|
21
29
|
# Internal: To be used by CircuitBreaker to either get an instance for a
|
@@ -38,7 +46,7 @@ module Resilient
|
|
38
46
|
# breakers, which should only really be used for cleaning up in
|
39
47
|
# test environment.
|
40
48
|
def reset
|
41
|
-
@source
|
49
|
+
@source = {}
|
42
50
|
nil
|
43
51
|
end
|
44
52
|
end
|
@@ -20,18 +20,17 @@ module Resilient
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def test_reset
|
23
|
-
|
24
|
-
|
25
|
-
bar_value.expect :reset, nil, []
|
26
|
-
wick_value.expect :reset, nil, []
|
27
|
-
|
28
|
-
@object.fetch("foo") { bar_value }
|
29
|
-
@object.fetch("baz") { wick_value }
|
23
|
+
original_foo = @object.fetch("foo") { Object.new }
|
24
|
+
original_bar = @object.fetch("bar") { Object.new }
|
30
25
|
|
31
26
|
assert_nil @object.reset
|
32
27
|
|
33
|
-
|
34
|
-
|
28
|
+
foo = @object.fetch("foo") { Object.new }
|
29
|
+
bar = @object.fetch("bar") { Object.new }
|
30
|
+
|
31
|
+
# assert that the objects before and after reset are not the same object
|
32
|
+
refute original_foo.equal?(foo)
|
33
|
+
refute original_bar.equal?(bar)
|
35
34
|
end
|
36
35
|
|
37
36
|
def test_reset_empty_registry
|
@@ -1,6 +1,16 @@
|
|
1
1
|
module Resilient
|
2
2
|
class Test
|
3
3
|
module MetricsInterface
|
4
|
+
def test_responds_to_under_request_volume_threshold_predicate
|
5
|
+
assert_respond_to @object, :under_request_volume_threshold?
|
6
|
+
assert_equal 1, @object.method(:under_request_volume_threshold?).arity
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_responds_to_under_error_threshold_percentage_predicate
|
10
|
+
assert_respond_to @object, :under_error_threshold_percentage?
|
11
|
+
assert_equal 1, @object.method(:under_error_threshold_percentage?).arity
|
12
|
+
end
|
13
|
+
|
4
14
|
def test_responds_to_success
|
5
15
|
assert_respond_to @object, :success
|
6
16
|
end
|
@@ -17,22 +27,6 @@ module Resilient
|
|
17
27
|
assert_nil @object.failure
|
18
28
|
end
|
19
29
|
|
20
|
-
def test_responds_to_successes
|
21
|
-
assert_respond_to @object, :successes
|
22
|
-
end
|
23
|
-
|
24
|
-
def test_responds_to_failures
|
25
|
-
assert_respond_to @object, :failures
|
26
|
-
end
|
27
|
-
|
28
|
-
def test_responds_to_requests
|
29
|
-
assert_respond_to @object, :requests
|
30
|
-
end
|
31
|
-
|
32
|
-
def test_responds_to_error_percentage
|
33
|
-
assert_respond_to @object, :error_percentage
|
34
|
-
end
|
35
|
-
|
36
30
|
def test_responds_to_reset
|
37
31
|
assert_respond_to @object, :reset
|
38
32
|
end
|
data/lib/resilient/version.rb
CHANGED
data/resilient.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_development_dependency "bundler", "
|
21
|
+
spec.add_development_dependency "bundler", "< 3.0"
|
22
22
|
spec.add_development_dependency "minitest", "~> 5.8"
|
23
23
|
spec.add_development_dependency "timecop", "~> 0.8.0"
|
24
24
|
end
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: resilient
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Nunemaker
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-04-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - "<"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '3.0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - "<"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '3.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: minitest
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -59,13 +59,15 @@ executables: []
|
|
59
59
|
extensions: []
|
60
60
|
extra_rdoc_files: []
|
61
61
|
files:
|
62
|
+
- ".github/workflows/ci.yml"
|
62
63
|
- ".gitignore"
|
63
|
-
-
|
64
|
+
- CHANGELOG.md
|
64
65
|
- Gemfile
|
65
66
|
- Guardfile
|
66
67
|
- LICENSE.txt
|
67
68
|
- README.md
|
68
69
|
- examples/basic.rb
|
70
|
+
- examples/binary.rb
|
69
71
|
- examples/get_vs_new.rb
|
70
72
|
- examples/long_running.rb
|
71
73
|
- lib/resilient.rb
|
@@ -108,12 +110,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
108
110
|
version: '0'
|
109
111
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
112
|
requirements:
|
111
|
-
- - "
|
113
|
+
- - ">="
|
112
114
|
- !ruby/object:Gem::Version
|
113
|
-
version:
|
115
|
+
version: '0'
|
114
116
|
requirements: []
|
115
|
-
|
116
|
-
rubygems_version: 2.4.5.1
|
117
|
+
rubygems_version: 3.0.3
|
117
118
|
signing_key:
|
118
119
|
specification_version: 4
|
119
120
|
summary: toolkit for resilient ruby apps
|