berater 0.12.0 → 0.13.0

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