circuitbox 1.1.0 → 2.0.0.pre4

Sign up to get free protection for your applications and to get access to all the features.
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