berater 0.2.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,7 @@
1
1
  describe Berater::ConcurrencyLimiter do
2
+ it_behaves_like 'a limiter', described_class.new(:key, 1)
3
+ it_behaves_like 'a limiter', described_class.new(:key, 1, timeout: 1)
4
+
2
5
  describe '.new' do
3
6
  let(:limiter) { described_class.new(:key, 1) }
4
7
 
@@ -20,6 +23,7 @@ describe Berater::ConcurrencyLimiter do
20
23
 
21
24
  it { expect_capacity(0) }
22
25
  it { expect_capacity(1) }
26
+ it { expect_capacity(1.5) }
23
27
  it { expect_capacity(10_000) }
24
28
 
25
29
  context 'with erroneous values' do
@@ -29,7 +33,6 @@ describe Berater::ConcurrencyLimiter do
29
33
  end.to raise_error ArgumentError
30
34
  end
31
35
 
32
- it { expect_bad_capacity(0.5) }
33
36
  it { expect_bad_capacity(-1) }
34
37
  it { expect_bad_capacity('1') }
35
38
  it { expect_bad_capacity(:one) }
@@ -37,31 +40,23 @@ describe Berater::ConcurrencyLimiter do
37
40
  end
38
41
 
39
42
  describe '#timeout' do
40
- def expect_timeout(timeout)
41
- limiter = described_class.new(:key, 1, timeout: timeout)
42
- expect(limiter.timeout).to eq timeout
43
- end
44
-
45
- it { expect_timeout(0) }
46
- it { expect_timeout(1) }
47
- it { expect_timeout(10_000) }
43
+ # see spec/utils_spec.rb
48
44
 
49
- context 'with erroneous values' do
50
- def expect_bad_timeout(timeout)
51
- expect do
52
- described_class.new(:key, 1, timeout: timeout)
53
- end.to raise_error ArgumentError
54
- 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
55
50
 
56
- it { expect_bad_timeout(0.5) }
57
- it { expect_bad_timeout(-1) }
58
- it { expect_bad_timeout('1') }
59
- 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
60
55
  end
61
56
  end
62
57
 
63
58
  describe '#limit' do
64
- let(:limiter) { described_class.new(:key, 2, timeout: 1) }
59
+ let(:limiter) { described_class.new(:key, 2, timeout: 30) }
65
60
 
66
61
  it 'works' do
67
62
  expect {|b| limiter.limit(&b) }.to yield_control
@@ -85,89 +80,200 @@ describe Berater::ConcurrencyLimiter do
85
80
  expect(limiter).to be_incapacitated
86
81
  end
87
82
 
88
- it 'times out locks' do
89
- expect(limiter.limit).to be_a Berater::Lock
90
- expect(limiter.limit).to be_a Berater::Lock
83
+ context 'with capacity 0' do
84
+ let(:limiter) { described_class.new(:key, 0) }
85
+
86
+ it 'always fails' do
87
+ expect(limiter).to be_incapacitated
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_incapacitated
100
+ end
101
+ end
102
+
103
+ it 'limit resets over time' do
104
+ 2.times { limiter.limit }
91
105
  expect(limiter).to be_incapacitated
92
106
 
93
- Timecop.travel(1)
107
+ Timecop.freeze(30)
94
108
 
95
- expect(limiter.limit).to be_a Berater::Lock
96
- expect(limiter.limit).to be_a Berater::Lock
109
+ 2.times { limiter.limit }
97
110
  expect(limiter).to be_incapacitated
98
111
  end
99
112
 
100
- context 'with capacity 0' do
101
- let(:limiter) { described_class.new(:key, 0) }
113
+ it 'limit resets with millisecond precision' do
114
+ 2.times { limiter.limit }
115
+ expect(limiter).to be_incapacitated
102
116
 
103
- it 'always fails' do
117
+ # travel forward to just before first lock times out
118
+ Timecop.freeze(29.999)
119
+ expect(limiter).to be_incapacitated
120
+
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
125
+ end
126
+
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)
104
149
  expect(limiter).to be_incapacitated
105
150
  end
151
+
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
162
+
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
175
+
176
+ it 'only allows positive values' do
177
+ expect { limiter.limit(cost: -1) }.to raise_error(ArgumentError)
178
+ end
106
179
  end
107
- end
108
180
 
109
- context 'with same key, different limiters' do
110
- let(:limiter_one) { described_class.new(:key, 1) }
111
- let(:limiter_two) { described_class.new(:key, 1) }
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) }
112
184
 
113
- it { expect(limiter_one.key).to eq limiter_two.key }
185
+ it { expect(limiter_one.key).to eq limiter_two.key }
114
186
 
115
- it 'works as expected' do
116
- expect(limiter_one.limit).to be_a Berater::Lock
187
+ it 'works as expected' do
188
+ expect(limiter_one.limit).to be_a Berater::Lock
117
189
 
118
- expect(limiter_one).to be_incapacitated
119
- expect(limiter_two).to be_incapacitated
190
+ expect(limiter_one).to be_incapacitated
191
+ expect(limiter_two).to be_incapacitated
192
+ end
120
193
  end
121
- end
122
194
 
123
- context 'with same key, different capacities' do
124
- let(:limiter_one) { described_class.new(:key, 1) }
125
- let(:limiter_two) { described_class.new(:key, 2) }
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) }
126
198
 
127
- it { expect(limiter_one.capacity).not_to eq limiter_two.capacity }
199
+ it { expect(limiter_one.capacity).not_to eq limiter_two.capacity }
128
200
 
129
- it 'works as expected' do
130
- one_lock = limiter_one.limit
131
- expect(one_lock).to be_a Berater::Lock
201
+ it 'works as expected' do
202
+ one_lock = limiter_one.limit
203
+ expect(one_lock).to be_a Berater::Lock
132
204
 
133
- expect(limiter_one).to be_incapacitated
134
- expect(limiter_two).not_to be_incapacitated
205
+ expect(limiter_one).to be_incapacitated
206
+ expect(limiter_two).not_to be_incapacitated
135
207
 
136
- two_lock = limiter_two.limit
137
- expect(two_lock).to be_a Berater::Lock
208
+ two_lock = limiter_two.limit
209
+ expect(two_lock).to be_a Berater::Lock
138
210
 
139
- expect(limiter_one).to be_incapacitated
140
- expect(limiter_two).to be_incapacitated
211
+ expect(limiter_one).to be_incapacitated
212
+ expect(limiter_two).to be_incapacitated
141
213
 
142
- one_lock.release
143
- expect(limiter_one).to be_incapacitated
144
- expect(limiter_two).not_to be_incapacitated
214
+ one_lock.release
215
+ expect(limiter_one).to be_incapacitated
216
+ expect(limiter_two).not_to be_incapacitated
145
217
 
146
- two_lock.release
147
- expect(limiter_one).not_to be_incapacitated
148
- expect(limiter_two).not_to be_incapacitated
218
+ two_lock.release
219
+ expect(limiter_one).not_to be_incapacitated
220
+ expect(limiter_two).not_to be_incapacitated
221
+ end
149
222
  end
150
- end
151
223
 
152
- context 'with different keys, different limiters' do
153
- let(:limiter_one) { described_class.new(:one, 1) }
154
- let(:limiter_two) { described_class.new(:two, 1) }
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
155
246
 
156
- it 'works as expected' do
157
- expect(limiter_one).not_to be_incapacitated
158
- expect(limiter_two).not_to be_incapacitated
247
+ describe '#overloaded?' do
248
+ let(:limiter) { described_class.new(:key, 1, timeout: 30) }
159
249
 
160
- one_lock = limiter_one.limit
161
- expect(one_lock).to be_a Berater::Lock
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
162
257
 
163
- expect(limiter_one).to be_incapacitated
164
- expect(limiter_two).not_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
264
+ end
265
+ end
165
266
 
166
- two_lock = limiter_two.limit
167
- expect(two_lock).to be_a Berater::Lock
267
+ describe '#to_s' do
268
+ def check(capacity, expected)
269
+ expect(
270
+ described_class.new(:key, capacity).to_s
271
+ ).to match(expected)
272
+ end
168
273
 
169
- expect(limiter_one).to be_incapacitated
170
- expect(limiter_two).to be_incapacitated
274
+ it 'works' do
275
+ check(1, /1 at a time/)
276
+ check(3, /3 at a time/)
171
277
  end
172
278
  end
173
279
 
@@ -0,0 +1,46 @@
1
+ require 'berater/dsl'
2
+
3
+ describe Berater do
4
+ using Berater::DSL
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
+ it 'instatiates a RateLimiter' do
19
+ limiter = Berater.new(:key) { 1.per second }
20
+ expect(limiter).to be_a Berater::RateLimiter
21
+ expect(limiter.key).to be :key
22
+ expect(limiter.capacity).to be 1
23
+ expect(limiter.interval).to be :second
24
+ end
25
+
26
+ it 'instatiates a ConcurrencyLimiter' do
27
+ limiter = Berater.new(:key, timeout: 2) { 1.at_once }
28
+ expect(limiter).to be_a Berater::ConcurrencyLimiter
29
+ expect(limiter.key).to be :key
30
+ expect(limiter.capacity).to be 1
31
+ expect(limiter.timeout).to be 2
32
+ end
33
+
34
+ it 'does not accept args and dsl block' do
35
+ expect {
36
+ Berater.new(:key, 2) { 3.at_once }
37
+ }.to raise_error(ArgumentError)
38
+ end
39
+
40
+ it 'requires either mode or dsl block' do
41
+ expect {
42
+ Berater.new(:key)
43
+ }.to raise_error(ArgumentError)
44
+ end
45
+
46
+ end
data/spec/dsl_spec.rb ADDED
@@ -0,0 +1,72 @@
1
+ require 'berater/dsl'
2
+
3
+ describe Berater::DSL do
4
+ def check(expected, &block)
5
+ expect(Berater::DSL.eval(&block)).to eq expected
6
+ end
7
+
8
+ context 'rate mode' do
9
+ it 'has keywords' do
10
+ check(:second) { second }
11
+ check(:minute) { minute }
12
+ check(:hour) { hour }
13
+ end
14
+
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 }
19
+ end
20
+
21
+ it 'cleans up afterward' do
22
+ check([ 1, :second ]) { 1.per second }
23
+
24
+ expect(Integer).not_to respond_to(:per)
25
+ expect(Integer).not_to respond_to(:every)
26
+ end
27
+
28
+ it 'works with variables' do
29
+ count = 1
30
+ interval = :second
31
+
32
+ check([ count, interval ]) { count.per interval }
33
+ end
34
+ end
35
+
36
+ context 'concurrency mode' do
37
+ it 'parses' do
38
+ check([ 1 ]) { 1.at_once }
39
+ check([ 3 ]) { 3.at_a_time }
40
+ check([ 5 ]) { 5.concurrently }
41
+ end
42
+
43
+ it 'cleans up afterward' do
44
+ check([ 1 ]) { 1.at_once }
45
+
46
+ expect(Integer).not_to respond_to(:at_once)
47
+ expect(Integer).not_to respond_to(:at_a_time)
48
+ expect(Integer).not_to respond_to(:concurrently)
49
+ end
50
+
51
+ it 'works with constants' do
52
+ class Foo
53
+ CAPACITY = 3
54
+ end
55
+
56
+ check([ Foo::CAPACITY ]) { Foo::CAPACITY.at_once }
57
+ end
58
+ end
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
+ end
@@ -15,11 +15,9 @@ describe Berater::Inhibitor do
15
15
  end
16
16
 
17
17
  describe '#limit' do
18
- let(:limiter) { described_class.new }
18
+ subject { described_class.new }
19
19
 
20
- it 'always limits' do
21
- expect { limiter.limit }.to be_inhibited
22
- end
20
+ it_behaves_like 'it is overloaded'
23
21
  end
24
22
 
25
23
  end