berater 0.3.0 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,3 @@
1
1
  module Berater
2
- VERSION = '0.3.0'
2
+ VERSION = '0.6.2'
3
3
  end
data/spec/berater_spec.rb CHANGED
@@ -7,12 +7,12 @@ describe Berater do
7
7
  it { is_expected.to respond_to :configure }
8
8
 
9
9
  describe '.configure' do
10
- it 'can be set via configure' do
10
+ it 'is used with a block' do
11
11
  Berater.configure do |c|
12
12
  c.redis = :redis
13
13
  end
14
14
 
15
- expect(Berater.redis).to eq :redis
15
+ expect(Berater.redis).to be :redis
16
16
  end
17
17
  end
18
18
 
@@ -26,7 +26,7 @@ describe Berater do
26
26
 
27
27
  describe '.new' do
28
28
  context 'unlimited mode' do
29
- let(:limiter) { Berater.new(:key, :unlimited) }
29
+ let(:limiter) { Berater.new(:key, Float::INFINITY) }
30
30
 
31
31
  it 'instantiates an Unlimiter' do
32
32
  expect(limiter).to be_a Berater::Unlimiter
@@ -39,18 +39,13 @@ describe Berater do
39
39
 
40
40
  it 'accepts options' do
41
41
  redis = double('Redis')
42
- limiter = Berater.new(:key, :unlimited, redis: redis)
42
+ limiter = Berater.new(:key, Float::INFINITY, redis: redis)
43
43
  expect(limiter.redis).to be redis
44
44
  end
45
-
46
- it 'works with convinience' do
47
- expect(Berater).to receive(:new).and_return(limiter)
48
- expect {|b| Berater(:key, :unlimited, &b) }.to yield_control
49
- end
50
45
  end
51
46
 
52
47
  context 'inhibited mode' do
53
- let(:limiter) { Berater.new(:key, :inhibited) }
48
+ let(:limiter) { Berater.new(:key, 0) }
54
49
 
55
50
  it 'instantiates an Inhibitor' do
56
51
  expect(limiter).to be_a Berater::Inhibitor
@@ -63,18 +58,13 @@ describe Berater do
63
58
 
64
59
  it 'accepts options' do
65
60
  redis = double('Redis')
66
- limiter = Berater.new(:key, :inhibited, redis: redis)
61
+ limiter = Berater.new(:key, 0, redis: redis)
67
62
  expect(limiter.redis).to be redis
68
63
  end
69
-
70
- it 'works with convinience' do
71
- expect(Berater).to receive(:new).and_return(limiter)
72
- expect { Berater(:key, :inhibited) }.to be_inhibited
73
- end
74
64
  end
75
65
 
76
66
  context 'rate mode' do
77
- let(:limiter) { Berater.new(:key, :rate, 1, :second) }
67
+ let(:limiter) { Berater.new(:key, 1, :second) }
78
68
 
79
69
  it 'instantiates a RateLimiter' do
80
70
  expect(limiter).to be_a Berater::RateLimiter
@@ -87,18 +77,13 @@ describe Berater do
87
77
 
88
78
  it 'accepts options' do
89
79
  redis = double('Redis')
90
- limiter = Berater.new(:key, :rate, 1, :second, redis: redis)
80
+ limiter = Berater.new(:key, 1, :second, redis: redis)
91
81
  expect(limiter.redis).to be redis
92
82
  end
93
-
94
- it 'works with convinience' do
95
- expect(Berater).to receive(:new).and_return(limiter)
96
- expect {|b| Berater(:key, :rate, 1, :second, &b) }.to yield_control
97
- end
98
83
  end
99
84
 
100
85
  context 'concurrency mode' do
101
- let(:limiter) { Berater.new(:key, :concurrency, 1) }
86
+ let(:limiter) { Berater.new(:key, 1) }
102
87
 
103
88
  it 'instantiates a ConcurrencyLimiter' do
104
89
  expect(limiter).to be_a Berater::ConcurrencyLimiter
@@ -111,61 +96,39 @@ describe Berater do
111
96
 
112
97
  it 'accepts options' do
113
98
  redis = double('Redis')
114
- limiter = Berater.new(:key, :concurrency, 1, redis: redis)
99
+ limiter = Berater.new(:key, 1, redis: redis)
115
100
  expect(limiter.redis).to be redis
116
101
  end
117
-
118
- it 'works with convinience' do
119
- expect(Berater).to receive(:new).and_return(limiter)
120
- expect {|b| Berater(:key, :concurrency, 1, &b) }.to yield_control
121
- end
122
102
  end
103
+ end
123
104
 
124
- context 'with DSL' do
125
- it 'instatiates an Unlimiter' do
126
- limiter = Berater.new(:key) { unlimited }
127
- expect(limiter).to be_a Berater::Unlimiter
128
- expect(limiter.key).to be :key
129
- end
130
-
131
- it 'instatiates an Inhibiter' do
132
- limiter = Berater.new(:key) { inhibited }
133
- expect(limiter).to be_a Berater::Inhibitor
134
- expect(limiter.key).to be :key
135
- end
136
-
137
- it 'instatiates a RateLimiter' do
138
- limiter = Berater.new(:key) { 1.per second }
139
- expect(limiter).to be_a Berater::RateLimiter
140
- expect(limiter.key).to be :key
141
- expect(limiter.count).to be 1
142
- expect(limiter.interval).to be :second
143
- end
144
-
145
- it 'instatiates a ConcurrencyLimiter' do
146
- limiter = Berater.new(:key, timeout: 2) { 1.at_once }
147
- expect(limiter).to be_a Berater::ConcurrencyLimiter
148
- expect(limiter.key).to be :key
149
- expect(limiter.capacity).to be 1
150
- expect(limiter.timeout).to be 2
105
+ describe 'Berater() - convenience method' do
106
+ RSpec.shared_examples 'test convenience' do |klass, *args|
107
+ it 'creates a limiter' do
108
+ limiter = Berater(:key, *args)
109
+ expect(limiter).to be_a klass
151
110
  end
152
111
 
153
- it 'does not accept mode/args and dsl block' do
154
- expect {
155
- Berater.new(:key, :rate) { 1.per second }
156
- }.to raise_error(ArgumentError)
112
+ context 'with a block' do
113
+ it 'creates a limiter and calls limit' do
114
+ limiter = Berater(:key, *args)
115
+ expect(klass).to receive(:new).and_return(limiter)
116
+ expect(limiter).to receive(:limit).and_call_original
157
117
 
158
- expect {
159
- Berater.new(:key, :concurrency, 2) { 3.at_once }
160
- }.to raise_error(ArgumentError)
161
- end
162
-
163
- it 'requires either mode or dsl block' do
164
- expect {
165
- Berater.new(:key)
166
- }.to raise_error(ArgumentError)
118
+ begin
119
+ res = Berater(:key, *args) { true }
120
+ expect(res).to be true
121
+ rescue Berater::Overloaded
122
+ expect(klass).to be Berater::Inhibitor
123
+ end
124
+ end
167
125
  end
168
126
  end
127
+
128
+ include_examples 'test convenience', Berater::Unlimiter, Float::INFINITY
129
+ include_examples 'test convenience', Berater::Inhibitor, 0
130
+ include_examples 'test convenience', Berater::RateLimiter, 1, :second
131
+ include_examples 'test convenience', Berater::ConcurrencyLimiter, 1
169
132
  end
170
133
 
171
134
  end
@@ -23,6 +23,7 @@ describe Berater::ConcurrencyLimiter do
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) }
@@ -40,31 +40,23 @@ describe Berater::ConcurrencyLimiter do
40
40
  end
41
41
 
42
42
  describe '#timeout' do
43
- def expect_timeout(timeout)
44
- limiter = described_class.new(:key, 1, timeout: timeout)
45
- expect(limiter.timeout).to eq timeout
46
- end
47
-
48
- it { expect_timeout(0) }
49
- it { expect_timeout(1) }
50
- it { expect_timeout(10_000) }
43
+ # see spec/utils_spec.rb
51
44
 
52
- context 'with erroneous values' do
53
- def expect_bad_timeout(timeout)
54
- expect do
55
- described_class.new(:key, 1, timeout: timeout)
56
- end.to raise_error ArgumentError
57
- end
45
+ it 'saves the interval in original and millisecond format' do
46
+ limiter = described_class.new(:key, 1, timeout: 3)
47
+ expect(limiter.timeout).to be 3
48
+ expect(limiter.instance_variable_get(:@timeout_msec)).to be (3 * 10**3)
49
+ end
58
50
 
59
- it { expect_bad_timeout(0.5) }
60
- it { expect_bad_timeout(-1) }
61
- it { expect_bad_timeout('1') }
62
- it { expect_bad_timeout(:one) }
51
+ it 'handles infinity' do
52
+ limiter = described_class.new(:key, 1, timeout: Float::INFINITY)
53
+ expect(limiter.timeout).to be Float::INFINITY
54
+ expect(limiter.instance_variable_get(:@timeout_msec)).to be 0
63
55
  end
64
56
  end
65
57
 
66
58
  describe '#limit' do
67
- let(:limiter) { described_class.new(:key, 2) }
59
+ let(:limiter) { described_class.new(:key, 2, timeout: 30) }
68
60
 
69
61
  it 'works' do
70
62
  expect {|b| limiter.limit(&b) }.to yield_control
@@ -95,70 +87,180 @@ describe Berater::ConcurrencyLimiter do
95
87
  expect(limiter).to be_incapacitated
96
88
  end
97
89
  end
98
- end
99
90
 
100
- context 'with same key, different limiters' do
101
- let(:limiter_one) { described_class.new(:key, 1) }
102
- let(:limiter_two) { described_class.new(:key, 1) }
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_incapacitated
100
+ end
101
+ end
102
+
103
+ it 'limit resets over time' do
104
+ 2.times { limiter.limit }
105
+ expect(limiter).to be_incapacitated
106
+
107
+ Timecop.freeze(30)
108
+
109
+ 2.times { limiter.limit }
110
+ expect(limiter).to be_incapacitated
111
+ end
103
112
 
104
- it { expect(limiter_one.key).to eq limiter_two.key }
113
+ it 'limit resets with millisecond precision' do
114
+ 2.times { limiter.limit }
115
+ expect(limiter).to be_incapacitated
105
116
 
106
- it 'works as expected' do
107
- expect(limiter_one.limit).to be_a Berater::Lock
117
+ # travel forward to just before first lock times out
118
+ Timecop.freeze(29.999)
119
+ expect(limiter).to be_incapacitated
108
120
 
109
- expect(limiter_one).to be_incapacitated
110
- expect(limiter_two).to be_incapacitated
121
+ # traveling one more millisecond will decrement the count
122
+ Timecop.freeze(0.001)
123
+ 2.times { limiter.limit }
124
+ expect(limiter).to be_incapacitated
111
125
  end
112
- end
113
126
 
114
- context 'with same key, different capacities' do
115
- let(:limiter_one) { described_class.new(:key, 1) }
116
- let(:limiter_two) { described_class.new(:key, 2) }
127
+ it 'accepts a dynamic capacity' do
128
+ expect { limiter.limit(capacity: 0) }.to be_incapacitated
129
+ 5.times { limiter.limit(capacity: 10) }
130
+ expect { limiter }.to be_incapacitated
131
+ end
132
+
133
+ context 'with cost parameter' do
134
+ it { expect { limiter.limit(cost: 4) }.to be_incapacitated }
135
+
136
+ it 'works within limit' do
137
+ limiter.limit(cost: 2)
138
+ expect(limiter).to be_incapacitated
139
+ end
140
+
141
+ it 'releases full cost' do
142
+ lock = limiter.limit(cost: 2)
143
+ expect(limiter).to be_incapacitated
144
+
145
+ lock.release
146
+ expect(limiter).not_to be_incapacitated
147
+
148
+ lock = limiter.limit(cost: 2)
149
+ expect(limiter).to be_incapacitated
150
+ end
117
151
 
118
- it { expect(limiter_one.capacity).not_to eq limiter_two.capacity }
152
+ it 'respects timeout' do
153
+ limiter.limit(cost: 2)
154
+ expect(limiter).to be_incapacitated
155
+
156
+ Timecop.freeze(30)
157
+ expect(limiter).not_to be_incapacitated
158
+
159
+ limiter.limit(cost: 2)
160
+ expect(limiter).to be_incapacitated
161
+ end
119
162
 
120
- it 'works as expected' do
121
- one_lock = limiter_one.limit
122
- expect(one_lock).to be_a Berater::Lock
163
+ context 'with fractional costs' do
164
+ it 'rounds up' do
165
+ limiter.limit(cost: 1.5)
166
+ expect(limiter).to be_incapacitated
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_incapacitated
173
+ end
174
+ end
123
175
 
124
- expect(limiter_one).to be_incapacitated
125
- expect(limiter_two).not_to be_incapacitated
176
+ it 'only allows positive values' do
177
+ expect { limiter.limit(cost: -1) }.to raise_error(ArgumentError)
178
+ end
179
+ end
126
180
 
127
- two_lock = limiter_two.limit
128
- expect(two_lock).to be_a Berater::Lock
181
+ context 'with same key, different limiters' do
182
+ let(:limiter_one) { described_class.new(:key, 1) }
183
+ let(:limiter_two) { described_class.new(:key, 1) }
129
184
 
130
- expect(limiter_one).to be_incapacitated
131
- expect(limiter_two).to be_incapacitated
185
+ it { expect(limiter_one.key).to eq limiter_two.key }
132
186
 
133
- one_lock.release
134
- expect(limiter_one).to be_incapacitated
135
- expect(limiter_two).not_to be_incapacitated
187
+ it 'works as expected' do
188
+ expect(limiter_one.limit).to be_a Berater::Lock
136
189
 
137
- two_lock.release
138
- expect(limiter_one).not_to be_incapacitated
139
- expect(limiter_two).not_to be_incapacitated
190
+ expect(limiter_one).to be_incapacitated
191
+ expect(limiter_two).to be_incapacitated
192
+ end
140
193
  end
141
- end
142
194
 
143
- context 'with different keys, different limiters' do
144
- let(:limiter_one) { described_class.new(:one, 1) }
145
- let(:limiter_two) { described_class.new(:two, 1) }
195
+ context 'with same key, different capacities' do
196
+ let(:limiter_one) { described_class.new(:key, 1) }
197
+ let(:limiter_two) { described_class.new(:key, 2) }
198
+
199
+ it { expect(limiter_one.capacity).not_to eq limiter_two.capacity }
200
+
201
+ it 'works as expected' do
202
+ one_lock = limiter_one.limit
203
+ expect(one_lock).to be_a Berater::Lock
146
204
 
147
- it 'works as expected' do
148
- expect(limiter_one).not_to be_incapacitated
149
- expect(limiter_two).not_to be_incapacitated
205
+ expect(limiter_one).to be_incapacitated
206
+ expect(limiter_two).not_to be_incapacitated
150
207
 
151
- one_lock = limiter_one.limit
152
- expect(one_lock).to be_a Berater::Lock
208
+ two_lock = limiter_two.limit
209
+ expect(two_lock).to be_a Berater::Lock
153
210
 
154
- expect(limiter_one).to be_incapacitated
155
- expect(limiter_two).not_to be_incapacitated
211
+ expect(limiter_one).to be_incapacitated
212
+ expect(limiter_two).to be_incapacitated
156
213
 
157
- two_lock = limiter_two.limit
158
- expect(two_lock).to be_a Berater::Lock
214
+ one_lock.release
215
+ expect(limiter_one).to be_incapacitated
216
+ expect(limiter_two).not_to be_incapacitated
217
+
218
+ two_lock.release
219
+ expect(limiter_one).not_to be_incapacitated
220
+ expect(limiter_two).not_to be_incapacitated
221
+ end
222
+ end
223
+
224
+ context 'with different keys, different limiters' do
225
+ let(:limiter_one) { described_class.new(:one, 1) }
226
+ let(:limiter_two) { described_class.new(:two, 1) }
227
+
228
+ it 'works as expected' do
229
+ expect(limiter_one).not_to be_incapacitated
230
+ expect(limiter_two).not_to be_incapacitated
231
+
232
+ one_lock = limiter_one.limit
233
+ expect(one_lock).to be_a Berater::Lock
234
+
235
+ expect(limiter_one).to be_incapacitated
236
+ expect(limiter_two).not_to be_incapacitated
237
+
238
+ two_lock = limiter_two.limit
239
+ expect(two_lock).to be_a Berater::Lock
240
+
241
+ expect(limiter_one).to be_incapacitated
242
+ expect(limiter_two).to be_incapacitated
243
+ end
244
+ end
245
+ end
246
+
247
+ describe '#overloaded?' do
248
+ let(:limiter) { described_class.new(:key, 1, timeout: 30) }
249
+
250
+ it 'works' do
251
+ expect(limiter.overloaded?).to be false
252
+ lock = limiter.limit
253
+ expect(limiter.overloaded?).to be true
254
+ lock.release
255
+ expect(limiter.overloaded?).to be false
256
+ end
159
257
 
160
- expect(limiter_one).to be_incapacitated
161
- expect(limiter_two).to be_incapacitated
258
+ it 'respects timeout' do
259
+ expect(limiter.overloaded?).to be false
260
+ lock = limiter.limit
261
+ expect(limiter.overloaded?).to be true
262
+ Timecop.freeze(30)
263
+ expect(limiter.overloaded?).to be false
162
264
  end
163
265
  end
164
266