circuitbox 1.0.3 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/.travis.yml +1 -0
- data/README.md +7 -2
- data/circuitbox.gemspec +12 -2
- data/lib/circuitbox/circuit_breaker.rb +2 -1
- data/lib/circuitbox/version.rb +1 -1
- data/test/circuit_breaker_test.rb +119 -140
- data/test/circuitbox_test.rb +30 -39
- data/test/notifier_test.rb +5 -5
- data/test/service_failure_error_test.rb +11 -18
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bb9f064a8178b7bfef8be4552f91410d0783acf5
|
4
|
+
data.tar.gz: ca1e8126ff01603f881c351c28c47396a8fa7d97
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f9e3f74178e1cf6200d5dbbf99925ddb1f235ebf3d287e3c82bd0894d1430b69a4ed062bbaf9afb9cacd5b5707c32cf2dcbd10cd78293b5f438f3b738b956837
|
7
|
+
data.tar.gz: fe2a7be07392bd92a0a9e731550f82f1b4e3c6d2ed21759c25d91776e5734d538bb4e7d5bc33da09edd8de5ea2ccdb731e6e8baa36946ea02a7e908415b5c17e
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.2.2
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -66,6 +66,9 @@ class ExampleServiceClient
|
|
66
66
|
|
67
67
|
# seconds before the circuit times out
|
68
68
|
timeout_seconds: 1
|
69
|
+
|
70
|
+
# Logger to use
|
71
|
+
logger: Logger.new(STDOUT)
|
69
72
|
})
|
70
73
|
end
|
71
74
|
end
|
@@ -298,7 +301,9 @@ c.use Circuitbox::FaradayMiddleware, open_circuit: lambda { |response| response.
|
|
298
301
|
```
|
299
302
|
|
300
303
|
## CHANGELOG
|
301
|
-
###
|
304
|
+
### v1.1.0
|
305
|
+
- ruby 2.2 support [#58](https://github.com/yammer/circuitbox/pull/58)
|
306
|
+
- configurable logger [#58](https://github.com/yammer/circuitbox/pull/58)
|
302
307
|
|
303
308
|
### v1.0.3
|
304
309
|
- fix timeout issue for default configuration, as default `:Memory` adapter does
|
@@ -315,7 +320,7 @@ c.use Circuitbox::FaradayMiddleware, open_circuit: lambda { |response| response.
|
|
315
320
|
removing the related railtie.
|
316
321
|
|
317
322
|
### v1.0.0
|
318
|
-
- support for cross process circuitbreakers by swapping the circuitbreaker store for a
|
323
|
+
- support for cross process circuitbreakers by swapping the circuitbreaker store for a
|
319
324
|
`Moneta` supported key value store.
|
320
325
|
- Change `FaradayMiddleware` default behaviour to not open on `4xx` errors but just on `5xx`
|
321
326
|
server errors and connection errors
|
data/circuitbox.gemspec
CHANGED
@@ -3,6 +3,8 @@ lib = File.expand_path('../lib', __FILE__)
|
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
4
|
require 'circuitbox/version'
|
5
5
|
|
6
|
+
ruby_2_2_2_plus = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.2.2")
|
7
|
+
|
6
8
|
Gem::Specification.new do |spec|
|
7
9
|
spec.name = "circuitbox"
|
8
10
|
spec.version = Circuitbox::VERSION
|
@@ -20,7 +22,11 @@ Gem::Specification.new do |spec|
|
|
20
22
|
|
21
23
|
spec.add_development_dependency "bundler"
|
22
24
|
spec.add_development_dependency "rake"
|
23
|
-
|
25
|
+
if ruby_2_2_2_plus
|
26
|
+
spec.add_development_dependency "rack"
|
27
|
+
else
|
28
|
+
spec.add_development_dependency "rack", '< 2'
|
29
|
+
end
|
24
30
|
spec.add_development_dependency "gimme"
|
25
31
|
spec.add_development_dependency "minitest"
|
26
32
|
spec.add_development_dependency "mocha"
|
@@ -33,6 +39,10 @@ Gem::Specification.new do |spec|
|
|
33
39
|
spec.add_development_dependency "lmdb"
|
34
40
|
spec.add_development_dependency "daybreak"
|
35
41
|
|
36
|
-
|
42
|
+
if ruby_2_2_2_plus
|
43
|
+
spec.add_dependency "activesupport"
|
44
|
+
else
|
45
|
+
spec.add_dependency "activesupport", '< 5'
|
46
|
+
end
|
37
47
|
spec.add_dependency "moneta"
|
38
48
|
end
|
@@ -20,6 +20,7 @@ class Circuitbox
|
|
20
20
|
# `timeout_seconds` - seconds until it will timeout the request
|
21
21
|
# `exceptions` - exceptions other than Timeout::Error that count as failures
|
22
22
|
# `time_window` - interval of time used to calculate error_rate (in seconds) - default is 60s
|
23
|
+
# `logger` - Logger to use - defaults to Rails.logger if defined, otherwise STDOUT
|
23
24
|
#
|
24
25
|
def initialize(service, options = {})
|
25
26
|
@service = service
|
@@ -30,7 +31,7 @@ class Circuitbox
|
|
30
31
|
@exceptions = options.fetch(:exceptions) { [] }
|
31
32
|
@exceptions = [Timeout::Error] if @exceptions.blank?
|
32
33
|
|
33
|
-
@logger = defined?(Rails) ? Rails.logger : Logger.new(STDOUT)
|
34
|
+
@logger = options.fetch(:logger) { defined?(Rails) ? Rails.logger : Logger.new(STDOUT) }
|
34
35
|
@time_class = options.fetch(:time_class) { Time }
|
35
36
|
sanitize_options
|
36
37
|
end
|
data/lib/circuitbox/version.rb
CHANGED
@@ -1,26 +1,15 @@
|
|
1
1
|
require 'test_helper'
|
2
|
-
require 'ostruct'
|
3
2
|
|
4
3
|
class CircuitBreakerTest < Minitest::Test
|
5
|
-
SUCCESSFUL_RESPONSE_STRING = "Success!"
|
6
|
-
RequestFailureError = Timeout::Error
|
7
4
|
class ConnectionError < StandardError; end;
|
8
|
-
class SomeOtherError < StandardError; end;
|
9
5
|
|
10
6
|
def setup
|
11
7
|
Circuitbox::CircuitBreaker.reset
|
12
8
|
end
|
13
9
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
:sleep_window => 1,
|
18
|
-
:time_window => 10
|
19
|
-
)
|
20
|
-
assert_equal circuit.option_value(:sleep_window),
|
21
|
-
circuit.option_value(:time_window),
|
22
|
-
'sleep_window has not been corrected properly'
|
23
|
-
end
|
10
|
+
def test_sleep_window_is_forced_to_equal_time_window
|
11
|
+
circuit = Circuitbox::CircuitBreaker.new(:yammer, sleep_window: 1, time_window: 10)
|
12
|
+
assert_equal circuit.option_value(:sleep_window), circuit.option_value(:time_window)
|
24
13
|
end
|
25
14
|
|
26
15
|
def test_goes_into_half_open_state_on_sleep
|
@@ -29,35 +18,28 @@ class CircuitBreakerTest < Minitest::Test
|
|
29
18
|
assert circuit.send(:half_open?)
|
30
19
|
end
|
31
20
|
|
32
|
-
|
33
|
-
describe 'ratio' do
|
34
|
-
def cb_options
|
35
|
-
{
|
36
|
-
sleep_window: 300,
|
37
|
-
volume_threshold: 5,
|
38
|
-
error_threshold: 33,
|
39
|
-
timeout_seconds: 1
|
40
|
-
}
|
41
|
-
end
|
42
|
-
|
21
|
+
class Ratio < Minitest::Test
|
43
22
|
def setup
|
44
23
|
Circuitbox::CircuitBreaker.reset
|
45
|
-
@circuit = Circuitbox::CircuitBreaker.new(:yammer,
|
24
|
+
@circuit = Circuitbox::CircuitBreaker.new(:yammer,
|
25
|
+
sleep_window: 300,
|
26
|
+
volume_threshold: 5,
|
27
|
+
error_threshold: 33,
|
28
|
+
timeout_seconds: 1)
|
46
29
|
end
|
47
30
|
|
48
|
-
|
49
|
-
it 'open the circuit on 100% failure' do
|
31
|
+
def test_open_circuit_on_100_percent_failure
|
50
32
|
run_counter = 0
|
51
33
|
10.times do
|
52
34
|
@circuit.run do
|
53
35
|
run_counter += 1
|
54
|
-
raise
|
36
|
+
raise Timeout::Error
|
55
37
|
end
|
56
38
|
end
|
57
39
|
assert_equal 6, run_counter, 'the circuit did not open after 6 failures (5 failures + 10%)'
|
58
40
|
end
|
59
41
|
|
60
|
-
|
42
|
+
def test_keep_circuit_closed_on_success
|
61
43
|
run_counter = 0
|
62
44
|
10.times do
|
63
45
|
@circuit.run do
|
@@ -68,12 +50,12 @@ class CircuitBreakerTest < Minitest::Test
|
|
68
50
|
assert_equal 10, run_counter, 'run block was not executed 10 times'
|
69
51
|
end
|
70
52
|
|
71
|
-
|
53
|
+
def test_open_circuit_on_low_success_rate_below_limit
|
72
54
|
run_counter = 0
|
73
55
|
5.times do
|
74
56
|
@circuit.run do
|
75
57
|
run_counter += 1
|
76
|
-
raise
|
58
|
+
raise Timeout::Error
|
77
59
|
end
|
78
60
|
end
|
79
61
|
|
@@ -84,13 +66,13 @@ class CircuitBreakerTest < Minitest::Test
|
|
84
66
|
5.times do
|
85
67
|
@circuit.run do
|
86
68
|
run_counter += 1
|
87
|
-
raise
|
69
|
+
raise Timeout::Error
|
88
70
|
end
|
89
71
|
end
|
90
72
|
assert_equal 5, run_counter, 'the circuit did not open after 5 failures (5 failures + 10%)'
|
91
73
|
end
|
92
74
|
|
93
|
-
|
75
|
+
def test_keep_circuit_closed_on_low_failure_rate_below_failure_limit
|
94
76
|
run_counter = 0
|
95
77
|
7.times do
|
96
78
|
@circuit.run do
|
@@ -103,14 +85,14 @@ class CircuitBreakerTest < Minitest::Test
|
|
103
85
|
3.times do
|
104
86
|
@circuit.run do
|
105
87
|
run_counter += 1
|
106
|
-
raise
|
88
|
+
raise Timeout::Error
|
107
89
|
end
|
108
90
|
end
|
109
91
|
assert_equal 10, run_counter, 'block was not executed 10 times'
|
110
92
|
assert @circuit.error_rate < 33, 'error_rate pass over 33%'
|
111
93
|
end
|
112
94
|
|
113
|
-
|
95
|
+
def test_open_circuit_on_high_failure_rate_exceeding_failure_limit
|
114
96
|
run_counter = 0
|
115
97
|
10.times do
|
116
98
|
@circuit.run do
|
@@ -123,79 +105,84 @@ class CircuitBreakerTest < Minitest::Test
|
|
123
105
|
10.times do
|
124
106
|
@circuit.run do
|
125
107
|
run_counter += 1
|
126
|
-
raise
|
108
|
+
raise Timeout::Error
|
127
109
|
end
|
128
110
|
end
|
129
111
|
# 5 failure on 15 run is 33%
|
130
112
|
assert_equal 15, run_counter, 'block was not executed 10 times'
|
131
113
|
assert @circuit.error_rate >= 33, 'error_rate pass over 33%'
|
132
114
|
end
|
133
|
-
|
134
115
|
end
|
135
116
|
|
136
|
-
|
137
|
-
|
117
|
+
class Exceptions < Minitest::Test
|
118
|
+
class SentinalError < StandardError; end
|
119
|
+
|
120
|
+
def setup
|
138
121
|
Circuitbox::CircuitBreaker.reset
|
139
|
-
@circuit = Circuitbox::CircuitBreaker.new(:yammer, exceptions: [
|
122
|
+
@circuit = Circuitbox::CircuitBreaker.new(:yammer, exceptions: [SentinalError])
|
140
123
|
end
|
141
124
|
|
142
|
-
|
125
|
+
def test_raises_when_circuit_is_open
|
143
126
|
@circuit.stubs(open_flag?: true)
|
144
|
-
|
127
|
+
assert_raises(Circuitbox::OpenCircuitError) { @circuit.run! {} }
|
145
128
|
end
|
146
129
|
|
147
|
-
|
148
|
-
|
149
|
-
err.original.must_be_instance_of SomeOtherError
|
130
|
+
def test_raises_on_service_failure
|
131
|
+
assert_raises(Circuitbox::ServiceFailureError) { @circuit.run! { raise SentinalError } }
|
150
132
|
end
|
151
|
-
end
|
152
133
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
time_window: 2,
|
158
|
-
volume_threshold: 5,
|
159
|
-
error_threshold: 5,
|
160
|
-
timeout_seconds: 1
|
161
|
-
}
|
134
|
+
def test_sets_original_error_on_service_failure
|
135
|
+
@circuit.run! { raise SentinalError }
|
136
|
+
rescue Circuitbox::ServiceFailureError => service_failure_error
|
137
|
+
assert_instance_of SentinalError, service_failure_error.original
|
162
138
|
end
|
139
|
+
end
|
163
140
|
|
141
|
+
class CloseAfterSleep < Minitest::Test
|
164
142
|
def setup
|
165
|
-
|
143
|
+
Circuitbox::CircuitBreaker.reset
|
144
|
+
@circuit = Circuitbox::CircuitBreaker.new(:yammer,
|
145
|
+
sleep_window: 1,
|
146
|
+
time_window: 2,
|
147
|
+
volume_threshold: 5,
|
148
|
+
error_threshold: 5,
|
149
|
+
timeout_seconds: 1)
|
166
150
|
end
|
167
151
|
|
168
|
-
|
169
|
-
|
170
|
-
(cb_options[:error_threshold] + 1).times { @circuit.run { raise RequestFailureError } }
|
152
|
+
def test_circuit_closes_after_sleep_time_window
|
153
|
+
open_circuit!
|
171
154
|
run_count = 0
|
172
155
|
@circuit.run { run_count += 1 }
|
173
|
-
assert_equal 0, run_count, 'circuit
|
156
|
+
assert_equal 0, run_count, 'circuit has not opened prior'
|
174
157
|
# it is + 2 on purpose, because + 1 is flaky here
|
175
|
-
sleep
|
158
|
+
sleep @circuit.option_value(:sleep_window) + 2
|
176
159
|
|
177
160
|
@circuit.run { run_count += 1 }
|
178
|
-
assert_equal 1, run_count, 'circuit
|
161
|
+
assert_equal 1, run_count, 'circuit did not close after sleep'
|
162
|
+
end
|
163
|
+
|
164
|
+
def open_circuit!
|
165
|
+
(@circuit.option_value(:error_threshold) + 1).times { @circuit.run { raise Timeout::Error } }
|
179
166
|
end
|
180
167
|
end
|
181
168
|
|
182
|
-
|
183
|
-
|
169
|
+
class HalfOpenState < Minitest::Test
|
170
|
+
def setup
|
184
171
|
Circuitbox::CircuitBreaker.reset
|
185
172
|
@circuit = Circuitbox::CircuitBreaker.new(:yammer)
|
186
173
|
end
|
187
174
|
|
188
|
-
|
175
|
+
def test_when_in_half_open_state_circuit_opens_on_failure
|
189
176
|
@circuit.stubs(half_open?: true)
|
190
177
|
@circuit.expects(:open!)
|
191
|
-
@circuit.run { raise
|
178
|
+
@circuit.run { raise Timeout::Error }
|
192
179
|
end
|
193
180
|
|
194
|
-
|
181
|
+
def test_when_in_half_open_state_circuit_closes_on_success
|
195
182
|
@circuit.send(:half_open!)
|
196
183
|
@circuit.run { 'success' }
|
197
|
-
|
198
|
-
|
184
|
+
refute @circuit.send(:half_open?)
|
185
|
+
refute @circuit.send(:open?)
|
199
186
|
end
|
200
187
|
end
|
201
188
|
|
@@ -213,8 +200,8 @@ class CircuitBreakerTest < Minitest::Test
|
|
213
200
|
|
214
201
|
def test_should_return_response_if_it_doesnt_timeout
|
215
202
|
circuit = Circuitbox::CircuitBreaker.new(:yammer)
|
216
|
-
response = emulate_circuit_run(circuit, :success,
|
217
|
-
assert_equal
|
203
|
+
response = emulate_circuit_run(circuit, :success, "success")
|
204
|
+
assert_equal "success", response
|
218
205
|
end
|
219
206
|
|
220
207
|
def test_timeout_seconds_run_options_overrides_circuit_options
|
@@ -230,36 +217,37 @@ class CircuitBreakerTest < Minitest::Test
|
|
230
217
|
end
|
231
218
|
|
232
219
|
def test_doesnt_catch_out_of_scope_exceptions
|
233
|
-
|
220
|
+
sentinal = Class.new(StandardError)
|
221
|
+
circuit = Circuitbox::CircuitBreaker.new(:yammer, :exceptions => [ConnectionError, Timeout::Error])
|
234
222
|
|
235
|
-
assert_raises
|
236
|
-
emulate_circuit_run(circuit, :failure,
|
223
|
+
assert_raises(sentinal) do
|
224
|
+
emulate_circuit_run(circuit, :failure, sentinal)
|
237
225
|
end
|
238
226
|
end
|
239
227
|
|
240
228
|
def test_records_response_failure
|
241
|
-
circuit = Circuitbox::CircuitBreaker.new(:yammer, :exceptions => [
|
229
|
+
circuit = Circuitbox::CircuitBreaker.new(:yammer, :exceptions => [Timeout::Error])
|
242
230
|
circuit.expects(:log_event).with(:failure)
|
243
|
-
emulate_circuit_run(circuit, :failure,
|
231
|
+
emulate_circuit_run(circuit, :failure, Timeout::Error)
|
244
232
|
end
|
245
233
|
|
246
234
|
def test_records_response_success
|
247
235
|
circuit = Circuitbox::CircuitBreaker.new(:yammer)
|
248
236
|
circuit.expects(:log_event).with(:success)
|
249
|
-
emulate_circuit_run(circuit, :success,
|
237
|
+
emulate_circuit_run(circuit, :success, "success")
|
250
238
|
end
|
251
239
|
|
252
240
|
def test_does_not_send_request_if_circuit_is_open
|
253
241
|
circuit = Circuitbox::CircuitBreaker.new(:yammer)
|
254
242
|
circuit.stubs(:open? => true)
|
255
243
|
circuit.expects(:yield).never
|
256
|
-
response = emulate_circuit_run(circuit, :failure,
|
244
|
+
response = emulate_circuit_run(circuit, :failure, Timeout::Error)
|
257
245
|
assert_equal nil, response
|
258
246
|
end
|
259
247
|
|
260
248
|
def test_returns_nil_response_on_failed_request
|
261
249
|
circuit = Circuitbox::CircuitBreaker.new(:yammer)
|
262
|
-
response = emulate_circuit_run(circuit, :failure,
|
250
|
+
response = emulate_circuit_run(circuit, :failure, Timeout::Error)
|
263
251
|
assert_equal nil, response
|
264
252
|
end
|
265
253
|
|
@@ -268,11 +256,11 @@ class CircuitBreakerTest < Minitest::Test
|
|
268
256
|
circuit.stubs(:open? => true)
|
269
257
|
|
270
258
|
assert !circuit.send(:open_flag?)
|
271
|
-
emulate_circuit_run(circuit, :failure,
|
259
|
+
emulate_circuit_run(circuit, :failure, Timeout::Error)
|
272
260
|
assert circuit.send(:open_flag?)
|
273
261
|
|
274
262
|
circuit.expects(:open!).never
|
275
|
-
emulate_circuit_run(circuit, :failure,
|
263
|
+
emulate_circuit_run(circuit, :failure, Timeout::Error)
|
276
264
|
end
|
277
265
|
|
278
266
|
def test_open_is_true_if_open_flag
|
@@ -347,89 +335,80 @@ class CircuitBreakerTest < Minitest::Test
|
|
347
335
|
assert_equal 0, circuit.send(:success_count)
|
348
336
|
end
|
349
337
|
|
350
|
-
|
351
|
-
|
338
|
+
class Notifications < Minitest::Test
|
352
339
|
def setup
|
353
340
|
Circuitbox::CircuitBreaker.reset
|
354
341
|
end
|
355
342
|
|
356
|
-
def
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
it 'notifies on open circuit' do
|
362
|
-
@notifier = gimme_notifier
|
363
|
-
c = circuit
|
364
|
-
10.times { c.run { raise RequestFailureError }}
|
365
|
-
assert @notifier.notified?, 'no notification sent'
|
343
|
+
def test_notification_on_open
|
344
|
+
notifier = gimme_notifier
|
345
|
+
circuit = Circuitbox::CircuitBreaker.new(:yammer, notifier_class: notifier)
|
346
|
+
10.times { circuit.run { raise Timeout::Error }}
|
347
|
+
assert notifier.notified?, 'no notification sent'
|
366
348
|
end
|
367
349
|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
5.times {
|
372
|
-
clear_notified!
|
373
|
-
10.times {
|
374
|
-
assert
|
350
|
+
def test_notification_on_close
|
351
|
+
notifier = gimme_notifier
|
352
|
+
circuit = Circuitbox::CircuitBreaker.new(:yammer, notifier_class: notifier)
|
353
|
+
5.times { circuit.run { raise Timeout::Error }}
|
354
|
+
notifier.clear_notified!
|
355
|
+
10.times { circuit.run { 'success' }}
|
356
|
+
assert notifier.notified?, 'no notification sent'
|
375
357
|
end
|
376
358
|
|
377
|
-
|
378
|
-
|
359
|
+
def test_warning_when_sleep_window_is_shorter_than_time_window
|
360
|
+
notifier = gimme_notifier
|
379
361
|
Circuitbox::CircuitBreaker.new(:yammer,
|
380
|
-
:
|
381
|
-
:
|
382
|
-
:
|
383
|
-
|
384
|
-
assert @notifier.notified?, 'no notification sent'
|
362
|
+
notifier_class: notifier,
|
363
|
+
sleep_window: 1,
|
364
|
+
time_window: 10)
|
365
|
+
assert notifier.notified?, 'no notification sent'
|
385
366
|
end
|
386
367
|
|
387
|
-
|
388
|
-
|
368
|
+
def test_does_not_warn_on_sleep_window_being_correctly_sized
|
369
|
+
notifier = gimme_notifier
|
389
370
|
Circuitbox::CircuitBreaker.new(:yammer,
|
390
|
-
:
|
391
|
-
:
|
392
|
-
:
|
393
|
-
|
394
|
-
assert_equal false, @notifier.notified?, 'no notification sent'
|
371
|
+
notifier_class: notifier,
|
372
|
+
sleep_window: 11,
|
373
|
+
time_window: 10)
|
374
|
+
assert_equal false, notifier.notified?, 'no notification sent'
|
395
375
|
end
|
396
376
|
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
10.times { circuit.run {
|
401
|
-
assert
|
377
|
+
def test_notifies_on_success_rate_calculation
|
378
|
+
notifier = gimme_notifier(metric: :error_rate, metric_value: 0.0)
|
379
|
+
circuit = Circuitbox::CircuitBreaker.new(:yammer, notifier_class: notifier)
|
380
|
+
10.times { circuit.run { "success" } }
|
381
|
+
assert notifier.notified?, "no notification sent"
|
402
382
|
end
|
403
383
|
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
384
|
+
def test_notifies_on_error_rate_calculation
|
385
|
+
notifier = gimme_notifier(metric: :failure_count, metric_value: 1)
|
386
|
+
circuit = Circuitbox::CircuitBreaker.new(:yammer, notifier_class: notifier)
|
387
|
+
10.times { circuit.run { raise Timeout::Error }}
|
388
|
+
assert notifier.notified?, 'no notification sent'
|
408
389
|
end
|
409
390
|
|
410
|
-
|
411
|
-
|
391
|
+
def test_success_count_on_error_rate_calculation
|
392
|
+
notifier = gimme_notifier(metric: :success_count, metric_value: 6)
|
393
|
+
circuit = Circuitbox::CircuitBreaker.new(:yammer, notifier_class: notifier)
|
412
394
|
10.times { circuit.run { 'success' }}
|
413
|
-
assert
|
414
|
-
end
|
415
|
-
|
416
|
-
def clear_notified!
|
417
|
-
@notified = false
|
395
|
+
assert notifier.notified?, 'no notification sent'
|
418
396
|
end
|
419
397
|
|
420
398
|
def gimme_notifier(opts={})
|
421
|
-
|
422
|
-
metric = opts.fetch(:metric,:error_rate)
|
399
|
+
metric = opts.fetch(:metric,:error_rate)
|
423
400
|
metric_value = opts.fetch(:metric_value, 0.0)
|
424
|
-
warning_msg
|
401
|
+
warning_msg = opts.fetch(:warning_msg, '')
|
425
402
|
fake_notifier = gimme
|
426
|
-
|
427
|
-
give(fake_notifier).notify(:
|
428
|
-
give(fake_notifier).
|
429
|
-
give(fake_notifier).
|
403
|
+
notified = false
|
404
|
+
give(fake_notifier).notify(:open) { notified = true }
|
405
|
+
give(fake_notifier).notify(:close) { notified = true }
|
406
|
+
give(fake_notifier).notify_warning(Gimme::Matchers::Anything.new) { notified = true }
|
407
|
+
give(fake_notifier).metric_gauge(metric, metric_value) { notified = true }
|
430
408
|
fake_notifier_class = gimme
|
431
409
|
give(fake_notifier_class).new(:yammer,nil) { fake_notifier }
|
432
|
-
give(fake_notifier_class).notified? {
|
410
|
+
give(fake_notifier_class).notified? { notified }
|
411
|
+
give(fake_notifier_class).clear_notified! { notified = false }
|
433
412
|
fake_notifier_class
|
434
413
|
end
|
435
414
|
end
|
@@ -443,7 +422,7 @@ class CircuitBreakerTest < Minitest::Test
|
|
443
422
|
response_value
|
444
423
|
end
|
445
424
|
end
|
446
|
-
rescue
|
425
|
+
rescue Timeout::Error
|
447
426
|
nil
|
448
427
|
end
|
449
428
|
end
|
data/test/circuitbox_test.rb
CHANGED
@@ -1,54 +1,45 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
class
|
3
|
+
class CircuitboxTest < Minitest::Test
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
after { Circuitbox.reset }
|
8
|
-
|
9
|
-
describe "Circuitbox.circuit_store" do
|
10
|
-
it "is configurable" do
|
11
|
-
example_store = Circuitbox::ExampleStore.new
|
12
|
-
Circuitbox.circuit_store = example_store
|
13
|
-
assert_equal example_store, Circuitbox[:yammer].circuit_store
|
14
|
-
end
|
5
|
+
def setup
|
6
|
+
Circuitbox.reset
|
15
7
|
end
|
16
8
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
22
|
-
|
23
|
-
it "creates a CircuitBreaker instance" do
|
24
|
-
assert Circuitbox[:yammer].is_a? Circuitbox::CircuitBreaker
|
25
|
-
end
|
9
|
+
def test_circuit_store_is_configurable
|
10
|
+
store = Moneta.new(:Memory, expires: true)
|
11
|
+
Circuitbox.circuit_store = store
|
12
|
+
assert_equal store, Circuitbox[:yammer].circuit_store
|
26
13
|
end
|
27
14
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
15
|
+
def test_delegates_to_circuit
|
16
|
+
Circuitbox.expects(:circuit).with(:yammer, {})
|
17
|
+
Circuitbox[:yammer]
|
18
|
+
end
|
32
19
|
|
33
|
-
|
34
|
-
|
35
|
-
|
20
|
+
def test_creates_a_circuit_breaker
|
21
|
+
assert Circuitbox[:yammer].is_a? Circuitbox::CircuitBreaker
|
22
|
+
end
|
36
23
|
|
37
|
-
|
38
|
-
|
39
|
-
end
|
24
|
+
def test_returns_the_same_circuit_every_time
|
25
|
+
assert_equal Circuitbox.circuit(:yammer), Circuitbox.circuit(:yammer)
|
40
26
|
end
|
41
27
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
assert_equal "api.yammer.com", service
|
46
|
-
end
|
28
|
+
def test_sets_the_circuit_options_the_first_time_only
|
29
|
+
circuit_one = Circuitbox.circuit(:yammer, :sleep_window => 1337)
|
30
|
+
circuit_two = Circuitbox.circuit(:yammer, :sleep_window => 2000)
|
47
31
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
32
|
+
assert_equal 1337, circuit_one.option_value(:sleep_window)
|
33
|
+
assert_equal 1337, circuit_two.option_value(:sleep_window)
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_uses_parsed_uri_host_as_identifier_for_circuit
|
37
|
+
service = Circuitbox.parameter_to_service_name("http://api.yammer.com/api/v1/messages")
|
38
|
+
assert_equal "api.yammer.com", service
|
52
39
|
end
|
53
40
|
|
41
|
+
def test_uses_identifier_directly_for_circuit_if_it_is_not_an_uri
|
42
|
+
service = Circuitbox.parameter_to_service_name(:yammer)
|
43
|
+
assert_equal "yammer", service
|
44
|
+
end
|
54
45
|
end
|
data/test/notifier_test.rb
CHANGED
@@ -2,20 +2,20 @@ require 'test_helper'
|
|
2
2
|
require 'circuitbox/notifier'
|
3
3
|
require 'active_support/notifications'
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
|
6
|
+
class NotifierTest < Minitest::Test
|
7
|
+
def test_sends_notification_on_notify
|
7
8
|
ActiveSupport::Notifications.expects(:instrument).with("circuit_open", circuit: 'yammer:12')
|
8
9
|
Circuitbox::Notifier.new(:yammer, 12).notify(:open)
|
9
10
|
end
|
10
11
|
|
11
|
-
|
12
|
+
def test_sends_warning_notificaiton_notify_warning
|
12
13
|
ActiveSupport::Notifications.expects(:instrument).with("circuit_warning", { circuit: 'yammer:12', message: 'hello'})
|
13
14
|
Circuitbox::Notifier.new(:yammer, 12).notify_warning('hello')
|
14
15
|
end
|
15
16
|
|
16
|
-
|
17
|
+
def test_sends_metric_as_notification
|
17
18
|
ActiveSupport::Notifications.expects(:instrument).with("circuit_gauge", { circuit: 'yammer:12', gauge: 'ratio', value: 12})
|
18
19
|
Circuitbox::Notifier.new(:yammer, 12).metric_gauge(:ratio, 12)
|
19
|
-
|
20
20
|
end
|
21
21
|
end
|
@@ -1,30 +1,23 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
|
3
|
+
class ServiceFailureErrorTest < Minitest::Test
|
4
4
|
class SomeOtherError < StandardError; end;
|
5
5
|
|
6
6
|
attr_reader :error
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
@error = ex
|
13
|
-
end
|
8
|
+
def setup
|
9
|
+
raise SomeOtherError, "some other error"
|
10
|
+
rescue => ex
|
11
|
+
@error = ex
|
14
12
|
end
|
15
13
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
assert_equal "Circuitbox::ServiceFailureError wrapped: #{error}", ex.to_s
|
20
|
-
end
|
14
|
+
def test_includes_the_message_of_the_wrapped_exception
|
15
|
+
ex = Circuitbox::ServiceFailureError.new('test', error)
|
16
|
+
assert_equal "Circuitbox::ServiceFailureError wrapped: #{error}", ex.to_s
|
21
17
|
end
|
22
18
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
assert_equal error.backtrace, ex.backtrace
|
27
|
-
end
|
19
|
+
def test_keeps_the_original_backtrace
|
20
|
+
ex = Circuitbox::ServiceFailureError.new('test', error)
|
21
|
+
assert_equal error.backtrace, ex.backtrace
|
28
22
|
end
|
29
|
-
|
30
23
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: circuitbox
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Fahim Ferdous
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-07-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -290,7 +290,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
290
290
|
version: '0'
|
291
291
|
requirements: []
|
292
292
|
rubyforge_project:
|
293
|
-
rubygems_version: 2.4.
|
293
|
+
rubygems_version: 2.4.8
|
294
294
|
signing_key:
|
295
295
|
specification_version: 4
|
296
296
|
summary: A robust circuit breaker that manages failing external services.
|