berater 0.3.0 → 0.6.2

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.
@@ -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