circuitbox 1.0.3 → 2.0.0.pre3

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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +68 -122
  3. data/lib/circuitbox.rb +12 -56
  4. data/lib/circuitbox/circuit_breaker.rb +133 -154
  5. data/lib/circuitbox/circuit_breaker/logger_messages.rb +31 -0
  6. data/lib/circuitbox/configuration.rb +55 -0
  7. data/lib/circuitbox/errors/error.rb +1 -2
  8. data/lib/circuitbox/errors/open_circuit_error.rb +0 -1
  9. data/lib/circuitbox/errors/service_failure_error.rb +2 -1
  10. data/lib/circuitbox/excon_middleware.rb +15 -24
  11. data/lib/circuitbox/faraday_middleware.rb +38 -61
  12. data/lib/circuitbox/memory_store.rb +84 -0
  13. data/lib/circuitbox/memory_store/container.rb +28 -0
  14. data/lib/circuitbox/memory_store/monotonic_time.rb +11 -0
  15. data/lib/circuitbox/notifier/active_support.rb +19 -0
  16. data/lib/circuitbox/notifier/null.rb +11 -0
  17. data/lib/circuitbox/timer/monotonic.rb +17 -0
  18. data/lib/circuitbox/timer/null.rb +9 -0
  19. data/lib/circuitbox/timer/simple.rb +13 -0
  20. data/lib/circuitbox/version.rb +1 -1
  21. metadata +81 -149
  22. data/.gitignore +0 -20
  23. data/.ruby-version +0 -1
  24. data/.travis.yml +0 -8
  25. data/Gemfile +0 -6
  26. data/Rakefile +0 -18
  27. data/benchmark/circuit_store_benchmark.rb +0 -114
  28. data/circuitbox.gemspec +0 -38
  29. data/lib/circuitbox/memcache_store.rb +0 -31
  30. data/lib/circuitbox/notifier.rb +0 -34
  31. data/test/circuit_breaker_test.rb +0 -449
  32. data/test/circuitbox_test.rb +0 -54
  33. data/test/excon_middleware_test.rb +0 -131
  34. data/test/faraday_middleware_test.rb +0 -175
  35. data/test/integration/circuitbox_cross_process_open_test.rb +0 -56
  36. data/test/integration/faraday_middleware_test.rb +0 -78
  37. data/test/integration_helper.rb +0 -48
  38. data/test/notifier_test.rb +0 -21
  39. data/test/service_failure_error_test.rb +0 -30
  40. data/test/test_helper.rb +0 -15
data/.gitignore DELETED
@@ -1,20 +0,0 @@
1
- *.gem
2
- .vagrant
3
- *.rbc
4
- .bundle
5
- .config
6
- .yardoc
7
- Gemfile.lock
8
- InstalledFiles
9
- _yardoc
10
- coverage
11
- doc/
12
- lib/bundler/man
13
- pkg
14
- rdoc
15
- spec/reports
16
- test/tmp
17
- test/reports
18
- test/version_tmp
19
- tmp
20
- .idea
@@ -1 +0,0 @@
1
- 2.1.5
@@ -1,8 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 1.9.3
4
- - 2.0.0
5
- - 2.1
6
- - 2.2
7
- before_install:
8
- - gem install bundler
data/Gemfile DELETED
@@ -1,6 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in circuitbox.gemspec
4
- gemspec
5
-
6
- gem "pry"
data/Rakefile DELETED
@@ -1,18 +0,0 @@
1
- require 'rake/testtask'
2
- require "bundler/gem_version_tasks"
3
-
4
- Rake::TestTask.new do |t|
5
- t.libs << 'test'
6
- t.test_files = FileList['test/**/*_test.rb']
7
- end
8
-
9
- desc "run the circuitbox benchmark scripts"
10
- task :benchmark do
11
- benchmark_scripts = FileList.new("./benchmark/*_benchmark.rb")
12
- benchmark_scripts.each do |script|
13
- system "bundle exec ruby #{script}"
14
- end
15
- end
16
-
17
- desc "Run tests"
18
- task :default => :test
@@ -1,114 +0,0 @@
1
- require 'circuitbox'
2
- require 'benchmark'
3
- require 'pstore'
4
- require 'tempfile'
5
- require 'tmpdir'
6
- require 'lmdb'
7
- require 'pry'
8
-
9
-
10
- class Circuitbox
11
- class CircuitBreaker
12
- # silence the circuitbreaker logger
13
- DEV_NULL = (RUBY_PLATFORM =~ /mswin|mingw/ ? "NUL" : "/dev/null")
14
- def logger
15
- @_dev_null_logger ||= Logger.new DEV_NULL
16
- end
17
- end
18
- end
19
-
20
- def service
21
- # 10% success rate to make the circuitbreaker flip flop
22
- if rand(10) <= 0
23
- "success"
24
- else
25
- raise RuntimeError, "fail"
26
- end
27
- end
28
-
29
- def run_flip_flopping circuit
30
- circuit.run { service }
31
- circuit.try_close_next_time if circuit.open?
32
- end
33
-
34
- def without_gc
35
- GC.start
36
- GC.disable
37
- yield
38
- GC.enable
39
- end
40
-
41
- def benchmark_circuitbox_method_with_reporter method, reporter
42
- without_gc { send(method, reporter) }
43
- Circuitbox.reset
44
- end
45
-
46
- def circuit_with_cache cache
47
- Circuitbox.circuit :performance, CIRCUIT_OPTIONS.merge(cache: cache)
48
- end
49
-
50
- CIRCUIT_OPTIONS = {
51
- exceptions: [RuntimeError],
52
- sleep_window: 0,
53
- time_window: 1
54
- }
55
-
56
- RUNS = 10000
57
-
58
- def circuit_store_memory_one_process reporter
59
- circuit = circuit_with_cache Moneta.new(:Memory)
60
-
61
- reporter.report "memory:" do
62
- RUNS.times { run_flip_flopping circuit }
63
- end
64
-
65
- circuit.circuit_store.close
66
- end
67
-
68
- def circuit_store_pstore_one_process reporter
69
- Tempfile.create("test_circuit_store_pstore_one_process") do |dbfile|
70
- circuit = circuit_with_cache Moneta.new(:PStore, file: dbfile)
71
-
72
- reporter.report "pstore:" do
73
- RUNS.times { run_flip_flopping circuit }
74
- end
75
-
76
- circuit.circuit_store.close
77
- end
78
- end
79
-
80
- def circuit_store_lmdb_one_process reporter
81
- Dir.mktmpdir("test_circuit_store_lmdb_one_process") do |dbdir|
82
- circuit = circuit_with_cache Moneta.new(:LMDB, dir: dbdir, db: "circuitbox_lmdb")
83
-
84
- reporter.report "lmdb:" do
85
- RUNS.times { run_flip_flopping circuit }
86
- end
87
-
88
- circuit.circuit_store.close
89
- end
90
- end
91
-
92
- def circuit_store_daybreak_one_process reporter
93
- Tempfile.create("test_circuit_store_daybreak_one_process") do |dbfile|
94
- circuit = circuit_with_cache Moneta.new(:Daybreak, file: dbfile)
95
-
96
- reporter.report "daybreak:" do
97
- RUNS.times { run_flip_flopping circuit }
98
- end
99
-
100
- circuit.circuit_store.close
101
- end
102
- end
103
-
104
- Benchmark.bm(8) do |x|
105
- benchmark_circuitbox_method_with_reporter :circuit_store_memory_one_process, x
106
- benchmark_circuitbox_method_with_reporter :circuit_store_lmdb_one_process, x
107
- benchmark_circuitbox_method_with_reporter :circuit_store_pstore_one_process, x
108
- benchmark_circuitbox_method_with_reporter :circuit_store_daybreak_one_process, x
109
- end
110
-
111
-
112
-
113
-
114
-
@@ -1,38 +0,0 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'circuitbox/version'
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = "circuitbox"
8
- spec.version = Circuitbox::VERSION
9
- spec.authors = ["Fahim Ferdous"]
10
- spec.email = ["fahimfmf@gmail.com"]
11
- spec.description = %q{A robust circuit breaker that manages failing external services.}
12
- spec.summary = %q{A robust circuit breaker that manages failing external services.}
13
- spec.homepage = ""
14
- spec.license = "MIT"
15
-
16
- spec.files = `git ls-files`.split($/)
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
20
-
21
- spec.add_development_dependency "bundler"
22
- spec.add_development_dependency "rake"
23
- spec.add_development_dependency "rack"
24
- spec.add_development_dependency "gimme"
25
- spec.add_development_dependency "minitest"
26
- spec.add_development_dependency "mocha"
27
- spec.add_development_dependency "typhoeus"
28
- spec.add_development_dependency "timecop"
29
- spec.add_development_dependency "faraday"
30
- spec.add_development_dependency "excon"
31
- spec.add_development_dependency "logger"
32
- spec.add_development_dependency "bundler-gem_version_tasks"
33
- spec.add_development_dependency "lmdb"
34
- spec.add_development_dependency "daybreak"
35
-
36
- spec.add_dependency "activesupport"
37
- spec.add_dependency "moneta"
38
- end
@@ -1,31 +0,0 @@
1
- module ActiveSupport
2
- module Cache
3
- class MemcacheStore
4
- def initialize(cache)
5
- @cache = cache
6
- end
7
-
8
- def read(key, options = {})
9
- @cache.get(key, options)
10
- rescue Memcached::NotFound
11
- nil
12
- end
13
-
14
- def increment(key)
15
- @cache.incr(key)
16
- end
17
-
18
- def write(key, value, options = {})
19
- if expires_in = options.delete(:expires_in)
20
- options[:expiry] = expires_in.to_i
21
- end
22
-
23
- @cache.set(key, value, options)
24
- end
25
-
26
- def delete(key)
27
- @cache.delete(key)
28
- end
29
- end
30
- end
31
- end
@@ -1,34 +0,0 @@
1
- class Circuitbox
2
- class Notifier
3
- def initialize(service, partition=nil)
4
- @service = service
5
- @partition = partition
6
- end
7
-
8
- def notify(event)
9
- return unless notification_available?
10
- ActiveSupport::Notifications.instrument("circuit_#{event}", circuit: circuit_name)
11
- end
12
-
13
- def notify_warning(message)
14
- return unless notification_available?
15
- ActiveSupport::Notifications.instrument("circuit_warning", { circuit: circuit_name, message: message})
16
- end
17
-
18
- def metric_gauge(gauge, value)
19
- return unless notification_available?
20
- ActiveSupport::Notifications.instrument("circuit_gauge", { circuit: circuit_name, gauge: gauge.to_s, value: value })
21
- end
22
-
23
- private
24
- def circuit_name
25
- circuit_name = @service.to_s
26
- circuit_name += ":#{@partition}" if @partition
27
- circuit_name
28
- end
29
-
30
- def notification_available?
31
- defined? ActiveSupport::Notifications
32
- end
33
- end
34
- end
@@ -1,449 +0,0 @@
1
- require 'test_helper'
2
- require 'ostruct'
3
-
4
- class CircuitBreakerTest < Minitest::Test
5
- SUCCESSFUL_RESPONSE_STRING = "Success!"
6
- RequestFailureError = Timeout::Error
7
- class ConnectionError < StandardError; end;
8
- class SomeOtherError < StandardError; end;
9
-
10
- def setup
11
- Circuitbox::CircuitBreaker.reset
12
- end
13
-
14
- describe 'initialize' do
15
- it 'force sleep_window to equal time_window if it is too short' do
16
- circuit = Circuitbox::CircuitBreaker.new(:yammer,
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
24
- end
25
-
26
- def test_goes_into_half_open_state_on_sleep
27
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
28
- circuit.send(:open!)
29
- assert circuit.send(:half_open?)
30
- end
31
-
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
-
43
- def setup
44
- Circuitbox::CircuitBreaker.reset
45
- @circuit = Circuitbox::CircuitBreaker.new(:yammer, cb_options)
46
- end
47
-
48
-
49
- it 'open the circuit on 100% failure' do
50
- run_counter = 0
51
- 10.times do
52
- @circuit.run do
53
- run_counter += 1
54
- raise RequestFailureError
55
- end
56
- end
57
- assert_equal 6, run_counter, 'the circuit did not open after 6 failures (5 failures + 10%)'
58
- end
59
-
60
- it 'keep circuit closed on 0% failure' do
61
- run_counter = 0
62
- 10.times do
63
- @circuit.run do
64
- run_counter += 1
65
- 'sucess'
66
- end
67
- end
68
- assert_equal 10, run_counter, 'run block was not executed 10 times'
69
- end
70
-
71
- it 'open the circuit even after 1 success' do
72
- run_counter = 0
73
- 5.times do
74
- @circuit.run do
75
- run_counter += 1
76
- raise RequestFailureError
77
- end
78
- end
79
-
80
- # one success
81
- @circuit.run { 'success'}
82
- assert_equal 5, @circuit.failure_count, 'the total count of failures is not 5'
83
-
84
- 5.times do
85
- @circuit.run do
86
- run_counter += 1
87
- raise RequestFailureError
88
- end
89
- end
90
- assert_equal 5, run_counter, 'the circuit did not open after 5 failures (5 failures + 10%)'
91
- end
92
-
93
- it 'keep circuit closed when failure ratio do not exceed limit' do
94
- run_counter = 0
95
- 7.times do
96
- @circuit.run do
97
- run_counter += 1
98
- 'sucess'
99
- end
100
- end
101
- assert_equal 0, @circuit.failure_count, 'some errors were counted'
102
-
103
- 3.times do
104
- @circuit.run do
105
- run_counter += 1
106
- raise RequestFailureError
107
- end
108
- end
109
- assert_equal 10, run_counter, 'block was not executed 10 times'
110
- assert @circuit.error_rate < 33, 'error_rate pass over 33%'
111
- end
112
-
113
- it 'circuit open when failure ratio exceed limit' do
114
- run_counter = 0
115
- 10.times do
116
- @circuit.run do
117
- run_counter += 1
118
- 'sucess'
119
- end
120
- end
121
- assert_equal 0, @circuit.failure_count, 'some errors were counted'
122
-
123
- 10.times do
124
- @circuit.run do
125
- run_counter += 1
126
- raise RequestFailureError
127
- end
128
- end
129
- # 5 failure on 15 run is 33%
130
- assert_equal 15, run_counter, 'block was not executed 10 times'
131
- assert @circuit.error_rate >= 33, 'error_rate pass over 33%'
132
- end
133
-
134
- end
135
-
136
- describe 'exceptions' do
137
- before do
138
- Circuitbox::CircuitBreaker.reset
139
- @circuit = Circuitbox::CircuitBreaker.new(:yammer, exceptions: [SomeOtherError])
140
- end
141
-
142
- it 'raises exception when circuit is open' do
143
- @circuit.stubs(open_flag?: true)
144
- -> { @circuit.run! {} }.must_raise Circuitbox::OpenCircuitError
145
- end
146
-
147
- it 'raises exception when service fails' do
148
- err = -> { @circuit.run! { raise SomeOtherError } }.must_raise Circuitbox::ServiceFailureError
149
- err.original.must_be_instance_of SomeOtherError
150
- end
151
- end
152
-
153
- describe 'closing the circuit after sleep' do
154
- def cb_options
155
- {
156
- sleep_window: 1,
157
- time_window: 2,
158
- volume_threshold: 5,
159
- error_threshold: 5,
160
- timeout_seconds: 1
161
- }
162
- end
163
-
164
- def setup
165
- @circuit = Circuitbox::CircuitBreaker.new(:yammer, cb_options)
166
- end
167
-
168
- it 'close the circuit after sleeping time' do
169
- # lets open the circuit
170
- (cb_options[:error_threshold] + 1).times { @circuit.run { raise RequestFailureError } }
171
- run_count = 0
172
- @circuit.run { run_count += 1 }
173
- assert_equal 0, run_count, 'circuit is not open'
174
- # it is + 2 on purpose, because + 1 is flaky here
175
- sleep cb_options[:sleep_window] + 2
176
-
177
- @circuit.run { run_count += 1 }
178
- assert_equal 1, run_count, 'circuit is not closed'
179
- end
180
- end
181
-
182
- describe "when in half open state" do
183
- before do
184
- Circuitbox::CircuitBreaker.reset
185
- @circuit = Circuitbox::CircuitBreaker.new(:yammer)
186
- end
187
-
188
- it "opens circuit on next failed request" do
189
- @circuit.stubs(half_open?: true)
190
- @circuit.expects(:open!)
191
- @circuit.run { raise RequestFailureError }
192
- end
193
-
194
- it "closes circuit on successful request" do
195
- @circuit.send(:half_open!)
196
- @circuit.run { 'success' }
197
- assert !@circuit.send(:half_open?)
198
- assert !@circuit.send(:open?)
199
- end
200
- end
201
-
202
- def test_should_use_timeout_class_if_exceptions_are_not_defined
203
- circuit = Circuitbox::CircuitBreaker.new(:yammer, timeout_seconds: 45)
204
- circuit.expects(:timeout).with(45).once
205
- emulate_circuit_run(circuit, :success, StandardError)
206
- end
207
-
208
- def test_should_not_use_timeout_class_if_custom_exceptions_are_defined
209
- circuit = Circuitbox::CircuitBreaker.new(:yammer, exceptions: [ConnectionError])
210
- circuit.expects(:timeout).never
211
- emulate_circuit_run(circuit, :success, StandardError)
212
- end
213
-
214
- def test_should_return_response_if_it_doesnt_timeout
215
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
216
- response = emulate_circuit_run(circuit, :success, SUCCESSFUL_RESPONSE_STRING)
217
- assert_equal SUCCESSFUL_RESPONSE_STRING, response
218
- end
219
-
220
- def test_timeout_seconds_run_options_overrides_circuit_options
221
- circuit = Circuitbox::CircuitBreaker.new(:yammer, timeout_seconds: 60)
222
- circuit.expects(:timeout).with(30).once
223
- circuit.run(timeout_seconds: 30) { true }
224
- end
225
-
226
- def test_catches_connection_error_failures_if_defined
227
- circuit = Circuitbox::CircuitBreaker.new(:yammer, :exceptions => [ConnectionError])
228
- response = emulate_circuit_run(circuit, :failure, ConnectionError)
229
- assert_equal nil, response
230
- end
231
-
232
- def test_doesnt_catch_out_of_scope_exceptions
233
- circuit = Circuitbox::CircuitBreaker.new(:yammer, :exceptions => [ConnectionError, RequestFailureError])
234
-
235
- assert_raises SomeOtherError do
236
- emulate_circuit_run(circuit, :failure, SomeOtherError)
237
- end
238
- end
239
-
240
- def test_records_response_failure
241
- circuit = Circuitbox::CircuitBreaker.new(:yammer, :exceptions => [RequestFailureError])
242
- circuit.expects(:log_event).with(:failure)
243
- emulate_circuit_run(circuit, :failure, RequestFailureError)
244
- end
245
-
246
- def test_records_response_success
247
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
248
- circuit.expects(:log_event).with(:success)
249
- emulate_circuit_run(circuit, :success, SUCCESSFUL_RESPONSE_STRING)
250
- end
251
-
252
- def test_does_not_send_request_if_circuit_is_open
253
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
254
- circuit.stubs(:open? => true)
255
- circuit.expects(:yield).never
256
- response = emulate_circuit_run(circuit, :failure, RequestFailureError)
257
- assert_equal nil, response
258
- end
259
-
260
- def test_returns_nil_response_on_failed_request
261
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
262
- response = emulate_circuit_run(circuit, :failure, RequestFailureError)
263
- assert_equal nil, response
264
- end
265
-
266
- def test_puts_circuit_to_sleep_once_opened
267
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
268
- circuit.stubs(:open? => true)
269
-
270
- assert !circuit.send(:open_flag?)
271
- emulate_circuit_run(circuit, :failure, RequestFailureError)
272
- assert circuit.send(:open_flag?)
273
-
274
- circuit.expects(:open!).never
275
- emulate_circuit_run(circuit, :failure, RequestFailureError)
276
- end
277
-
278
- def test_open_is_true_if_open_flag
279
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
280
- circuit.stubs(:open_flag? => true)
281
- assert circuit.open?
282
- end
283
-
284
- def test_open_checks_if_volume_threshold_has_passed
285
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
286
- circuit.stubs(:open_flag? => false)
287
-
288
- circuit.expects(:passed_volume_threshold?).once
289
- circuit.open?
290
- end
291
-
292
- def test_open_checks_error_rate_threshold
293
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
294
- circuit.stubs(:open_flag? => false,
295
- :passed_volume_threshold? => true)
296
-
297
- circuit.expects(:passed_rate_threshold?).once
298
- circuit.open?
299
- end
300
-
301
- def test_open_is_false_if_awake_and_under_rate_threshold
302
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
303
- circuit.stubs(:open_flag? => false,
304
- :passed_volume_threshold? => false,
305
- :passed_rate_threshold => false)
306
-
307
- assert !circuit.open?
308
- end
309
-
310
- def test_error_rate_threshold_calculation
311
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
312
- circuit.stubs(:failure_count => 3, :success_count => 2)
313
- assert circuit.send(:passed_rate_threshold?)
314
-
315
- circuit.stubs(:failure_count => 2, :success_count => 3)
316
- assert !circuit.send(:passed_rate_threshold?)
317
- end
318
-
319
- def test_logs_and_retrieves_success_events
320
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
321
- 5.times { circuit.send(:log_event, :success) }
322
- assert_equal 5, circuit.send(:success_count)
323
- end
324
-
325
- def test_logs_and_retrieves_failure_events
326
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
327
- 5.times { circuit.send(:log_event, :failure) }
328
- assert_equal 5, circuit.send(:failure_count)
329
- end
330
-
331
- def test_logs_events_by_minute
332
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
333
-
334
- Timecop.travel(Time.now.change(sec: 5))
335
- 4.times { circuit.send(:log_event, :success) }
336
- assert_equal 4, circuit.send(:success_count)
337
-
338
- Timecop.travel(1.minute.from_now)
339
- 7.times { circuit.send(:log_event, :success) }
340
- assert_equal 7, circuit.send(:success_count)
341
-
342
- Timecop.travel(30.seconds.from_now)
343
- circuit.send(:log_event, :success)
344
- assert_equal 8, circuit.send(:success_count)
345
-
346
- Timecop.travel(50.seconds.from_now)
347
- assert_equal 0, circuit.send(:success_count)
348
- end
349
-
350
- describe 'notifications' do
351
-
352
- def setup
353
- Circuitbox::CircuitBreaker.reset
354
- end
355
-
356
- def circuit
357
- Circuitbox::CircuitBreaker.new(:yammer, :notifier_class => @notifier)
358
- end
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'
366
- end
367
-
368
- it 'notifies on close circuit' do
369
- @notifier = gimme_notifier
370
- c = circuit
371
- 5.times { c.run { raise RequestFailureError }}
372
- clear_notified!
373
- 10.times { c.run { 'success' }}
374
- assert @notifier.notified?, 'no notification sent'
375
- end
376
-
377
- it 'notifies warning if sleep_window is shorter than time_window' do
378
- @notifier = gimme_notifier
379
- Circuitbox::CircuitBreaker.new(:yammer,
380
- :notifier_class => @notifier,
381
- :sleep_window => 1,
382
- :time_window => 10
383
- )
384
- assert @notifier.notified?, 'no notification sent'
385
- end
386
-
387
- it 'DO NOT notifies warning if sleep_window is longer than time_window' do
388
- @notifier = gimme_notifier
389
- Circuitbox::CircuitBreaker.new(:yammer,
390
- :notifier_class => @notifier,
391
- :sleep_window => 11,
392
- :time_window => 10
393
- )
394
- assert_equal false, @notifier.notified?, 'no notification sent'
395
- end
396
-
397
-
398
- it 'notifies error_rate on error_rate calculation' do
399
- @notifier = gimme_notifier(metric: :error_rate, metric_value: 0.0)
400
- 10.times { circuit.run {'success' }}
401
- assert @notifier.notified?, 'no notification sent'
402
- end
403
-
404
- it 'notifies failure_count on error_rate calculation' do
405
- @notifier = gimme_notifier(metric: :failure_count, metric_value: 1)
406
- 10.times { circuit.run { raise RequestFailureError }}
407
- assert @notifier.notified?, 'no notification sent'
408
- end
409
-
410
- it 'notifies success_count on error_rate calculation' do
411
- @notifier = gimme_notifier(metric: :success_count, metric_value: 6)
412
- 10.times { circuit.run { 'success' }}
413
- assert @notifier.notified?, 'no notification sent'
414
- end
415
-
416
- def clear_notified!
417
- @notified = false
418
- end
419
-
420
- def gimme_notifier(opts={})
421
- clear_notified!
422
- metric = opts.fetch(:metric,:error_rate)
423
- metric_value = opts.fetch(:metric_value, 0.0)
424
- warning_msg = opts.fetch(:warning_msg, '')
425
- fake_notifier = gimme
426
- give(fake_notifier).notify(:open) { @notified=true }
427
- give(fake_notifier).notify(:close) { @notified=true }
428
- give(fake_notifier).notify_warning(Gimme::Matchers::Anything.new) { @notified = true }
429
- give(fake_notifier).metric_gauge(metric, metric_value) { @notified=true }
430
- fake_notifier_class = gimme
431
- give(fake_notifier_class).new(:yammer,nil) { fake_notifier }
432
- give(fake_notifier_class).notified? { @notified }
433
- fake_notifier_class
434
- end
435
- end
436
-
437
- def emulate_circuit_run(circuit, response_type, response_value)
438
- circuit.run do
439
- case response_type
440
- when :failure
441
- raise response_value
442
- when :success
443
- response_value
444
- end
445
- end
446
- rescue RequestFailureError
447
- nil
448
- end
449
- end