berater 0.6.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -18,7 +18,7 @@ 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) }
@@ -36,22 +36,28 @@ describe Berater::ConcurrencyLimiter do
36
36
  it { expect_bad_capacity(-1) }
37
37
  it { expect_bad_capacity('1') }
38
38
  it { expect_bad_capacity(:one) }
39
+ it { expect_bad_capacity(Float::INFINITY) }
39
40
  end
40
41
  end
41
42
 
42
43
  describe '#timeout' do
43
44
  # see spec/utils_spec.rb
44
45
 
46
+ it 'defaults to nil' do
47
+ limiter = described_class.new(:key, 1)
48
+ expect(limiter.timeout).to be nil
49
+ end
50
+
45
51
  it 'saves the interval in original and millisecond format' do
46
52
  limiter = described_class.new(:key, 1, timeout: 3)
47
53
  expect(limiter.timeout).to be 3
48
- expect(limiter.instance_variable_get(:@timeout_msec)).to be (3 * 10**3)
54
+ expect(limiter.instance_variable_get(:@timeout)).to be (3 * 10**3)
49
55
  end
50
56
 
51
57
  it 'handles infinity' do
52
58
  limiter = described_class.new(:key, 1, timeout: Float::INFINITY)
53
59
  expect(limiter.timeout).to be Float::INFINITY
54
- expect(limiter.instance_variable_get(:@timeout_msec)).to be 0
60
+ expect(limiter.instance_variable_get(:@timeout)).to be 0
55
61
  end
56
62
  end
57
63
 
@@ -77,14 +83,14 @@ describe Berater::ConcurrencyLimiter do
77
83
  expect(limiter.limit).to be_a Berater::Lock
78
84
  expect(limiter.limit).to be_a Berater::Lock
79
85
 
80
- expect(limiter).to be_incapacitated
86
+ expect(limiter).to be_overloaded
81
87
  end
82
88
 
83
89
  context 'with capacity 0' do
84
90
  let(:limiter) { described_class.new(:key, 0) }
85
91
 
86
92
  it 'always fails' do
87
- expect(limiter).to be_incapacitated
93
+ expect(limiter).to be_overloaded
88
94
  end
89
95
  end
90
96
 
@@ -96,75 +102,85 @@ describe Berater::ConcurrencyLimiter do
96
102
 
97
103
  # since fractional cost is not supported
98
104
  expect(lock.capacity).to be 1
99
- expect(limiter).to be_incapacitated
105
+ expect(limiter).to be_overloaded
100
106
  end
101
107
  end
102
108
 
103
109
  it 'limit resets over time' do
104
110
  2.times { limiter.limit }
105
- expect(limiter).to be_incapacitated
111
+ expect(limiter).to be_overloaded
106
112
 
107
113
  Timecop.freeze(30)
108
114
 
109
115
  2.times { limiter.limit }
110
- expect(limiter).to be_incapacitated
116
+ expect(limiter).to be_overloaded
111
117
  end
112
118
 
113
119
  it 'limit resets with millisecond precision' do
114
120
  2.times { limiter.limit }
115
- expect(limiter).to be_incapacitated
121
+ expect(limiter).to be_overloaded
116
122
 
117
123
  # travel forward to just before first lock times out
118
124
  Timecop.freeze(29.999)
119
- expect(limiter).to be_incapacitated
125
+ expect(limiter).to be_overloaded
120
126
 
121
127
  # traveling one more millisecond will decrement the count
122
128
  Timecop.freeze(0.001)
123
129
  2.times { limiter.limit }
124
- expect(limiter).to be_incapacitated
130
+ expect(limiter).to be_overloaded
131
+ end
132
+
133
+ it 'accepts a dynamic capacity' do
134
+ expect { limiter.limit(capacity: 0) }.to be_overloaded
135
+ 5.times { limiter.limit(capacity: 10) }
136
+ expect { limiter }.to be_overloaded
125
137
  end
126
138
 
127
139
  context 'with cost parameter' do
128
- it { expect { limiter.limit(cost: 4) }.to be_incapacitated }
140
+ it { expect { limiter.limit(cost: 4) }.to be_overloaded }
129
141
 
130
142
  it 'works within limit' do
131
143
  limiter.limit(cost: 2)
132
- expect(limiter).to be_incapacitated
144
+ expect(limiter).to be_overloaded
133
145
  end
134
146
 
135
147
  it 'releases full cost' do
136
148
  lock = limiter.limit(cost: 2)
137
- expect(limiter).to be_incapacitated
149
+ expect(limiter).to be_overloaded
138
150
 
139
151
  lock.release
140
- expect(limiter).not_to be_incapacitated
152
+ expect(limiter).not_to be_overloaded
141
153
 
142
154
  lock = limiter.limit(cost: 2)
143
- expect(limiter).to be_incapacitated
155
+ expect(limiter).to be_overloaded
144
156
  end
145
157
 
146
158
  it 'respects timeout' do
147
159
  limiter.limit(cost: 2)
148
- expect(limiter).to be_incapacitated
160
+ expect(limiter).to be_overloaded
149
161
 
150
162
  Timecop.freeze(30)
151
- expect(limiter).not_to be_incapacitated
163
+ expect(limiter).not_to be_overloaded
152
164
 
153
165
  limiter.limit(cost: 2)
154
- expect(limiter).to be_incapacitated
166
+ expect(limiter).to be_overloaded
155
167
  end
156
168
 
157
- it 'accepts a dynamic capacity' do
158
- limiter = described_class.new(:key, 1)
159
-
160
- expect { limiter.limit(capacity: 0) }.to be_incapacitated
161
- 5.times { limiter.limit(capacity: 10) }
162
- expect { limiter }.to be_incapacitated
169
+ context 'with fractional costs' do
170
+ it 'rounds up' do
171
+ limiter.limit(cost: 1.5)
172
+ expect(limiter).to be_overloaded
173
+ end
174
+
175
+ it 'accumulates correctly' do
176
+ limiter.limit(cost: 0.5) # => 1
177
+ limiter.limit(cost: 0.7) # => 2
178
+ expect(limiter).to be_overloaded
179
+ end
163
180
  end
164
181
 
165
- it 'only allows positive, Integer values' do
182
+ it 'only allows positive values' do
166
183
  expect { limiter.limit(cost: -1) }.to raise_error(ArgumentError)
167
- expect { limiter.limit(cost: 1.5) }.to raise_error(ArgumentError)
168
184
  end
169
185
  end
170
186
 
@@ -177,8 +193,8 @@ describe Berater::ConcurrencyLimiter do
177
193
  it 'works as expected' do
178
194
  expect(limiter_one.limit).to be_a Berater::Lock
179
195
 
180
- expect(limiter_one).to be_incapacitated
181
- expect(limiter_two).to be_incapacitated
196
+ expect(limiter_one).to be_overloaded
197
+ expect(limiter_two).to be_overloaded
182
198
  end
183
199
  end
184
200
 
@@ -192,22 +208,22 @@ describe Berater::ConcurrencyLimiter do
192
208
  one_lock = limiter_one.limit
193
209
  expect(one_lock).to be_a Berater::Lock
194
210
 
195
- expect(limiter_one).to be_incapacitated
196
- expect(limiter_two).not_to be_incapacitated
211
+ expect(limiter_one).to be_overloaded
212
+ expect(limiter_two).not_to be_overloaded
197
213
 
198
214
  two_lock = limiter_two.limit
199
215
  expect(two_lock).to be_a Berater::Lock
200
216
 
201
- expect(limiter_one).to be_incapacitated
202
- expect(limiter_two).to be_incapacitated
217
+ expect(limiter_one).to be_overloaded
218
+ expect(limiter_two).to be_overloaded
203
219
 
204
220
  one_lock.release
205
- expect(limiter_one).to be_incapacitated
206
- expect(limiter_two).not_to be_incapacitated
221
+ expect(limiter_one).to be_overloaded
222
+ expect(limiter_two).not_to be_overloaded
207
223
 
208
224
  two_lock.release
209
- expect(limiter_one).not_to be_incapacitated
210
- expect(limiter_two).not_to be_incapacitated
225
+ expect(limiter_one).not_to be_overloaded
226
+ expect(limiter_two).not_to be_overloaded
211
227
  end
212
228
  end
213
229
 
@@ -216,41 +232,41 @@ describe Berater::ConcurrencyLimiter do
216
232
  let(:limiter_two) { described_class.new(:two, 1) }
217
233
 
218
234
  it 'works as expected' do
219
- expect(limiter_one).not_to be_incapacitated
220
- expect(limiter_two).not_to be_incapacitated
235
+ expect(limiter_one).not_to be_overloaded
236
+ expect(limiter_two).not_to be_overloaded
221
237
 
222
238
  one_lock = limiter_one.limit
223
239
  expect(one_lock).to be_a Berater::Lock
224
240
 
225
- expect(limiter_one).to be_incapacitated
226
- expect(limiter_two).not_to be_incapacitated
241
+ expect(limiter_one).to be_overloaded
242
+ expect(limiter_two).not_to be_overloaded
227
243
 
228
244
  two_lock = limiter_two.limit
229
245
  expect(two_lock).to be_a Berater::Lock
230
246
 
231
- expect(limiter_one).to be_incapacitated
232
- expect(limiter_two).to be_incapacitated
247
+ expect(limiter_one).to be_overloaded
248
+ expect(limiter_two).to be_overloaded
233
249
  end
234
250
  end
235
251
  end
236
252
 
237
- describe '#overloaded?' do
238
- let(:limiter) { described_class.new(:key, 1, timeout: 30) }
253
+ describe '#utilization' do
254
+ let(:limiter) { described_class.new(:key, 10, timeout: 30) }
239
255
 
240
256
  it 'works' do
241
- expect(limiter.overloaded?).to be false
242
- lock = limiter.limit
243
- expect(limiter.overloaded?).to be true
244
- lock.release
245
- expect(limiter.overloaded?).to be false
246
- end
257
+ expect(limiter.utilization).to be 0.0
247
258
 
248
- it 'respects timeout' do
249
- expect(limiter.overloaded?).to be false
250
- lock = limiter.limit
251
- expect(limiter.overloaded?).to be true
252
- Timecop.freeze(30)
253
- expect(limiter.overloaded?).to be false
259
+ 2.times { limiter.limit }
260
+ expect(limiter.utilization).to be 0.2
261
+
262
+ Timecop.freeze(15)
263
+
264
+ 8.times { limiter.limit }
265
+ expect(limiter.utilization).to be 1.0
266
+
267
+ Timecop.freeze(15)
268
+
269
+ expect(limiter.utilization).to be 0.8
254
270
  end
255
271
  end
256
272
 
@@ -3,18 +3,6 @@ require 'berater/dsl'
3
3
  describe Berater do
4
4
  using Berater::DSL
5
5
 
6
- it 'instatiates an Unlimiter' do
7
- limiter = Berater.new(:key) { unlimited }
8
- expect(limiter).to be_a Berater::Unlimiter
9
- expect(limiter.key).to be :key
10
- end
11
-
12
- it 'instatiates an Inhibiter' do
13
- limiter = Berater.new(:key) { inhibited }
14
- expect(limiter).to be_a Berater::Inhibitor
15
- expect(limiter.key).to be :key
16
- end
17
-
18
6
  it 'instatiates a RateLimiter' do
19
7
  limiter = Berater.new(:key) { 1.per second }
20
8
  expect(limiter).to be_a Berater::RateLimiter
data/spec/dsl_spec.rb CHANGED
@@ -13,13 +13,13 @@ describe Berater::DSL do
13
13
  end
14
14
 
15
15
  it 'parses' do
16
- check([ 1, :second ]) { 1.per second }
17
- check([ 3, :minute ]) { 3.per minute }
18
- check([ 5, :hour ]) { 5.every hour }
16
+ check([ 1, interval: :second ]) { 1.per second }
17
+ check([ 3, interval: :minute ]) { 3.per minute }
18
+ check([ 5, interval: :hour ]) { 5.every hour }
19
19
  end
20
20
 
21
21
  it 'cleans up afterward' do
22
- check([ 1, :second ]) { 1.per second }
22
+ check([ 1, interval: :second ]) { 1.per second }
23
23
 
24
24
  expect(Integer).not_to respond_to(:per)
25
25
  expect(Integer).not_to respond_to(:every)
@@ -29,7 +29,7 @@ describe Berater::DSL do
29
29
  count = 1
30
30
  interval = :second
31
31
 
32
- check([ count, interval ]) { count.per interval }
32
+ check([ count, interval: interval ]) { count.per interval }
33
33
  end
34
34
  end
35
35
 
@@ -57,16 +57,4 @@ describe Berater::DSL do
57
57
  end
58
58
  end
59
59
 
60
- context 'unlimited mode' do
61
- it 'has keywords' do
62
- check(:unlimited) { unlimited }
63
- end
64
- end
65
-
66
- context 'inhibited mode' do
67
- it 'has keywords' do
68
- check(:inhibited) { inhibited }
69
- end
70
- end
71
-
72
60
  end
@@ -1,7 +1,9 @@
1
1
  describe Berater::Inhibitor do
2
+ subject { described_class.new }
3
+
2
4
  describe '.new' do
3
5
  it 'initializes without any arguments or options' do
4
- expect(described_class.new).to be_a described_class
6
+ is_expected.to be_a described_class
5
7
  end
6
8
 
7
9
  it 'initializes with any arguments and options' do
@@ -9,15 +11,18 @@ describe Berater::Inhibitor do
9
11
  end
10
12
 
11
13
  it 'has default values' do
12
- expect(described_class.new.key).to be :inhibitor
13
- expect(described_class.new.redis).to be Berater.redis
14
+ expect(subject.key).to be :inhibitor
15
+ expect(subject.redis).to be Berater.redis
14
16
  end
15
17
  end
16
18
 
17
19
  describe '#limit' do
18
- subject { described_class.new }
19
-
20
20
  it_behaves_like 'it is overloaded'
21
21
  end
22
22
 
23
+ describe '#to_s' do
24
+ it do
25
+ expect(subject.to_s).to include described_class.to_s
26
+ end
27
+ end
23
28
  end
data/spec/limiter_spec.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  describe Berater::Limiter do
2
2
  it 'can not be initialized' do
3
- expect { described_class.new }.to raise_error(NotImplementedError)
3
+ expect { described_class.new }.to raise_error(NoMethodError)
4
4
  end
5
5
 
6
6
  describe 'abstract methods' do
@@ -8,11 +8,51 @@ describe Berater::Limiter do
8
8
 
9
9
  it do
10
10
  expect { limiter.limit }.to raise_error(NotImplementedError)
11
- expect { limiter.overloaded? }.to raise_error(NotImplementedError)
11
+ expect { limiter.utilization }.to raise_error(NotImplementedError)
12
12
  end
13
13
  end
14
14
 
15
- describe '==' do
15
+ describe '#limit' do
16
+ subject { Berater::Unlimiter.new }
17
+
18
+ context 'with a capacity parameter' do
19
+ it 'overrides the stored value' do
20
+ is_expected.to receive(:acquire_lock).with(3, anything)
21
+
22
+ subject.limit(capacity: 3)
23
+ end
24
+
25
+ it 'validates the type' do
26
+ expect {
27
+ subject.limit(capacity: 'abc')
28
+ }.to raise_error(ArgumentError)
29
+ end
30
+ end
31
+
32
+ context 'with a cost parameter' do
33
+ it 'overrides the stored value' do
34
+ is_expected.to receive(:acquire_lock).with(anything, 2)
35
+
36
+ subject.limit(cost: 2)
37
+ end
38
+
39
+ it 'validates' do
40
+ expect {
41
+ subject.limit(cost: 'abc')
42
+ }.to raise_error(ArgumentError)
43
+
44
+ expect {
45
+ subject.limit(cost: -1)
46
+ }.to raise_error(ArgumentError)
47
+
48
+ expect {
49
+ subject.limit(cost: Float::INFINITY)
50
+ }.to raise_error(ArgumentError)
51
+ end
52
+ end
53
+ end
54
+
55
+ describe '#==' do
16
56
  let(:limiter) { Berater::RateLimiter.new(:key, 1, :second) }
17
57
 
18
58
  it 'equals itself' do
@@ -25,12 +65,6 @@ describe Berater::Limiter do
25
65
  )
26
66
  end
27
67
 
28
- it 'equals something with equvalent initialization parameters' do
29
- expect(limiter).to eq(
30
- Berater::RateLimiter.new(:key, 1, 1)
31
- )
32
- end
33
-
34
68
  it 'does not equal something different' do
35
69
  expect(limiter).not_to eq(
36
70
  Berater::RateLimiter.new(:key, 2, :second)
@@ -68,4 +102,56 @@ describe Berater::Limiter do
68
102
  end
69
103
  end
70
104
 
105
+ describe '#cache_key' do
106
+ subject { klass.new(:key).send(:cache_key) }
107
+
108
+ context 'with Unlimiter' do
109
+ let(:klass) { Berater::Unlimiter }
110
+
111
+ it do
112
+ is_expected.to eq 'Berater:Unlimiter:key'
113
+ end
114
+ end
115
+
116
+ context 'with custom limiter' do
117
+ MyLimiter = Class.new(Berater::Unlimiter)
118
+
119
+ let(:klass) { MyLimiter }
120
+
121
+ it 'adds Berater prefix' do
122
+ is_expected.to eq 'Berater:MyLimiter:key'
123
+ end
124
+ end
125
+ end
126
+
127
+ describe '.cache_key' do
128
+ subject { klass.send(:cache_key, :key) }
129
+
130
+ context 'with Unlimiter' do
131
+ let(:klass) { Berater::Unlimiter }
132
+
133
+ it do
134
+ is_expected.to eq 'Berater:Unlimiter:key'
135
+ end
136
+ end
137
+
138
+ context 'with custom limiter' do
139
+ MyLimiter = Class.new(Berater::Unlimiter)
140
+
141
+ let(:klass) { MyLimiter }
142
+
143
+ it 'adds Berater prefix' do
144
+ is_expected.to eq 'Berater:MyLimiter:key'
145
+ end
146
+ end
147
+ end
148
+
149
+ describe '.inherited' do
150
+ it 'creates convenience methods' do
151
+ expect(Berater.method(:Unlimiter)).to be_a Method
152
+ expect(Berater::Unlimiter()).to be_a Berater::Unlimiter
153
+ expect {|b| Berater::Unlimiter(&b) }.to yield_control
154
+ end
155
+ end
156
+
71
157
  end