berater 0.12.0 → 0.13.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 72cc5fcf9c68fff6dd3988ff85897b38d6fdcff21c0e6f94d3ec098ca8b35fc7
4
- data.tar.gz: 49144f0dee2d7197740d39b9da365f9d959c730b7ece1ae761948ec9263ada75
3
+ metadata.gz: defd1ba585ef0f8b5ff8f2f388604c344571fe4fe887f7ca44d03d57c7ce20f8
4
+ data.tar.gz: 729865914aa2f72c4b61a16cbcd29f12d86d034a04f9c15f3c48c53a7611c5cf
5
5
  SHA512:
6
- metadata.gz: 401a193c358a80098f1250053983d8f1a362cd14649156a036deedeb461fd70202743764d375b04a2b6b4fb08c795ec61d013e1be342626a2c8aad7dab2cb259
7
- data.tar.gz: 55498f8b1a6bd70a64ed9053b80bc9c95b0d60043f073c302d48bb79b51677d7a3c235c07f35cc51dacec067d0c2cc8dcf972e37fb9e9baae126f69fa8aacb4f
6
+ metadata.gz: 864cc51b0eaf4c1767a36580331a27dfb20a2e0eeaaff805d2d188078cf9500ccfbf0d08e331cd06efb238b0c58dca9f67cde27025d1e87ebec8ef4ae234c683
7
+ data.tar.gz: aa303e6a58f1d79b0471d9d31bb5aa8835bd7f253c26f7930c6253e7801e154b9c26ce977529e70a7de647d9967caa6753963608828e721f7d1cfe5523805f41
@@ -63,7 +63,7 @@ module Berater
63
63
  LUA
64
64
  )
65
65
 
66
- protected def acquire_lock(capacity, cost)
66
+ protected def acquire_lock(capacity:, cost:)
67
67
  # round fractional capacity and cost
68
68
  capacity = capacity.to_i
69
69
  cost = cost.ceil
@@ -1,5 +1,6 @@
1
1
  module Berater
2
2
  class Limiter
3
+ DEFAULT_COST = 1
3
4
 
4
5
  attr_reader :key, :capacity, :options
5
6
 
@@ -9,7 +10,7 @@ module Berater
9
10
 
10
11
  def limit(**opts, &block)
11
12
  opts[:capacity] ||= @capacity
12
- opts[:cost] ||= 1
13
+ opts[:cost] ||= DEFAULT_COST
13
14
 
14
15
  lock = Berater.middleware.call(self, **opts) do |limiter, **opts|
15
16
  limiter.inner_limit(**opts)
@@ -26,7 +27,7 @@ module Berater
26
27
  end
27
28
  end
28
29
 
29
- protected def inner_limit(capacity:, cost:)
30
+ protected def inner_limit(capacity:, cost:, **opts)
30
31
  if capacity.is_a?(String)
31
32
  # try casting
32
33
  begin
@@ -49,7 +50,7 @@ module Berater
49
50
  raise ArgumentError, "invalid cost: #{cost}"
50
51
  end
51
52
 
52
- acquire_lock(capacity, cost)
53
+ acquire_lock(capacity: capacity, cost: cost, **opts)
53
54
  rescue NoMethodError => e
54
55
  raise unless e.message.include?("undefined method `evalsha' for")
55
56
 
@@ -110,13 +111,12 @@ module Berater
110
111
  @capacity = capacity
111
112
  end
112
113
 
113
- def acquire_lock(capacity, cost)
114
+ def acquire_lock(capacity:, cost:)
114
115
  raise NotImplementedError
115
116
  end
116
117
 
117
- def cache_key(subkey = nil)
118
- instance_key = subkey.nil? ? key : "#{key}:#{subkey}"
119
- self.class.cache_key(instance_key)
118
+ def cache_key
119
+ self.class.cache_key(key)
120
120
  end
121
121
 
122
122
  class << self
@@ -10,7 +10,7 @@ module Berater
10
10
  duration = -Process.clock_gettime(Process::CLOCK_MONOTONIC)
11
11
  lock = yield
12
12
  rescue Exception => error
13
- # note exception and propagate
13
+ # capture exception for reporting, then propagate
14
14
  raise
15
15
  ensure
16
16
  duration += Process.clock_gettime(Process::CLOCK_MONOTONIC)
@@ -21,7 +21,7 @@ module Berater
21
21
  @client.timing(
22
22
  'berater.limiter.limit',
23
23
  duration,
24
- tags: tags.merge(overloaded: !lock),
24
+ tags: tags,
25
25
  )
26
26
 
27
27
  @client.gauge(
@@ -30,31 +30,36 @@ module Berater
30
30
  tags: tags,
31
31
  )
32
32
 
33
- if lock && lock.contention > 0 # not a failsafe lock
34
- @client.gauge(
35
- 'berater.lock.capacity',
36
- lock.capacity,
37
- tags: tags,
38
- )
39
- @client.gauge(
40
- 'berater.limiter.contention',
41
- lock.contention,
33
+ if lock
34
+ @client.increment(
35
+ 'berater.lock.acquired',
42
36
  tags: tags,
43
37
  )
38
+
39
+ if lock.contention >= 0 # not a failsafe lock
40
+ @client.gauge(
41
+ 'berater.lock.capacity',
42
+ lock.capacity,
43
+ tags: tags,
44
+ )
45
+ @client.gauge(
46
+ 'berater.lock.contention',
47
+ lock.contention,
48
+ tags: tags,
49
+ )
50
+ end
44
51
  end
45
52
 
46
53
  if error
47
54
  if error.is_a?(Berater::Overloaded)
48
- # overloaded, so contention >= capacity
49
- @client.gauge(
50
- 'berater.limiter.contention',
51
- limiter.capacity,
55
+ @client.increment(
56
+ 'berater.limiter.overloaded',
52
57
  tags: tags,
53
58
  )
54
59
  else
55
60
  @client.increment(
56
61
  'berater.limiter.error',
57
- tags: tags.merge(type: error.class.to_s)
62
+ tags: tags.merge(type: error.class.to_s.gsub('::', '_'))
58
63
  )
59
64
  end
60
65
  end
@@ -68,14 +73,19 @@ module Berater
68
73
  limiter: limiter.class.to_s.split(':')[-1],
69
74
  }
70
75
 
71
- # append custom tags
72
- if @tags.respond_to?(:call)
73
- tags.merge!(@tags.call(limiter, **opts) || {})
74
- else
75
- tags.merge!(@tags)
76
+ # append global custom tags
77
+ if @tags
78
+ if @tags.respond_to?(:call)
79
+ tags.merge!(@tags.call(limiter, **opts) || {})
80
+ else
81
+ tags.merge!(@tags)
82
+ end
76
83
  end
77
84
 
78
- tags.merge!(opts.fetch(:tags, {}))
85
+ # append call specific custom tags
86
+ tags.merge!(opts[:tags]) if opts[:tags]
87
+
88
+ tags
79
89
  end
80
90
  end
81
91
  end
@@ -0,0 +1,41 @@
1
+ require 'ddtrace'
2
+
3
+ module Berater
4
+ module Middleware
5
+ class Trace
6
+ def initialize(tracer: nil)
7
+ @tracer = tracer
8
+ end
9
+
10
+ def call(limiter, **)
11
+ tracer.trace('Berater.limit') do |span|
12
+ begin
13
+ lock = yield
14
+ rescue Exception => error
15
+ # capture exception for reporting, then propagate
16
+ raise
17
+ ensure
18
+ span.set_tag('capacity', limiter.capacity)
19
+ span.set_tag('contention', lock.contention) if lock
20
+ span.set_tag('key', limiter.key)
21
+ span.set_tag('limiter', limiter.class.to_s.split(':')[-1])
22
+
23
+ if error
24
+ if error.is_a?(Berater::Overloaded)
25
+ span.set_tag('overloaded', true)
26
+ else
27
+ span.set_tag('error', error.class.to_s.gsub('::', '_'))
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def tracer
37
+ @tracer || Datadog.tracer
38
+ end
39
+ end
40
+ end
41
+ end
@@ -3,5 +3,6 @@ module Berater
3
3
  autoload 'FailOpen', 'berater/middleware/fail_open'
4
4
  autoload 'LoadShedder', 'berater/middleware/load_shedder'
5
5
  autoload 'Statsd', 'berater/middleware/statsd'
6
+ autoload 'Trace', 'berater/middleware/trace'
6
7
  end
7
8
  end
@@ -20,7 +20,6 @@ module Berater
20
20
 
21
21
  LUA_SCRIPT = Berater::LuaScript(<<~LUA
22
22
  local key = KEYS[1]
23
- local ts_key = KEYS[2]
24
23
  local ts = tonumber(ARGV[1])
25
24
  local capacity = tonumber(ARGV[2])
26
25
  local interval_msec = tonumber(ARGV[3])
@@ -71,7 +70,7 @@ module Berater
71
70
  LUA
72
71
  )
73
72
 
74
- protected def acquire_lock(capacity, cost)
73
+ protected def acquire_lock(capacity:, cost:)
75
74
  # timestamp in milliseconds
76
75
  ts = (Time.now.to_f * 10**3).to_i
77
76
 
@@ -18,7 +18,7 @@ module Berater
18
18
  LUA
19
19
  )
20
20
 
21
- protected def acquire_lock(capacity, cost)
21
+ protected def acquire_lock(capacity:, cost:)
22
22
  if cost == 0
23
23
  # utilization check
24
24
  count = redis.get(cache_key) || "0"
@@ -21,7 +21,7 @@ module Berater
21
21
 
22
22
  class Limiter
23
23
  module TestMode
24
- def acquire_lock(*)
24
+ def acquire_lock(**)
25
25
  case Berater.test_mode
26
26
  when :pass
27
27
  Lock.new(Float::INFINITY, 0)
@@ -43,5 +43,7 @@ Berater.singleton_class.prepend Berater::TestMode
43
43
  ObjectSpace.each_object(Class).each do |klass|
44
44
  next unless klass < Berater::Limiter
45
45
 
46
+ next if klass == Berater::Unlimiter || klass == Berater::Inhibitor
47
+
46
48
  klass.prepend Berater::Limiter::TestMode
47
49
  end
@@ -1,3 +1,3 @@
1
1
  module Berater
2
- VERSION = "0.12.0"
2
+ VERSION = "0.13.0"
3
3
  end
data/lib/berater.rb CHANGED
@@ -52,8 +52,8 @@ module Berater
52
52
  end
53
53
 
54
54
  def expunge
55
- redis.scan_each(match: "#{self.name}*") do |key|
56
- redis.del key
55
+ redis.scan_each(match: "#{name}*") do |key|
56
+ redis.del(key)
57
57
  end
58
58
  end
59
59
 
data/spec/berater_spec.rb CHANGED
@@ -77,18 +77,21 @@ describe Berater do
77
77
  end
78
78
 
79
79
  context 'with a block' do
80
- before { Berater.test_mode = :pass }
81
-
82
80
  subject { Berater(:key, capacity, **opts) { 123 } }
83
81
 
84
82
  it 'creates a limiter and calls limit' do
85
83
  expect(klass).to receive(:new).and_return(limiter)
86
- expect(limiter).to receive(:limit).and_call_original
84
+ expect(limiter).to receive(:limit)
85
+
87
86
  subject
88
87
  end
89
88
 
90
89
  it 'yields' do
91
- is_expected.to be 123
90
+ if capacity > 0
91
+ is_expected.to be 123
92
+ else
93
+ expect { subject }.to be_overloaded
94
+ end
92
95
  end
93
96
  end
94
97
  end
data/spec/limiter_spec.rb CHANGED
@@ -47,7 +47,9 @@ describe Berater::Limiter do
47
47
 
48
48
  context 'with a capacity parameter' do
49
49
  it 'overrides the stored value' do
50
- is_expected.to receive(:acquire_lock).with(3, anything)
50
+ is_expected.to receive(:acquire_lock).with(hash_including(
51
+ capacity: 3,
52
+ ))
51
53
 
52
54
  subject.limit(capacity: 3)
53
55
  end
@@ -59,15 +61,29 @@ describe Berater::Limiter do
59
61
  end
60
62
 
61
63
  it 'handles stringified numerics gracefully' do
62
- is_expected.to receive(:acquire_lock).with(3.5, anything)
64
+ is_expected.to receive(:acquire_lock).with(hash_including(
65
+ capacity: 3.5,
66
+ ))
63
67
 
64
68
  subject.limit(capacity: '3.5')
65
69
  end
66
70
  end
67
71
 
72
+ it 'has a default cost' do
73
+ is_expected.to receive(:acquire_lock).with(hash_including(
74
+ cost: described_class::DEFAULT_COST,
75
+ ))
76
+
77
+ subject.limit
78
+ end
79
+
68
80
  context 'with a cost parameter' do
69
- it 'overrides the stored value' do
70
- is_expected.to receive(:acquire_lock).with(anything, 2)
81
+ it 'overrides the default value' do
82
+ expect(described_class::DEFAULT_COST).not_to eq 2
83
+
84
+ is_expected.to receive(:acquire_lock).with(hash_including(
85
+ cost: 2,
86
+ ))
71
87
 
72
88
  subject.limit(cost: 2)
73
89
  end
@@ -87,12 +103,23 @@ describe Berater::Limiter do
87
103
  end
88
104
 
89
105
  it 'handles stringified numerics gracefully' do
90
- is_expected.to receive(:acquire_lock).with(anything, 2.5)
106
+ is_expected.to receive(:acquire_lock).with(hash_including(
107
+ cost: 2.5,
108
+ ))
91
109
 
92
110
  subject.limit(cost: '2.5')
93
111
  end
94
112
  end
95
113
 
114
+ it 'passes through arbitrary parameters' do
115
+ is_expected.to receive(:acquire_lock).with(hash_including(
116
+ priority: 123,
117
+ zed: nil,
118
+ ))
119
+
120
+ subject.limit(priority: 123, zed: nil)
121
+ end
122
+
96
123
  context 'when Berater.redis is nil' do
97
124
  let!(:redis) { Berater.redis }
98
125
 
@@ -1,4 +1,6 @@
1
1
  describe Berater::Middleware::FailOpen do
2
+ it_behaves_like 'a limiter middleware'
3
+
2
4
  let(:limiter) { Berater::Unlimiter.new }
3
5
  let(:lock) { limiter.limit }
4
6
  let(:error) { Redis::TimeoutError }
@@ -1,4 +1,6 @@
1
1
  describe Berater::Middleware::LoadShedder do
2
+ it_behaves_like 'a limiter middleware'
3
+
2
4
  describe '#call' do
3
5
  subject { described_class.new }
4
6
 
@@ -9,13 +9,23 @@ describe Berater::Middleware::Statsd do
9
9
  allow(client).to receive(:timing)
10
10
  end
11
11
 
12
+ context do
13
+ before do
14
+ Berater.middleware.use described_class, client
15
+ end
16
+
17
+ it_behaves_like 'a limiter middleware'
18
+ end
19
+
12
20
  describe '#call' do
13
- subject { described_class.new(client, **client_opts).call(limiter, **opts, &block) }
21
+ subject do
22
+ described_class.new(client, **middleware_opts).call(limiter, **opts, &block)
23
+ end
14
24
 
15
- let(:client_opts) { {} }
25
+ let(:middleware_opts) { {} }
16
26
  let(:limiter) { double(Berater::Limiter, key: :key, capacity: 5) }
17
27
  let(:lock) { double(Berater::Lock, capacity: 4, contention: 2) }
18
- let(:opts) { { capacity: lock.capacity, cost: 1 } }
28
+ let(:opts) { { capacity: limiter.capacity, cost: 1 } }
19
29
  let(:block) { lambda { lock } }
20
30
 
21
31
  after { subject }
@@ -31,7 +41,6 @@ describe Berater::Middleware::Statsd do
31
41
  tags: {
32
42
  key: limiter.key,
33
43
  limiter: String,
34
- overloaded: false,
35
44
  },
36
45
  )
37
46
  end
@@ -44,39 +53,63 @@ describe Berater::Middleware::Statsd do
44
53
  )
45
54
  end
46
55
 
47
- context 'when custom tags are passed in' do
48
- let(:opts) { { tags: { abc: 123 } } }
56
+ it 'tracks lock acquisition' do
57
+ expect(client).to receive(:increment).with(
58
+ 'berater.lock.acquired',
59
+ Hash
60
+ )
61
+ end
62
+
63
+ it 'tracks lock capacity' do
64
+ expect(client).to receive(:gauge).with(
65
+ 'berater.lock.capacity',
66
+ lock.capacity,
67
+ Hash
68
+ )
69
+ end
70
+
71
+ it 'tracks lock contention' do
72
+ expect(client).to receive(:gauge).with(
73
+ 'berater.lock.contention',
74
+ lock.contention,
75
+ Hash
76
+ )
77
+ end
49
78
 
50
- it 'incorporates the tags' do
79
+ describe 'tags' do
80
+ def expect_tags_to(matcher)
51
81
  expect(client).to receive(:timing) do |*, tags:|
52
- expect(tags).to include(opts[:tags])
82
+ expect(tags).to matcher
53
83
  end
54
84
  end
55
- end
56
85
 
57
- context 'with global tags' do
58
- let(:client_opts) { { tags: { abc: 123 } }}
86
+ context 'with global tags' do
87
+ let(:middleware_opts) { { tags: { abc: 123 } }}
59
88
 
60
- it 'incorporates the tags' do
61
- expect(client).to receive(:timing) do |*, tags:|
62
- expect(tags).to include(client_opts[:tags])
89
+ it 'incorporates the tags' do
90
+ expect_tags_to include(middleware_opts[:tags])
63
91
  end
64
92
  end
65
- end
66
93
 
67
- context 'with global tag callback' do
68
- let(:client_opts) { { tags: callback }}
69
- let(:callback) { double(Proc) }
94
+ context 'with global tag callback' do
95
+ let(:middleware_opts) { { tags: callback }}
96
+ let(:callback) { double(Proc) }
97
+
98
+ it 'calls the callback' do
99
+ expect(callback).to receive(:call).with(limiter, **opts)
100
+ end
70
101
 
71
- it 'calls the callback' do
72
- expect(callback).to receive(:call).with(limiter, **opts)
102
+ it 'incorporates the tags' do
103
+ expect(callback).to receive(:call).and_return({ abc: 123 })
104
+ expect_tags_to include(abc: 123)
105
+ end
73
106
  end
74
107
 
75
- it 'incorporates the tags' do
76
- expect(callback).to receive(:call).and_return({ abc: 123 })
108
+ context 'when call specific custom tags are passed in' do
109
+ let(:opts) { { tags: { abc: 123 } } }
77
110
 
78
- expect(client).to receive(:timing) do |*, tags:|
79
- expect(tags).to include(abc: 123)
111
+ it 'incorporates the tags' do
112
+ expect_tags_to include(opts[:tags])
80
113
  end
81
114
  end
82
115
  end
@@ -101,7 +134,13 @@ describe Berater::Middleware::Statsd do
101
134
  )
102
135
 
103
136
  expect(client).to receive(:gauge).with(
104
- 'berater.limiter.contention',
137
+ 'berater.lock.capacity',
138
+ limiter.capacity,
139
+ Hash,
140
+ )
141
+
142
+ expect(client).to receive(:gauge).with(
143
+ 'berater.lock.contention',
105
144
  1,
106
145
  Hash,
107
146
  )
@@ -111,13 +150,13 @@ describe Berater::Middleware::Statsd do
111
150
 
112
151
  it 'tracks each call' do
113
152
  expect(client).to receive(:gauge).with(
114
- 'berater.limiter.contention',
153
+ 'berater.lock.contention',
115
154
  1,
116
155
  Hash,
117
156
  )
118
157
 
119
158
  expect(client).to receive(:gauge).with(
120
- 'berater.limiter.contention',
159
+ 'berater.lock.contention',
121
160
  2,
122
161
  Hash,
123
162
  )
@@ -135,7 +174,7 @@ describe Berater::Middleware::Statsd do
135
174
  it 'tracks limiter exceptions' do
136
175
  expect(client).to receive(:increment).with(
137
176
  'berater.limiter.error',
138
- tags: hash_including(type: error.to_s),
177
+ tags: hash_including(type: 'Redis_TimeoutError'),
139
178
  )
140
179
 
141
180
  expect { limiter.limit }.to raise_error(error)
@@ -149,6 +188,7 @@ describe Berater::Middleware::Statsd do
149
188
  it 'does not track the exception' do
150
189
  expect(client).not_to receive(:increment).with(
151
190
  'berater.limiter.error',
191
+ anything,
152
192
  )
153
193
 
154
194
  limiter.limit
@@ -156,11 +196,8 @@ describe Berater::Middleware::Statsd do
156
196
 
157
197
  it 'does not track lock-based stats' do
158
198
  expect(client).not_to receive(:gauge).with(
159
- 'berater.lock.capacity',
160
- )
161
-
162
- expect(client).not_to receive(:gauge).with(
163
- 'berater.limiter.contention',
199
+ /berater.lock/,
200
+ any_args,
164
201
  )
165
202
 
166
203
  limiter.limit
@@ -175,7 +212,7 @@ describe Berater::Middleware::Statsd do
175
212
  it 'tracks the exception' do
176
213
  expect(client).to receive(:increment).with(
177
214
  'berater.limiter.error',
178
- Hash,
215
+ anything,
179
216
  )
180
217
 
181
218
  limiter.limit
@@ -183,11 +220,8 @@ describe Berater::Middleware::Statsd do
183
220
 
184
221
  it 'does not track lock-based stats' do
185
222
  expect(client).not_to receive(:gauge).with(
186
- 'berater.lock.capacity',
187
- )
188
-
189
- expect(client).not_to receive(:gauge).with(
190
- 'berater.limiter.contention',
223
+ /berater.lock/,
224
+ any_args,
191
225
  )
192
226
 
193
227
  limiter.limit
@@ -202,24 +236,24 @@ describe Berater::Middleware::Statsd do
202
236
  expect { limiter.limit }.to be_overloaded
203
237
  end
204
238
 
205
- it 'tracks the limit call' do
206
- expect(client).to receive(:timing).with(
207
- 'berater.limiter.limit',
208
- Float,
209
- tags: hash_including(overloaded: true),
239
+ it 'tracks the overloaded count' do
240
+ expect(client).to receive(:increment).with(
241
+ 'berater.limiter.overloaded',
242
+ Hash
210
243
  )
211
244
  end
212
245
 
213
- it 'tracks contention' do
246
+ it 'does not track lock-based stats' do
214
247
  expect(client).not_to receive(:gauge).with(
215
- 'berater.limiter.contention',
216
- limiter.capacity,
248
+ /berater.lock/,
249
+ any_args,
217
250
  )
218
251
  end
219
252
 
220
253
  it 'does not track the exception' do
221
254
  expect(client).not_to receive(:increment).with(
222
255
  'berater.limiter.error',
256
+ anything,
223
257
  )
224
258
  end
225
259
  end
@@ -0,0 +1,58 @@
1
+ describe Berater::Middleware::Trace do
2
+ before { Datadog.tracer.enabled = false }
3
+
4
+ it_behaves_like 'a limiter middleware'
5
+
6
+ let(:limiter) { Berater::Unlimiter.new }
7
+ let(:span) { double(Datadog::Span, set_tag: nil) }
8
+ let(:tracer) { double(Datadog::Tracer) }
9
+
10
+ before do
11
+ allow(tracer).to receive(:trace) {|&b| b.call(span) }
12
+ end
13
+
14
+ context 'with a provided tracer' do
15
+ let(:instance) { described_class.new(tracer: tracer) }
16
+
17
+ it 'traces' do
18
+ expect(tracer).to receive(:trace).with(/Berater/)
19
+ expect(span).to receive(:set_tag).with(/capacity/, Numeric)
20
+
21
+ instance.call(limiter) {}
22
+ end
23
+
24
+ it 'yields' do
25
+ expect {|b| instance.call(limiter, &b) }.to yield_control
26
+ end
27
+
28
+ context 'when an exception is raised' do
29
+ it 'tags the span and raises' do
30
+ expect(span).to receive(:set_tag).with('error', 'IOError')
31
+
32
+ expect {
33
+ instance.call(limiter) { raise IOError }
34
+ }.to raise_error(IOError)
35
+ end
36
+ end
37
+
38
+ context 'when an Overloaded exception is raised' do
39
+ let(:limiter) { Berater::Inhibitor.new }
40
+
41
+ it 'tags the span as overloaded and raises' do
42
+ expect(span).to receive(:set_tag).with('overloaded', true)
43
+
44
+ expect {
45
+ instance.call(limiter) { raise Berater::Overloaded }
46
+ }.to be_overloaded
47
+ end
48
+ end
49
+ end
50
+
51
+ context 'with the default tracer' do
52
+ it 'uses Datadog.tracer' do
53
+ expect(Datadog).to receive(:tracer).and_return(tracer)
54
+
55
+ described_class.new.call(limiter) {}
56
+ end
57
+ end
58
+ end
@@ -9,8 +9,13 @@ describe Berater::TestMode do
9
9
  end
10
10
 
11
11
  it 'prepends Limiter subclasses' do
12
- expect(Berater::Unlimiter.ancestors).to include(Berater::Limiter::TestMode)
13
- expect(Berater::Inhibitor.ancestors).to include(Berater::Limiter::TestMode)
12
+ expect(Berater::ConcurrencyLimiter.ancestors).to include(Berater::Limiter::TestMode)
13
+ expect(Berater::RateLimiter.ancestors).to include(Berater::Limiter::TestMode)
14
+ end
15
+
16
+ it 'does not modify Unlimiter or Inhibitor' do
17
+ expect(Berater::Unlimiter.ancestors).not_to include(Berater::Limiter::TestMode)
18
+ expect(Berater::Inhibitor.ancestors).not_to include(Berater::Limiter::TestMode)
14
19
  end
15
20
 
16
21
  it 'preserves the original functionality via super' do
@@ -19,6 +24,8 @@ describe Berater::TestMode do
19
24
  end
20
25
 
21
26
  describe '.test_mode' do
27
+ let(:limiter) { Berater::ConcurrencyLimiter.new(:key, 1) }
28
+
22
29
  it 'can be turned on' do
23
30
  Berater.test_mode = :pass
24
31
  expect(Berater.test_mode).to be :pass
@@ -37,7 +44,6 @@ describe Berater::TestMode do
37
44
  end
38
45
 
39
46
  it 'works no matter when limiter was created' do
40
- limiter = Berater::Unlimiter.new
41
47
  expect(limiter).not_to be_overloaded
42
48
 
43
49
  Berater.test_mode = :fail
@@ -47,7 +53,7 @@ describe Berater::TestMode do
47
53
  it 'supports a generic expectation' do
48
54
  Berater.test_mode = :pass
49
55
  expect_any_instance_of(Berater::Limiter).to receive(:limit)
50
- Berater::Unlimiter.new.limit
56
+ limiter.limit
51
57
  end
52
58
  end
53
59
 
@@ -94,7 +100,17 @@ describe Berater::TestMode do
94
100
  it_behaves_like 'it is not overloaded'
95
101
  end
96
102
 
97
- it_behaves_like 'it supports test_mode'
103
+ context 'when test_mode = :pass' do
104
+ before { Berater.test_mode = :pass }
105
+
106
+ it_behaves_like 'it is not overloaded'
107
+ end
108
+
109
+ context 'when test_mode = :fail' do
110
+ before { Berater.test_mode = :fail }
111
+
112
+ it_behaves_like 'it is not overloaded'
113
+ end
98
114
  end
99
115
 
100
116
  describe 'Inhibitor' do
@@ -106,7 +122,17 @@ describe Berater::TestMode do
106
122
  it_behaves_like 'it is overloaded'
107
123
  end
108
124
 
109
- it_behaves_like 'it supports test_mode'
125
+ context 'when test_mode = :pass' do
126
+ before { Berater.test_mode = :pass }
127
+
128
+ it_behaves_like 'it is overloaded'
129
+ end
130
+
131
+ context 'when test_mode = :fail' do
132
+ before { Berater.test_mode = :fail }
133
+
134
+ it_behaves_like 'it is overloaded'
135
+ end
110
136
  end
111
137
 
112
138
  describe 'RateLimiter' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: berater
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Pepper
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-11-03 00:00:00.000000000 Z
11
+ date: 2022-03-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: meddleware
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: ddtrace
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: dogstatsd-ruby
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -168,6 +182,7 @@ files:
168
182
  - lib/berater/middleware/fail_open.rb
169
183
  - lib/berater/middleware/load_shedder.rb
170
184
  - lib/berater/middleware/statsd.rb
185
+ - lib/berater/middleware/trace.rb
171
186
  - lib/berater/rate_limiter.rb
172
187
  - lib/berater/rspec.rb
173
188
  - lib/berater/rspec/matchers.rb
@@ -188,6 +203,7 @@ files:
188
203
  - spec/middleware/fail_open_spec.rb
189
204
  - spec/middleware/load_shedder_spec.rb
190
205
  - spec/middleware/statsd_spec.rb
206
+ - spec/middleware/trace_spec.rb
191
207
  - spec/middleware_spec.rb
192
208
  - spec/rate_limiter_spec.rb
193
209
  - spec/riddle_spec.rb
@@ -222,6 +238,7 @@ test_files:
222
238
  - spec/rate_limiter_spec.rb
223
239
  - spec/middleware/load_shedder_spec.rb
224
240
  - spec/middleware/statsd_spec.rb
241
+ - spec/middleware/trace_spec.rb
225
242
  - spec/middleware/fail_open_spec.rb
226
243
  - spec/matchers_spec.rb
227
244
  - spec/dsl_refinement_spec.rb