berater 0.2.0 → 0.6.1

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,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