berater 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 microsecond 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_usec)).to be (3 * 10**6)
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_usec)).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,153 @@ 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
+ it 'limit resets over time' do
92
+ 2.times { limiter.limit }
93
+ expect(limiter).to be_incapacitated
103
94
 
104
- it { expect(limiter_one.key).to eq limiter_two.key }
95
+ Timecop.freeze(30)
105
96
 
106
- it 'works as expected' do
107
- expect(limiter_one.limit).to be_a Berater::Lock
97
+ 2.times { limiter.limit }
98
+ expect(limiter).to be_incapacitated
99
+ end
108
100
 
109
- expect(limiter_one).to be_incapacitated
110
- expect(limiter_two).to be_incapacitated
101
+ it 'limit resets with millisecond precision' do
102
+ 2.times { limiter.limit }
103
+ expect(limiter).to be_incapacitated
104
+
105
+ # travel forward to just before first lock times out
106
+ Timecop.freeze(29.999)
107
+ expect(limiter).to be_incapacitated
108
+
109
+ # traveling one more millisecond will decrement the count
110
+ Timecop.freeze(0.001)
111
+ 2.times { limiter.limit }
112
+ expect(limiter).to be_incapacitated
111
113
  end
112
- end
113
114
 
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) }
115
+ context 'with cost parameter' do
116
+ it { expect { limiter.limit(cost: 4) }.to be_incapacitated }
117
+
118
+ it 'works within limit' do
119
+ limiter.limit(cost: 2)
120
+ expect(limiter).to be_incapacitated
121
+ end
122
+
123
+ it 'releases full cost' do
124
+ lock = limiter.limit(cost: 2)
125
+ expect(limiter).to be_incapacitated
126
+
127
+ lock.release
128
+ expect(limiter).not_to be_incapacitated
129
+
130
+ lock = limiter.limit(cost: 2)
131
+ expect(limiter).to be_incapacitated
132
+ end
133
+
134
+ it 'respects timeout' do
135
+ limiter.limit(cost: 2)
136
+ expect(limiter).to be_incapacitated
117
137
 
118
- it { expect(limiter_one.capacity).not_to eq limiter_two.capacity }
138
+ Timecop.freeze(30)
139
+ expect(limiter).not_to be_incapacitated
119
140
 
120
- it 'works as expected' do
121
- one_lock = limiter_one.limit
122
- expect(one_lock).to be_a Berater::Lock
141
+ limiter.limit(cost: 2)
142
+ expect(limiter).to be_incapacitated
143
+ end
123
144
 
124
- expect(limiter_one).to be_incapacitated
125
- expect(limiter_two).not_to be_incapacitated
145
+ it 'accepts a dynamic capacity' do
146
+ limiter = described_class.new(:key, 1)
147
+
148
+ expect { limiter.limit(capacity: 0) }.to be_incapacitated
149
+ 5.times { limiter.limit(capacity: 10) }
150
+ expect { limiter }.to be_incapacitated
151
+ end
152
+ end
126
153
 
127
- two_lock = limiter_two.limit
128
- expect(two_lock).to be_a Berater::Lock
154
+ context 'with same key, different limiters' do
155
+ let(:limiter_one) { described_class.new(:key, 1) }
156
+ let(:limiter_two) { described_class.new(:key, 1) }
129
157
 
130
- expect(limiter_one).to be_incapacitated
131
- expect(limiter_two).to be_incapacitated
158
+ it { expect(limiter_one.key).to eq limiter_two.key }
132
159
 
133
- one_lock.release
134
- expect(limiter_one).to be_incapacitated
135
- expect(limiter_two).not_to be_incapacitated
160
+ it 'works as expected' do
161
+ expect(limiter_one.limit).to be_a Berater::Lock
136
162
 
137
- two_lock.release
138
- expect(limiter_one).not_to be_incapacitated
139
- expect(limiter_two).not_to be_incapacitated
163
+ expect(limiter_one).to be_incapacitated
164
+ expect(limiter_two).to be_incapacitated
165
+ end
140
166
  end
141
- end
142
167
 
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) }
168
+ context 'with same key, different capacities' do
169
+ let(:limiter_one) { described_class.new(:key, 1) }
170
+ let(:limiter_two) { described_class.new(:key, 2) }
171
+
172
+ it { expect(limiter_one.capacity).not_to eq limiter_two.capacity }
173
+
174
+ it 'works as expected' do
175
+ one_lock = limiter_one.limit
176
+ expect(one_lock).to be_a Berater::Lock
146
177
 
147
- it 'works as expected' do
148
- expect(limiter_one).not_to be_incapacitated
149
- expect(limiter_two).not_to be_incapacitated
178
+ expect(limiter_one).to be_incapacitated
179
+ expect(limiter_two).not_to be_incapacitated
150
180
 
151
- one_lock = limiter_one.limit
152
- expect(one_lock).to be_a Berater::Lock
181
+ two_lock = limiter_two.limit
182
+ expect(two_lock).to be_a Berater::Lock
153
183
 
154
- expect(limiter_one).to be_incapacitated
155
- expect(limiter_two).not_to be_incapacitated
184
+ expect(limiter_one).to be_incapacitated
185
+ expect(limiter_two).to be_incapacitated
156
186
 
157
- two_lock = limiter_two.limit
158
- expect(two_lock).to be_a Berater::Lock
187
+ one_lock.release
188
+ expect(limiter_one).to be_incapacitated
189
+ expect(limiter_two).not_to be_incapacitated
190
+
191
+ two_lock.release
192
+ expect(limiter_one).not_to be_incapacitated
193
+ expect(limiter_two).not_to be_incapacitated
194
+ end
195
+ end
196
+
197
+ context 'with different keys, different limiters' do
198
+ let(:limiter_one) { described_class.new(:one, 1) }
199
+ let(:limiter_two) { described_class.new(:two, 1) }
200
+
201
+ it 'works as expected' do
202
+ expect(limiter_one).not_to be_incapacitated
203
+ expect(limiter_two).not_to be_incapacitated
204
+
205
+ one_lock = limiter_one.limit
206
+ expect(one_lock).to be_a Berater::Lock
207
+
208
+ expect(limiter_one).to be_incapacitated
209
+ expect(limiter_two).not_to be_incapacitated
210
+
211
+ two_lock = limiter_two.limit
212
+ expect(two_lock).to be_a Berater::Lock
213
+
214
+ expect(limiter_one).to be_incapacitated
215
+ expect(limiter_two).to be_incapacitated
216
+ end
217
+ end
218
+ end
219
+
220
+ describe '#overloaded?' do
221
+ let(:limiter) { described_class.new(:key, 1, timeout: 30) }
222
+
223
+ it 'works' do
224
+ expect(limiter.overloaded?).to be false
225
+ lock = limiter.limit
226
+ expect(limiter.overloaded?).to be true
227
+ lock.release
228
+ expect(limiter.overloaded?).to be false
229
+ end
159
230
 
160
- expect(limiter_one).to be_incapacitated
161
- expect(limiter_two).to be_incapacitated
231
+ it 'respects timeout' do
232
+ expect(limiter.overloaded?).to be false
233
+ lock = limiter.limit
234
+ expect(limiter.overloaded?).to be true
235
+ Timecop.freeze(30)
236
+ expect(limiter.overloaded?).to be false
162
237
  end
163
238
  end
164
239
 
@@ -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
@@ -0,0 +1,71 @@
1
+ describe Berater::Limiter do
2
+ it 'can not be initialized' do
3
+ expect { described_class.new }.to raise_error(NotImplementedError)
4
+ end
5
+
6
+ describe 'abstract methods' do
7
+ let(:limiter) { Class.new(described_class).new(:key, 1) }
8
+
9
+ it do
10
+ expect { limiter.limit }.to raise_error(NotImplementedError)
11
+ expect { limiter.overloaded? }.to raise_error(NotImplementedError)
12
+ end
13
+ end
14
+
15
+ describe '==' do
16
+ let(:limiter) { Berater::RateLimiter.new(:key, 1, :second) }
17
+
18
+ it 'equals itself' do
19
+ expect(limiter).to eq limiter
20
+ end
21
+
22
+ it 'equals something with the same initialization parameters' do
23
+ expect(limiter).to eq(
24
+ Berater::RateLimiter.new(:key, 1, :second)
25
+ )
26
+ end
27
+
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
+ it 'does not equal something different' do
35
+ expect(limiter).not_to eq(
36
+ Berater::RateLimiter.new(:key, 2, :second)
37
+ )
38
+
39
+ expect(limiter).not_to eq(
40
+ Berater::RateLimiter.new(:keyz, 1, :second)
41
+ )
42
+
43
+ expect(limiter).not_to eq(
44
+ Berater::RateLimiter.new(:key, 1, :minute)
45
+ )
46
+ end
47
+
48
+ it 'does not equal something altogether different' do
49
+ expect(limiter).not_to eq(
50
+ Berater::ConcurrencyLimiter.new(:key, 1)
51
+ )
52
+ end
53
+
54
+ it 'works for ConcurrencyLimiter too' do
55
+ limiter = Berater::ConcurrencyLimiter.new(:key, 1)
56
+ expect(limiter).to eq limiter
57
+
58
+ expect(limiter).not_to eq(
59
+ Berater::ConcurrencyLimiter.new(:key, 1, timeout: 1)
60
+ )
61
+ end
62
+
63
+ it 'and the others' do
64
+ unlimiter = Berater::Unlimiter.new
65
+ expect(unlimiter).to eq unlimiter
66
+
67
+ expect(unlimiter).not_to eq Berater::Inhibitor.new
68
+ end
69
+ end
70
+
71
+ end
@@ -0,0 +1,97 @@
1
+ describe Berater::LuaScript do
2
+ subject { Berater::LuaScript('return redis.call("PING")') }
3
+
4
+ before { redis.script(:flush) }
5
+
6
+ let(:redis) { Berater.redis }
7
+
8
+ it { is_expected.to be_a Berater::LuaScript }
9
+
10
+ describe '#eval' do
11
+ def ping
12
+ expect(subject.eval(redis)).to eq 'PONG'
13
+ end
14
+
15
+ it { ping }
16
+
17
+ it 'loads the script into redis' do
18
+ expect(redis).to receive(:evalsha).once.and_call_original
19
+ expect(redis).to receive(:eval).once.and_call_original
20
+ ping
21
+ expect(subject.loaded?(redis)).to be true
22
+ end
23
+ end
24
+
25
+ describe '#load' do
26
+ it 'loads script into redis' do
27
+ expect(redis.script(:exists, subject.sha)).to be false
28
+ subject.load(redis)
29
+ expect(redis.script(:exists, subject.sha)).to be true
30
+ end
31
+
32
+ it 'returns the sha' do
33
+ expect(subject.load(redis)).to eq subject.sha
34
+ end
35
+
36
+ it 'validates the returned sha' do
37
+ allow(redis).to receive(:script).with(:flush).and_call_original
38
+ expect(redis).to receive(:script).with(:load, String).and_return('abc')
39
+ expect { subject.load(redis) }.to raise_error(RuntimeError)
40
+ end
41
+ end
42
+
43
+ describe '#loaded?' do
44
+ it do
45
+ expect(subject.loaded?(redis)).to be false
46
+ subject.load(redis)
47
+ expect(subject.loaded?(redis)).to be true
48
+ end
49
+ end
50
+
51
+ describe '#to_s' do
52
+ it { expect(subject.to_s).to be subject.source }
53
+ end
54
+
55
+ describe '#minify' do
56
+ subject do
57
+ expect(
58
+ Berater::LuaScript(lua).send(:minify)
59
+ ).to eq expected
60
+ end
61
+
62
+ context do
63
+ let(:lua) do <<-LUA
64
+ -- this comment gets removed
65
+ redis.call('PING') -- this one too
66
+ LUA
67
+ end
68
+
69
+ let(:expected) { "redis.call('PING')" }
70
+
71
+ it { subject }
72
+ end
73
+
74
+ context 'with if statement' do
75
+ let(:lua) do <<~LUA
76
+ if condition then
77
+ call
78
+ end
79
+
80
+ return 123
81
+ LUA
82
+ end
83
+
84
+ let(:expected) do
85
+ [
86
+ 'if condition then',
87
+ 'call',
88
+ 'end',
89
+ 'return 123'
90
+ ].join "\n"
91
+ end
92
+
93
+ it { subject }
94
+ end
95
+ end
96
+
97
+ end