circuitbox 1.0.3 → 1.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 +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.
|