circuitbox 1.1.0 → 2.0.0.pre4

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 (38) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +56 -187
  3. data/lib/circuitbox.rb +14 -57
  4. data/lib/circuitbox/circuit_breaker.rb +137 -161
  5. data/lib/circuitbox/circuit_breaker/logger_messages.rb +31 -0
  6. data/lib/circuitbox/configuration.rb +51 -0
  7. data/lib/circuitbox/errors/error.rb +3 -2
  8. data/lib/circuitbox/errors/open_circuit_error.rb +3 -1
  9. data/lib/circuitbox/errors/service_failure_error.rb +5 -1
  10. data/lib/circuitbox/excon_middleware.rb +23 -30
  11. data/lib/circuitbox/faraday_middleware.rb +43 -63
  12. data/lib/circuitbox/memory_store.rb +85 -0
  13. data/lib/circuitbox/memory_store/container.rb +30 -0
  14. data/lib/circuitbox/memory_store/monotonic_time.rb +13 -0
  15. data/lib/circuitbox/notifier/active_support.rb +19 -0
  16. data/lib/circuitbox/notifier/null.rb +13 -0
  17. data/lib/circuitbox/timer.rb +51 -0
  18. data/lib/circuitbox/version.rb +3 -1
  19. metadata +106 -118
  20. data/.gitignore +0 -20
  21. data/.ruby-version +0 -1
  22. data/.travis.yml +0 -9
  23. data/Gemfile +0 -6
  24. data/Rakefile +0 -18
  25. data/benchmark/circuit_store_benchmark.rb +0 -114
  26. data/circuitbox.gemspec +0 -48
  27. data/lib/circuitbox/memcache_store.rb +0 -31
  28. data/lib/circuitbox/notifier.rb +0 -34
  29. data/test/circuit_breaker_test.rb +0 -428
  30. data/test/circuitbox_test.rb +0 -45
  31. data/test/excon_middleware_test.rb +0 -131
  32. data/test/faraday_middleware_test.rb +0 -175
  33. data/test/integration/circuitbox_cross_process_open_test.rb +0 -56
  34. data/test/integration/faraday_middleware_test.rb +0 -78
  35. data/test/integration_helper.rb +0 -48
  36. data/test/notifier_test.rb +0 -21
  37. data/test/service_failure_error_test.rb +0 -23
  38. 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.2.2
@@ -1,9 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 1.9.3
4
- - 2.0.0
5
- - 2.1
6
- - 2.2
7
- - 2.2.2
8
- before_install:
9
- - 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,48 +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
- ruby_2_2_2_plus = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.2.2")
7
-
8
- Gem::Specification.new do |spec|
9
- spec.name = "circuitbox"
10
- spec.version = Circuitbox::VERSION
11
- spec.authors = ["Fahim Ferdous"]
12
- spec.email = ["fahimfmf@gmail.com"]
13
- spec.description = %q{A robust circuit breaker that manages failing external services.}
14
- spec.summary = %q{A robust circuit breaker that manages failing external services.}
15
- spec.homepage = ""
16
- spec.license = "MIT"
17
-
18
- spec.files = `git ls-files`.split($/)
19
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
- spec.require_paths = ["lib"]
22
-
23
- spec.add_development_dependency "bundler"
24
- spec.add_development_dependency "rake"
25
- if ruby_2_2_2_plus
26
- spec.add_development_dependency "rack"
27
- else
28
- spec.add_development_dependency "rack", '< 2'
29
- end
30
- spec.add_development_dependency "gimme"
31
- spec.add_development_dependency "minitest"
32
- spec.add_development_dependency "mocha"
33
- spec.add_development_dependency "typhoeus"
34
- spec.add_development_dependency "timecop"
35
- spec.add_development_dependency "faraday"
36
- spec.add_development_dependency "excon"
37
- spec.add_development_dependency "logger"
38
- spec.add_development_dependency "bundler-gem_version_tasks"
39
- spec.add_development_dependency "lmdb"
40
- spec.add_development_dependency "daybreak"
41
-
42
- if ruby_2_2_2_plus
43
- spec.add_dependency "activesupport"
44
- else
45
- spec.add_dependency "activesupport", '< 5'
46
- end
47
- spec.add_dependency "moneta"
48
- 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,428 +0,0 @@
1
- require 'test_helper'
2
-
3
- class CircuitBreakerTest < Minitest::Test
4
- class ConnectionError < StandardError; end;
5
-
6
- def setup
7
- Circuitbox::CircuitBreaker.reset
8
- end
9
-
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)
13
- end
14
-
15
- def test_goes_into_half_open_state_on_sleep
16
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
17
- circuit.send(:open!)
18
- assert circuit.send(:half_open?)
19
- end
20
-
21
- class Ratio < Minitest::Test
22
- def setup
23
- Circuitbox::CircuitBreaker.reset
24
- @circuit = Circuitbox::CircuitBreaker.new(:yammer,
25
- sleep_window: 300,
26
- volume_threshold: 5,
27
- error_threshold: 33,
28
- timeout_seconds: 1)
29
- end
30
-
31
- def test_open_circuit_on_100_percent_failure
32
- run_counter = 0
33
- 10.times do
34
- @circuit.run do
35
- run_counter += 1
36
- raise Timeout::Error
37
- end
38
- end
39
- assert_equal 6, run_counter, 'the circuit did not open after 6 failures (5 failures + 10%)'
40
- end
41
-
42
- def test_keep_circuit_closed_on_success
43
- run_counter = 0
44
- 10.times do
45
- @circuit.run do
46
- run_counter += 1
47
- 'sucess'
48
- end
49
- end
50
- assert_equal 10, run_counter, 'run block was not executed 10 times'
51
- end
52
-
53
- def test_open_circuit_on_low_success_rate_below_limit
54
- run_counter = 0
55
- 5.times do
56
- @circuit.run do
57
- run_counter += 1
58
- raise Timeout::Error
59
- end
60
- end
61
-
62
- # one success
63
- @circuit.run { 'success'}
64
- assert_equal 5, @circuit.failure_count, 'the total count of failures is not 5'
65
-
66
- 5.times do
67
- @circuit.run do
68
- run_counter += 1
69
- raise Timeout::Error
70
- end
71
- end
72
- assert_equal 5, run_counter, 'the circuit did not open after 5 failures (5 failures + 10%)'
73
- end
74
-
75
- def test_keep_circuit_closed_on_low_failure_rate_below_failure_limit
76
- run_counter = 0
77
- 7.times do
78
- @circuit.run do
79
- run_counter += 1
80
- 'sucess'
81
- end
82
- end
83
- assert_equal 0, @circuit.failure_count, 'some errors were counted'
84
-
85
- 3.times do
86
- @circuit.run do
87
- run_counter += 1
88
- raise Timeout::Error
89
- end
90
- end
91
- assert_equal 10, run_counter, 'block was not executed 10 times'
92
- assert @circuit.error_rate < 33, 'error_rate pass over 33%'
93
- end
94
-
95
- def test_open_circuit_on_high_failure_rate_exceeding_failure_limit
96
- run_counter = 0
97
- 10.times do
98
- @circuit.run do
99
- run_counter += 1
100
- 'sucess'
101
- end
102
- end
103
- assert_equal 0, @circuit.failure_count, 'some errors were counted'
104
-
105
- 10.times do
106
- @circuit.run do
107
- run_counter += 1
108
- raise Timeout::Error
109
- end
110
- end
111
- # 5 failure on 15 run is 33%
112
- assert_equal 15, run_counter, 'block was not executed 10 times'
113
- assert @circuit.error_rate >= 33, 'error_rate pass over 33%'
114
- end
115
- end
116
-
117
- class Exceptions < Minitest::Test
118
- class SentinalError < StandardError; end
119
-
120
- def setup
121
- Circuitbox::CircuitBreaker.reset
122
- @circuit = Circuitbox::CircuitBreaker.new(:yammer, exceptions: [SentinalError])
123
- end
124
-
125
- def test_raises_when_circuit_is_open
126
- @circuit.stubs(open_flag?: true)
127
- assert_raises(Circuitbox::OpenCircuitError) { @circuit.run! {} }
128
- end
129
-
130
- def test_raises_on_service_failure
131
- assert_raises(Circuitbox::ServiceFailureError) { @circuit.run! { raise SentinalError } }
132
- end
133
-
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
138
- end
139
- end
140
-
141
- class CloseAfterSleep < Minitest::Test
142
- def setup
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)
150
- end
151
-
152
- def test_circuit_closes_after_sleep_time_window
153
- open_circuit!
154
- run_count = 0
155
- @circuit.run { run_count += 1 }
156
- assert_equal 0, run_count, 'circuit has not opened prior'
157
- # it is + 2 on purpose, because + 1 is flaky here
158
- sleep @circuit.option_value(:sleep_window) + 2
159
-
160
- @circuit.run { run_count += 1 }
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 } }
166
- end
167
- end
168
-
169
- class HalfOpenState < Minitest::Test
170
- def setup
171
- Circuitbox::CircuitBreaker.reset
172
- @circuit = Circuitbox::CircuitBreaker.new(:yammer)
173
- end
174
-
175
- def test_when_in_half_open_state_circuit_opens_on_failure
176
- @circuit.stubs(half_open?: true)
177
- @circuit.expects(:open!)
178
- @circuit.run { raise Timeout::Error }
179
- end
180
-
181
- def test_when_in_half_open_state_circuit_closes_on_success
182
- @circuit.send(:half_open!)
183
- @circuit.run { 'success' }
184
- refute @circuit.send(:half_open?)
185
- refute @circuit.send(:open?)
186
- end
187
- end
188
-
189
- def test_should_use_timeout_class_if_exceptions_are_not_defined
190
- circuit = Circuitbox::CircuitBreaker.new(:yammer, timeout_seconds: 45)
191
- circuit.expects(:timeout).with(45).once
192
- emulate_circuit_run(circuit, :success, StandardError)
193
- end
194
-
195
- def test_should_not_use_timeout_class_if_custom_exceptions_are_defined
196
- circuit = Circuitbox::CircuitBreaker.new(:yammer, exceptions: [ConnectionError])
197
- circuit.expects(:timeout).never
198
- emulate_circuit_run(circuit, :success, StandardError)
199
- end
200
-
201
- def test_should_return_response_if_it_doesnt_timeout
202
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
203
- response = emulate_circuit_run(circuit, :success, "success")
204
- assert_equal "success", response
205
- end
206
-
207
- def test_timeout_seconds_run_options_overrides_circuit_options
208
- circuit = Circuitbox::CircuitBreaker.new(:yammer, timeout_seconds: 60)
209
- circuit.expects(:timeout).with(30).once
210
- circuit.run(timeout_seconds: 30) { true }
211
- end
212
-
213
- def test_catches_connection_error_failures_if_defined
214
- circuit = Circuitbox::CircuitBreaker.new(:yammer, :exceptions => [ConnectionError])
215
- response = emulate_circuit_run(circuit, :failure, ConnectionError)
216
- assert_equal nil, response
217
- end
218
-
219
- def test_doesnt_catch_out_of_scope_exceptions
220
- sentinal = Class.new(StandardError)
221
- circuit = Circuitbox::CircuitBreaker.new(:yammer, :exceptions => [ConnectionError, Timeout::Error])
222
-
223
- assert_raises(sentinal) do
224
- emulate_circuit_run(circuit, :failure, sentinal)
225
- end
226
- end
227
-
228
- def test_records_response_failure
229
- circuit = Circuitbox::CircuitBreaker.new(:yammer, :exceptions => [Timeout::Error])
230
- circuit.expects(:log_event).with(:failure)
231
- emulate_circuit_run(circuit, :failure, Timeout::Error)
232
- end
233
-
234
- def test_records_response_success
235
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
236
- circuit.expects(:log_event).with(:success)
237
- emulate_circuit_run(circuit, :success, "success")
238
- end
239
-
240
- def test_does_not_send_request_if_circuit_is_open
241
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
242
- circuit.stubs(:open? => true)
243
- circuit.expects(:yield).never
244
- response = emulate_circuit_run(circuit, :failure, Timeout::Error)
245
- assert_equal nil, response
246
- end
247
-
248
- def test_returns_nil_response_on_failed_request
249
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
250
- response = emulate_circuit_run(circuit, :failure, Timeout::Error)
251
- assert_equal nil, response
252
- end
253
-
254
- def test_puts_circuit_to_sleep_once_opened
255
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
256
- circuit.stubs(:open? => true)
257
-
258
- assert !circuit.send(:open_flag?)
259
- emulate_circuit_run(circuit, :failure, Timeout::Error)
260
- assert circuit.send(:open_flag?)
261
-
262
- circuit.expects(:open!).never
263
- emulate_circuit_run(circuit, :failure, Timeout::Error)
264
- end
265
-
266
- def test_open_is_true_if_open_flag
267
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
268
- circuit.stubs(:open_flag? => true)
269
- assert circuit.open?
270
- end
271
-
272
- def test_open_checks_if_volume_threshold_has_passed
273
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
274
- circuit.stubs(:open_flag? => false)
275
-
276
- circuit.expects(:passed_volume_threshold?).once
277
- circuit.open?
278
- end
279
-
280
- def test_open_checks_error_rate_threshold
281
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
282
- circuit.stubs(:open_flag? => false,
283
- :passed_volume_threshold? => true)
284
-
285
- circuit.expects(:passed_rate_threshold?).once
286
- circuit.open?
287
- end
288
-
289
- def test_open_is_false_if_awake_and_under_rate_threshold
290
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
291
- circuit.stubs(:open_flag? => false,
292
- :passed_volume_threshold? => false,
293
- :passed_rate_threshold => false)
294
-
295
- assert !circuit.open?
296
- end
297
-
298
- def test_error_rate_threshold_calculation
299
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
300
- circuit.stubs(:failure_count => 3, :success_count => 2)
301
- assert circuit.send(:passed_rate_threshold?)
302
-
303
- circuit.stubs(:failure_count => 2, :success_count => 3)
304
- assert !circuit.send(:passed_rate_threshold?)
305
- end
306
-
307
- def test_logs_and_retrieves_success_events
308
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
309
- 5.times { circuit.send(:log_event, :success) }
310
- assert_equal 5, circuit.send(:success_count)
311
- end
312
-
313
- def test_logs_and_retrieves_failure_events
314
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
315
- 5.times { circuit.send(:log_event, :failure) }
316
- assert_equal 5, circuit.send(:failure_count)
317
- end
318
-
319
- def test_logs_events_by_minute
320
- circuit = Circuitbox::CircuitBreaker.new(:yammer)
321
-
322
- Timecop.travel(Time.now.change(sec: 5))
323
- 4.times { circuit.send(:log_event, :success) }
324
- assert_equal 4, circuit.send(:success_count)
325
-
326
- Timecop.travel(1.minute.from_now)
327
- 7.times { circuit.send(:log_event, :success) }
328
- assert_equal 7, circuit.send(:success_count)
329
-
330
- Timecop.travel(30.seconds.from_now)
331
- circuit.send(:log_event, :success)
332
- assert_equal 8, circuit.send(:success_count)
333
-
334
- Timecop.travel(50.seconds.from_now)
335
- assert_equal 0, circuit.send(:success_count)
336
- end
337
-
338
- class Notifications < Minitest::Test
339
- def setup
340
- Circuitbox::CircuitBreaker.reset
341
- end
342
-
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'
348
- end
349
-
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'
357
- end
358
-
359
- def test_warning_when_sleep_window_is_shorter_than_time_window
360
- notifier = gimme_notifier
361
- Circuitbox::CircuitBreaker.new(:yammer,
362
- notifier_class: notifier,
363
- sleep_window: 1,
364
- time_window: 10)
365
- assert notifier.notified?, 'no notification sent'
366
- end
367
-
368
- def test_does_not_warn_on_sleep_window_being_correctly_sized
369
- notifier = gimme_notifier
370
- Circuitbox::CircuitBreaker.new(:yammer,
371
- notifier_class: notifier,
372
- sleep_window: 11,
373
- time_window: 10)
374
- assert_equal false, notifier.notified?, 'no notification sent'
375
- end
376
-
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"
382
- end
383
-
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'
389
- end
390
-
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)
394
- 10.times { circuit.run { 'success' }}
395
- assert notifier.notified?, 'no notification sent'
396
- end
397
-
398
- def gimme_notifier(opts={})
399
- metric = opts.fetch(:metric,:error_rate)
400
- metric_value = opts.fetch(:metric_value, 0.0)
401
- warning_msg = opts.fetch(:warning_msg, '')
402
- fake_notifier = gimme
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 }
408
- fake_notifier_class = gimme
409
- give(fake_notifier_class).new(:yammer,nil) { fake_notifier }
410
- give(fake_notifier_class).notified? { notified }
411
- give(fake_notifier_class).clear_notified! { notified = false }
412
- fake_notifier_class
413
- end
414
- end
415
-
416
- def emulate_circuit_run(circuit, response_type, response_value)
417
- circuit.run do
418
- case response_type
419
- when :failure
420
- raise response_value
421
- when :success
422
- response_value
423
- end
424
- end
425
- rescue Timeout::Error
426
- nil
427
- end
428
- end