berater 0.4.0 → 0.5.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.
@@ -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