berater 0.5.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/berater/rspec.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  require 'berater'
2
2
  require 'berater/rspec/matchers'
3
3
  require 'berater/test_mode'
4
- require 'rspec'
4
+ require 'rspec/core'
5
5
 
6
6
  RSpec.configure do |config|
7
- config.include(BeraterMatchers)
7
+ config.include(Berater::Matchers)
8
8
 
9
9
  config.after do
10
10
  Berater.expunge rescue nil
@@ -1,23 +1,20 @@
1
- module BeraterMatchers
2
- class Overloaded
3
- def initialize(type)
4
- @type = type
5
- end
6
-
7
- def supports_block_expectations?
8
- true
9
- end
1
+ module Berater
2
+ module Matchers
3
+ class Overloaded
4
+ def supports_block_expectations?
5
+ true
6
+ end
10
7
 
11
- def matches?(obj)
12
- begin
8
+ def matches?(obj)
13
9
  case obj
14
10
  when Proc
15
- # eg. expect { ... }.to be_overrated
11
+ # eg. expect { ... }.to be_overloaded
16
12
  res = obj.call
17
13
 
18
14
  if res.is_a? Berater::Limiter
19
15
  # eg. expect { Berater.new(...) }.to be_overloaded
20
- res.overloaded?
16
+ @limiter = res
17
+ @limiter.utilization >= 1
21
18
  else
22
19
  # eg. expect { Berater(...) }.to be_overloaded
23
20
  # eg. expect { limiter.limit }.to be_overloaded
@@ -25,38 +22,40 @@ module BeraterMatchers
25
22
  end
26
23
  when Berater::Limiter
27
24
  # eg. expect(Berater.new(...)).to be_overloaded
28
- obj.overloaded?
25
+ @limiter = obj
26
+ @limiter.utilization >= 1
29
27
  end
30
- rescue @type
28
+ rescue Berater::Overloaded
31
29
  true
32
30
  end
33
- end
34
31
 
35
- # def description
36
- # it { expect { Berater.new(:inhibitor) }.not_to be_overrated }
32
+ def description
33
+ if @limiter
34
+ "be overloaded"
35
+ else
36
+ "raise #{Berater::Overloaded}"
37
+ end
38
+ end
37
39
 
38
- def failure_message
39
- "expected #{@type} to be raised"
40
- end
40
+ def failure_message
41
+ if @limiter
42
+ "expected to be overloaded"
43
+ else
44
+ "expected #{Berater::Overloaded} to be raised"
45
+ end
46
+ end
41
47
 
42
- def failure_message_when_negated
43
- "did not expect #{@type} to be raised"
48
+ def failure_message_when_negated
49
+ if @limiter
50
+ "expected not to be overloaded"
51
+ else
52
+ "did not expect #{Berater::Overloaded} to be raised"
53
+ end
54
+ end
44
55
  end
45
- end
46
-
47
- def be_overloaded
48
- Overloaded.new(Berater::Overloaded)
49
- end
50
56
 
51
- def be_overrated
52
- Overloaded.new(Berater::RateLimiter::Overrated)
53
- end
54
-
55
- def be_incapacitated
56
- Overloaded.new(Berater::ConcurrencyLimiter::Incapacitated)
57
- end
58
-
59
- def be_inhibited
60
- Overloaded.new(Berater::Inhibitor::Inhibited)
57
+ def be_overloaded
58
+ Overloaded.new
59
+ end
61
60
  end
62
61
  end
@@ -11,42 +11,26 @@ module Berater
11
11
  end
12
12
 
13
13
  @test_mode = mode
14
-
15
- # overload class methods
16
- unless Berater::Limiter.singleton_class.ancestors.include?(TestMode)
17
- Berater::Limiter.singleton_class.prepend(TestMode)
18
- end
19
14
  end
20
15
 
21
16
  module TestMode
22
- def new(*args, **opts)
23
- return super unless Berater.test_mode
24
-
25
- # chose a stub class with desired behavior
26
- stub_klass = case Berater.test_mode
17
+ def acquire_lock(*)
18
+ case Berater.test_mode
27
19
  when :pass
28
- Berater::Unlimiter
20
+ Lock.new(Float::INFINITY, 0)
29
21
  when :fail
30
- Berater::Inhibitor
31
- end
32
-
33
- # don't stub self
34
- return super if self < stub_klass
35
-
36
- # swap out limit and overloaded? methods with stub
37
- super.tap do |instance|
38
- stub = stub_klass.allocate
39
- stub.send(:initialize, *args, **opts)
40
-
41
- instance.define_singleton_method(:limit) do |**opts, &block|
42
- stub.limit(**opts, &block)
43
- end
44
-
45
- instance.define_singleton_method(:overloaded?) do
46
- stub.overloaded?
47
- end
22
+ raise Overloaded
23
+ else
24
+ super
48
25
  end
49
26
  end
50
27
  end
51
28
 
52
29
  end
30
+
31
+ # stub each Limiter subclass
32
+ ObjectSpace.each_object(Class).each do |klass|
33
+ next unless klass < Berater::Limiter
34
+
35
+ klass.prepend(Berater::TestMode)
36
+ end
@@ -5,12 +5,14 @@ module Berater
5
5
  super(key, Float::INFINITY, **opts)
6
6
  end
7
7
 
8
- def limit(**opts, &block)
9
- yield_lock(Lock.new(self, Float::INFINITY, 0), &block)
8
+ protected
9
+
10
+ def capacity=(*)
11
+ @capacity = Float::INFINITY
10
12
  end
11
13
 
12
- def overloaded?
13
- false
14
+ def acquire_lock(*)
15
+ Lock.new(Float::INFINITY, 0)
14
16
  end
15
17
 
16
18
  end
data/lib/berater/utils.rb CHANGED
@@ -3,12 +3,12 @@ module Berater
3
3
  extend self
4
4
 
5
5
  refine Object do
6
- def to_usec
7
- Berater::Utils.to_usec(self)
6
+ def to_msec
7
+ Berater::Utils.to_msec(self)
8
8
  end
9
9
  end
10
10
 
11
- def to_usec(val)
11
+ def to_msec(val)
12
12
  res = val
13
13
 
14
14
  if val.is_a? String
@@ -39,7 +39,7 @@ module Berater
39
39
  raise ArgumentError, "infinite values not allowed"
40
40
  end
41
41
 
42
- (res * 10**6).to_i
42
+ (res * 10**3).to_i
43
43
  end
44
44
 
45
45
  end
@@ -1,3 +1,3 @@
1
1
  module Berater
2
- VERSION = '0.5.0'
2
+ VERSION = '0.7.1'
3
3
  end
data/spec/berater_spec.rb CHANGED
@@ -25,7 +25,7 @@ describe Berater do
25
25
  end
26
26
 
27
27
  describe '.new' do
28
- context 'unlimited mode' do
28
+ context 'Unlimiter mode' do
29
29
  let(:limiter) { Berater.new(:key, Float::INFINITY) }
30
30
 
31
31
  it 'instantiates an Unlimiter' do
@@ -44,7 +44,7 @@ describe Berater do
44
44
  end
45
45
  end
46
46
 
47
- context 'inhibited mode' do
47
+ context 'Inhibitor mode' do
48
48
  let(:limiter) { Berater.new(:key, 0) }
49
49
 
50
50
  it 'instantiates an Inhibitor' do
@@ -64,7 +64,7 @@ describe Berater do
64
64
  end
65
65
 
66
66
  context 'rate mode' do
67
- let(:limiter) { Berater.new(:key, 1, :second) }
67
+ let(:limiter) { Berater.new(:key, 1, interval: :second) }
68
68
 
69
69
  it 'instantiates a RateLimiter' do
70
70
  expect(limiter).to be_a Berater::RateLimiter
@@ -77,7 +77,7 @@ describe Berater do
77
77
 
78
78
  it 'accepts options' do
79
79
  redis = double('Redis')
80
- limiter = Berater.new(:key, 1, :second, redis: redis)
80
+ limiter = Berater.new(:key, 1, interval: :second, redis: redis)
81
81
  expect(limiter.redis).to be redis
82
82
  end
83
83
  end
@@ -103,20 +103,20 @@ describe Berater do
103
103
  end
104
104
 
105
105
  describe 'Berater() - convenience method' do
106
- RSpec.shared_examples 'test convenience' do |klass, *args|
106
+ RSpec.shared_examples 'test convenience' do |klass, capacity, **opts|
107
107
  it 'creates a limiter' do
108
- limiter = Berater(:key, *args)
108
+ limiter = Berater(:key, capacity, **opts)
109
109
  expect(limiter).to be_a klass
110
110
  end
111
111
 
112
112
  context 'with a block' do
113
113
  it 'creates a limiter and calls limit' do
114
- limiter = Berater(:key, *args)
114
+ limiter = Berater(:key, capacity, **opts)
115
115
  expect(klass).to receive(:new).and_return(limiter)
116
116
  expect(limiter).to receive(:limit).and_call_original
117
117
 
118
118
  begin
119
- res = Berater(:key, *args) { true }
119
+ res = Berater(:key, capacity, **opts) { true }
120
120
  expect(res).to be true
121
121
  rescue Berater::Overloaded
122
122
  expect(klass).to be Berater::Inhibitor
@@ -127,7 +127,7 @@ describe Berater do
127
127
 
128
128
  include_examples 'test convenience', Berater::Unlimiter, Float::INFINITY
129
129
  include_examples 'test convenience', Berater::Inhibitor, 0
130
- include_examples 'test convenience', Berater::RateLimiter, 1, :second
130
+ include_examples 'test convenience', Berater::RateLimiter, 1, interval: :second
131
131
  include_examples 'test convenience', Berater::ConcurrencyLimiter, 1
132
132
  end
133
133
 
@@ -18,11 +18,12 @@ describe Berater::ConcurrencyLimiter do
18
18
  describe '#capacity' do
19
19
  def expect_capacity(capacity)
20
20
  limiter = described_class.new(:key, capacity)
21
- expect(limiter.capacity).to eq capacity
21
+ expect(limiter.capacity).to eq capacity.to_i
22
22
  end
23
23
 
24
24
  it { expect_capacity(0) }
25
25
  it { expect_capacity(1) }
26
+ it { expect_capacity(1.5) }
26
27
  it { expect_capacity(10_000) }
27
28
 
28
29
  context 'with erroneous values' do
@@ -32,7 +33,6 @@ describe Berater::ConcurrencyLimiter do
32
33
  end.to raise_error ArgumentError
33
34
  end
34
35
 
35
- it { expect_bad_capacity(0.5) }
36
36
  it { expect_bad_capacity(-1) }
37
37
  it { expect_bad_capacity('1') }
38
38
  it { expect_bad_capacity(:one) }
@@ -42,16 +42,16 @@ describe Berater::ConcurrencyLimiter do
42
42
  describe '#timeout' do
43
43
  # see spec/utils_spec.rb
44
44
 
45
- it 'saves the interval in original and microsecond format' do
45
+ it 'saves the interval in original and millisecond format' do
46
46
  limiter = described_class.new(:key, 1, timeout: 3)
47
47
  expect(limiter.timeout).to be 3
48
- expect(limiter.instance_variable_get(:@timeout_usec)).to be (3 * 10**6)
48
+ expect(limiter.instance_variable_get(:@timeout_msec)).to be (3 * 10**3)
49
49
  end
50
50
 
51
51
  it 'handles infinity' do
52
52
  limiter = described_class.new(:key, 1, timeout: Float::INFINITY)
53
53
  expect(limiter.timeout).to be Float::INFINITY
54
- expect(limiter.instance_variable_get(:@timeout_usec)).to be 0
54
+ expect(limiter.instance_variable_get(:@timeout_msec)).to be 0
55
55
  end
56
56
  end
57
57
 
@@ -77,77 +77,104 @@ describe Berater::ConcurrencyLimiter do
77
77
  expect(limiter.limit).to be_a Berater::Lock
78
78
  expect(limiter.limit).to be_a Berater::Lock
79
79
 
80
- expect(limiter).to be_incapacitated
80
+ expect(limiter).to be_overloaded
81
81
  end
82
82
 
83
83
  context 'with capacity 0' do
84
84
  let(:limiter) { described_class.new(:key, 0) }
85
85
 
86
86
  it 'always fails' do
87
- expect(limiter).to be_incapacitated
87
+ expect(limiter).to be_overloaded
88
+ end
89
+ end
90
+
91
+ context 'when capacity is a Float' do
92
+ let(:limiter) { described_class.new(:key, 1.5) }
93
+
94
+ it 'still works' do
95
+ lock = limiter.limit
96
+
97
+ # since fractional cost is not supported
98
+ expect(lock.capacity).to be 1
99
+ expect(limiter).to be_overloaded
88
100
  end
89
101
  end
90
102
 
91
103
  it 'limit resets over time' do
92
104
  2.times { limiter.limit }
93
- expect(limiter).to be_incapacitated
105
+ expect(limiter).to be_overloaded
94
106
 
95
107
  Timecop.freeze(30)
96
108
 
97
109
  2.times { limiter.limit }
98
- expect(limiter).to be_incapacitated
110
+ expect(limiter).to be_overloaded
99
111
  end
100
112
 
101
113
  it 'limit resets with millisecond precision' do
102
114
  2.times { limiter.limit }
103
- expect(limiter).to be_incapacitated
115
+ expect(limiter).to be_overloaded
104
116
 
105
117
  # travel forward to just before first lock times out
106
118
  Timecop.freeze(29.999)
107
- expect(limiter).to be_incapacitated
119
+ expect(limiter).to be_overloaded
108
120
 
109
121
  # traveling one more millisecond will decrement the count
110
122
  Timecop.freeze(0.001)
111
123
  2.times { limiter.limit }
112
- expect(limiter).to be_incapacitated
124
+ expect(limiter).to be_overloaded
125
+ end
126
+
127
+ it 'accepts a dynamic capacity' do
128
+ expect { limiter.limit(capacity: 0) }.to be_overloaded
129
+ 5.times { limiter.limit(capacity: 10) }
130
+ expect { limiter }.to be_overloaded
113
131
  end
114
132
 
115
133
  context 'with cost parameter' do
116
- it { expect { limiter.limit(cost: 4) }.to be_incapacitated }
134
+ it { expect { limiter.limit(cost: 4) }.to be_overloaded }
117
135
 
118
136
  it 'works within limit' do
119
137
  limiter.limit(cost: 2)
120
- expect(limiter).to be_incapacitated
138
+ expect(limiter).to be_overloaded
121
139
  end
122
140
 
123
141
  it 'releases full cost' do
124
142
  lock = limiter.limit(cost: 2)
125
- expect(limiter).to be_incapacitated
143
+ expect(limiter).to be_overloaded
126
144
 
127
145
  lock.release
128
- expect(limiter).not_to be_incapacitated
146
+ expect(limiter).not_to be_overloaded
129
147
 
130
148
  lock = limiter.limit(cost: 2)
131
- expect(limiter).to be_incapacitated
149
+ expect(limiter).to be_overloaded
132
150
  end
133
151
 
134
152
  it 'respects timeout' do
135
153
  limiter.limit(cost: 2)
136
- expect(limiter).to be_incapacitated
154
+ expect(limiter).to be_overloaded
137
155
 
138
156
  Timecop.freeze(30)
139
- expect(limiter).not_to be_incapacitated
157
+ expect(limiter).not_to be_overloaded
140
158
 
141
159
  limiter.limit(cost: 2)
142
- expect(limiter).to be_incapacitated
160
+ expect(limiter).to be_overloaded
143
161
  end
144
162
 
145
- it 'accepts a dynamic capacity' do
146
- limiter = described_class.new(:key, 1)
163
+ context 'with fractional costs' do
164
+ it 'rounds up' do
165
+ limiter.limit(cost: 1.5)
166
+ expect(limiter).to be_overloaded
167
+ end
168
+
169
+ it 'accumulates correctly' do
170
+ limiter.limit(cost: 0.5) # => 1
171
+ limiter.limit(cost: 0.7) # => 2
172
+ expect(limiter).to be_overloaded
173
+ end
174
+ end
147
175
 
148
- expect { limiter.limit(capacity: 0) }.to be_incapacitated
149
- 5.times { limiter.limit(capacity: 10) }
150
- expect { limiter }.to be_incapacitated
176
+ it 'only allows positive values' do
177
+ expect { limiter.limit(cost: -1) }.to raise_error(ArgumentError)
151
178
  end
152
179
  end
153
180
 
@@ -160,8 +187,8 @@ describe Berater::ConcurrencyLimiter do
160
187
  it 'works as expected' do
161
188
  expect(limiter_one.limit).to be_a Berater::Lock
162
189
 
163
- expect(limiter_one).to be_incapacitated
164
- expect(limiter_two).to be_incapacitated
190
+ expect(limiter_one).to be_overloaded
191
+ expect(limiter_two).to be_overloaded
165
192
  end
166
193
  end
167
194
 
@@ -175,22 +202,22 @@ describe Berater::ConcurrencyLimiter do
175
202
  one_lock = limiter_one.limit
176
203
  expect(one_lock).to be_a Berater::Lock
177
204
 
178
- expect(limiter_one).to be_incapacitated
179
- expect(limiter_two).not_to be_incapacitated
205
+ expect(limiter_one).to be_overloaded
206
+ expect(limiter_two).not_to be_overloaded
180
207
 
181
208
  two_lock = limiter_two.limit
182
209
  expect(two_lock).to be_a Berater::Lock
183
210
 
184
- expect(limiter_one).to be_incapacitated
185
- expect(limiter_two).to be_incapacitated
211
+ expect(limiter_one).to be_overloaded
212
+ expect(limiter_two).to be_overloaded
186
213
 
187
214
  one_lock.release
188
- expect(limiter_one).to be_incapacitated
189
- expect(limiter_two).not_to be_incapacitated
215
+ expect(limiter_one).to be_overloaded
216
+ expect(limiter_two).not_to be_overloaded
190
217
 
191
218
  two_lock.release
192
- expect(limiter_one).not_to be_incapacitated
193
- expect(limiter_two).not_to be_incapacitated
219
+ expect(limiter_one).not_to be_overloaded
220
+ expect(limiter_two).not_to be_overloaded
194
221
  end
195
222
  end
196
223
 
@@ -199,41 +226,41 @@ describe Berater::ConcurrencyLimiter do
199
226
  let(:limiter_two) { described_class.new(:two, 1) }
200
227
 
201
228
  it 'works as expected' do
202
- expect(limiter_one).not_to be_incapacitated
203
- expect(limiter_two).not_to be_incapacitated
229
+ expect(limiter_one).not_to be_overloaded
230
+ expect(limiter_two).not_to be_overloaded
204
231
 
205
232
  one_lock = limiter_one.limit
206
233
  expect(one_lock).to be_a Berater::Lock
207
234
 
208
- expect(limiter_one).to be_incapacitated
209
- expect(limiter_two).not_to be_incapacitated
235
+ expect(limiter_one).to be_overloaded
236
+ expect(limiter_two).not_to be_overloaded
210
237
 
211
238
  two_lock = limiter_two.limit
212
239
  expect(two_lock).to be_a Berater::Lock
213
240
 
214
- expect(limiter_one).to be_incapacitated
215
- expect(limiter_two).to be_incapacitated
241
+ expect(limiter_one).to be_overloaded
242
+ expect(limiter_two).to be_overloaded
216
243
  end
217
244
  end
218
245
  end
219
246
 
220
- describe '#overloaded?' do
221
- let(:limiter) { described_class.new(:key, 1, timeout: 30) }
247
+ describe '#utilization' do
248
+ let(:limiter) { described_class.new(:key, 10, timeout: 30) }
222
249
 
223
250
  it 'works' do
224
- expect(limiter.overloaded?).to be false
225
- lock = limiter.limit
226
- expect(limiter.overloaded?).to be true
227
- lock.release
228
- expect(limiter.overloaded?).to be false
229
- end
251
+ expect(limiter.utilization).to be 0.0
230
252
 
231
- it 'respects timeout' do
232
- expect(limiter.overloaded?).to be false
233
- lock = limiter.limit
234
- expect(limiter.overloaded?).to be true
235
- Timecop.freeze(30)
236
- expect(limiter.overloaded?).to be false
253
+ 2.times { limiter.limit }
254
+ expect(limiter.utilization).to be 0.2
255
+
256
+ Timecop.freeze(15)
257
+
258
+ 8.times { limiter.limit }
259
+ expect(limiter.utilization).to be 1.0
260
+
261
+ Timecop.freeze(15)
262
+
263
+ expect(limiter.utilization).to be 0.8
237
264
  end
238
265
  end
239
266