berater 0.6.0 → 0.8.0

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