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.
- checksums.yaml +4 -4
- data/lib/berater.rb +27 -38
- data/lib/berater/concurrency_limiter.rb +58 -44
- data/lib/berater/dsl.rb +20 -9
- data/lib/berater/inhibitor.rb +7 -2
- data/lib/berater/limiter.rb +48 -2
- data/lib/berater/lock.rb +1 -10
- data/lib/berater/lua_script.rb +55 -0
- data/lib/berater/rate_limiter.rb +42 -73
- data/lib/berater/rspec.rb +2 -0
- data/lib/berater/rspec/matchers.rb +8 -6
- data/lib/berater/test_mode.rb +14 -5
- data/lib/berater/unlimiter.rb +6 -12
- data/lib/berater/utils.rb +46 -0
- data/lib/berater/version.rb +1 -1
- data/spec/berater_spec.rb +33 -70
- data/spec/concurrency_limiter_spec.rb +138 -63
- data/spec/dsl_refinement_spec.rb +46 -0
- data/spec/dsl_spec.rb +72 -0
- data/spec/inhibitor_spec.rb +2 -4
- data/spec/limiter_spec.rb +71 -0
- data/spec/lua_script_spec.rb +97 -0
- data/spec/matchers_spec.rb +14 -2
- data/spec/rate_limiter_spec.rb +94 -97
- data/spec/riddle_spec.rb +102 -0
- data/spec/test_mode_spec.rb +108 -78
- data/spec/unlimiter_spec.rb +3 -9
- data/spec/utils_spec.rb +78 -0
- metadata +31 -3
data/spec/matchers_spec.rb
CHANGED
@@ -16,6 +16,12 @@ describe 'be_overloaded' do
|
|
16
16
|
it { expect { limiter.limit }.not_to be_inhibited }
|
17
17
|
it { expect { limiter.limit }.not_to be_overrated }
|
18
18
|
it { expect { limiter.limit }.not_to be_incapacitated }
|
19
|
+
|
20
|
+
it 'catches false positives' do
|
21
|
+
expect {
|
22
|
+
expect { limiter }.to be_overloaded
|
23
|
+
}.to fail
|
24
|
+
end
|
19
25
|
end
|
20
26
|
|
21
27
|
context 'Berater::Inhibitor' do
|
@@ -29,10 +35,16 @@ describe 'be_overloaded' do
|
|
29
35
|
|
30
36
|
it { expect { limiter.limit }.to be_overloaded }
|
31
37
|
it { expect { limiter.limit }.to be_inhibited }
|
38
|
+
|
39
|
+
it 'catches false negatives' do
|
40
|
+
expect {
|
41
|
+
expect { limiter }.not_to be_overloaded
|
42
|
+
}.to fail
|
43
|
+
end
|
32
44
|
end
|
33
45
|
|
34
46
|
context 'Berater::RateLimiter' do
|
35
|
-
let(:limiter) { Berater.new(:key,
|
47
|
+
let(:limiter) { Berater.new(:key, 1, :second) }
|
36
48
|
|
37
49
|
it { expect(limiter).not_to be_overloaded }
|
38
50
|
it { expect(limiter).not_to be_inhibited }
|
@@ -67,7 +79,7 @@ describe 'be_overloaded' do
|
|
67
79
|
end
|
68
80
|
|
69
81
|
context 'Berater::ConcurrencyLimiter' do
|
70
|
-
let(:limiter) { Berater.new(:key,
|
82
|
+
let(:limiter) { Berater.new(:key, 1) }
|
71
83
|
|
72
84
|
it { expect(limiter).not_to be_overloaded }
|
73
85
|
it { expect(limiter).not_to be_inhibited }
|
data/spec/rate_limiter_spec.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
describe Berater::RateLimiter do
|
2
|
-
it_behaves_like 'a limiter', Berater.new(:key,
|
2
|
+
it_behaves_like 'a limiter', Berater.new(:key, 3, :second)
|
3
3
|
|
4
4
|
describe '.new' do
|
5
5
|
let(:limiter) { described_class.new(:key, 1, :second) }
|
6
6
|
|
7
7
|
it 'initializes' do
|
8
8
|
expect(limiter.key).to be :key
|
9
|
-
expect(limiter.
|
9
|
+
expect(limiter.capacity).to eq 1
|
10
10
|
expect(limiter.interval).to eq :second
|
11
11
|
end
|
12
12
|
|
@@ -15,82 +15,38 @@ describe Berater::RateLimiter do
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
describe '#
|
19
|
-
def
|
20
|
-
limiter = described_class.new(:key,
|
21
|
-
expect(limiter.
|
18
|
+
describe '#capacity' do
|
19
|
+
def expect_capacity(capacity)
|
20
|
+
limiter = described_class.new(:key, capacity, :second)
|
21
|
+
expect(limiter.capacity).to eq capacity
|
22
22
|
end
|
23
23
|
|
24
|
-
it {
|
25
|
-
it {
|
26
|
-
it {
|
24
|
+
it { expect_capacity(0) }
|
25
|
+
it { expect_capacity(1) }
|
26
|
+
it { expect_capacity(100) }
|
27
27
|
|
28
28
|
context 'with erroneous values' do
|
29
|
-
def
|
29
|
+
def expect_bad_capacity(capacity)
|
30
30
|
expect do
|
31
|
-
described_class.new(:key,
|
31
|
+
described_class.new(:key, capacity, :second)
|
32
32
|
end.to raise_error ArgumentError
|
33
33
|
end
|
34
34
|
|
35
|
-
it {
|
36
|
-
it {
|
37
|
-
it {
|
38
|
-
it {
|
35
|
+
it { expect_bad_capacity(0.5) }
|
36
|
+
it { expect_bad_capacity(-1) }
|
37
|
+
it { expect_bad_capacity('1') }
|
38
|
+
it { expect_bad_capacity(:one) }
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
42
|
describe '#interval' do
|
43
|
-
|
44
|
-
limiter = described_class.new(:key, 1, interval)
|
45
|
-
expect(limiter.interval).to eq expected
|
46
|
-
end
|
47
|
-
|
48
|
-
context 'with ints' do
|
49
|
-
it { expect_interval(0, 0) }
|
50
|
-
it { expect_interval(1, 1) }
|
51
|
-
it { expect_interval(33, 33) }
|
52
|
-
end
|
53
|
-
|
54
|
-
context 'with symbols' do
|
55
|
-
it { expect_interval(:sec, :second) }
|
56
|
-
it { expect_interval(:second, :second) }
|
57
|
-
it { expect_interval(:seconds, :second) }
|
58
|
-
|
59
|
-
it { expect_interval(:min, :minute) }
|
60
|
-
it { expect_interval(:minute, :minute) }
|
61
|
-
it { expect_interval(:minutes, :minute) }
|
62
|
-
|
63
|
-
it { expect_interval(:hour, :hour) }
|
64
|
-
it { expect_interval(:hours, :hour) }
|
65
|
-
end
|
66
|
-
|
67
|
-
context 'with strings' do
|
68
|
-
it { expect_interval('sec', :second) }
|
69
|
-
it { expect_interval('minute', :minute) }
|
70
|
-
it { expect_interval('hours', :hour) }
|
71
|
-
end
|
72
|
-
|
73
|
-
context 'with erroneous values' do
|
74
|
-
def expect_bad_interval(interval)
|
75
|
-
expect do
|
76
|
-
described_class.new(:key, 1, interval)
|
77
|
-
end.to raise_error(ArgumentError)
|
78
|
-
end
|
79
|
-
|
80
|
-
it { expect_bad_interval(-1) }
|
81
|
-
it { expect_bad_interval(:secondz) }
|
82
|
-
it { expect_bad_interval('huor') }
|
83
|
-
end
|
43
|
+
# see spec/utils_spec.rb for more
|
84
44
|
|
85
|
-
|
86
|
-
def expect_sec(interval, expected)
|
87
|
-
limiter = described_class.new(:key, 1, interval)
|
88
|
-
expect(limiter.instance_variable_get(:@interval_sec)).to eq expected
|
89
|
-
end
|
45
|
+
subject { described_class.new(:key, 1, :second) }
|
90
46
|
|
91
|
-
|
92
|
-
|
93
|
-
|
47
|
+
it 'saves the interval in original and microsecond format' do
|
48
|
+
expect(subject.interval).to be :second
|
49
|
+
expect(subject.instance_variable_get(:@interval_usec)).to be 10**6
|
94
50
|
end
|
95
51
|
end
|
96
52
|
|
@@ -112,50 +68,101 @@ describe Berater::RateLimiter do
|
|
112
68
|
expect(limiter).to be_overrated
|
113
69
|
end
|
114
70
|
|
115
|
-
it 'limit resets over time' do
|
71
|
+
it 'limit resets over time, with millisecond precision' do
|
116
72
|
3.times { limiter.limit }
|
117
73
|
expect(limiter).to be_overrated
|
118
74
|
|
119
|
-
# travel forward
|
75
|
+
# travel forward to just before the count decrements
|
76
|
+
Timecop.freeze(0.333)
|
77
|
+
expect(limiter).to be_overrated
|
78
|
+
|
79
|
+
# traveling one more millisecond will decrement the count
|
80
|
+
Timecop.freeze(0.001)
|
81
|
+
limiter.limit
|
82
|
+
expect(limiter).to be_overrated
|
83
|
+
|
84
|
+
# traveling 1 second will reset the count
|
120
85
|
Timecop.freeze(1)
|
121
86
|
|
122
87
|
3.times { limiter.limit }
|
123
88
|
expect(limiter).to be_overrated
|
124
89
|
end
|
125
|
-
end
|
126
90
|
|
127
|
-
|
128
|
-
|
129
|
-
let(:limiter_two) { described_class.new(:key, 1, :second) }
|
91
|
+
it 'accepts a dynamic capacity' do
|
92
|
+
limiter = described_class.new(:key, 1, :second)
|
130
93
|
|
131
|
-
|
132
|
-
|
94
|
+
expect { limiter.limit(capacity: 0) }.to be_overrated
|
95
|
+
5.times { limiter.limit(capacity: 10) }
|
96
|
+
expect { limiter }.to be_overrated
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'works with cost parameter' do
|
100
|
+
it { expect { limiter.limit(cost: 4) }.to be_overrated }
|
133
101
|
|
134
|
-
|
135
|
-
|
102
|
+
it 'works within limit' do
|
103
|
+
limiter.limit(cost: 3)
|
104
|
+
expect { limiter.limit }.to be_overrated
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'resets over time' do
|
108
|
+
limiter.limit(cost: 3)
|
109
|
+
expect(limiter).to be_overrated
|
110
|
+
|
111
|
+
Timecop.freeze(1)
|
112
|
+
expect(limiter).not_to be_overrated
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'can be a Float' do
|
116
|
+
2.times { limiter.limit(cost: 1.5) }
|
117
|
+
expect(limiter).to be_overrated
|
118
|
+
end
|
136
119
|
end
|
137
|
-
end
|
138
120
|
|
139
|
-
|
140
|
-
|
141
|
-
|
121
|
+
context 'with same key, different limiters' do
|
122
|
+
let(:limiter_one) { described_class.new(:key, 1, :second) }
|
123
|
+
let(:limiter_two) { described_class.new(:key, 1, :second) }
|
142
124
|
|
143
|
-
|
144
|
-
|
145
|
-
expect(limiter_two.limit).not_to be_overrated
|
125
|
+
it 'works as expected' do
|
126
|
+
expect(limiter_one.limit).not_to be_overrated
|
146
127
|
|
147
|
-
|
148
|
-
|
128
|
+
expect(limiter_one).to be_overrated
|
129
|
+
expect(limiter_two).to be_overrated
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context 'with different keys, different limiters' do
|
134
|
+
let(:limiter_one) { described_class.new(:one, 1, :second) }
|
135
|
+
let(:limiter_two) { described_class.new(:two, 2, :second) }
|
149
136
|
|
150
|
-
|
151
|
-
|
137
|
+
it 'works as expected' do
|
138
|
+
expect(limiter_one.limit).not_to be_overrated
|
139
|
+
expect(limiter_two.limit).not_to be_overrated
|
140
|
+
|
141
|
+
expect(limiter_one).to be_overrated
|
142
|
+
expect(limiter_two.limit).not_to be_overrated
|
143
|
+
|
144
|
+
expect(limiter_one).to be_overrated
|
145
|
+
expect(limiter_two).to be_overrated
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe '#overloaded?' do
|
151
|
+
let(:limiter) { described_class.new(:key, 1, :second) }
|
152
|
+
|
153
|
+
it 'works' do
|
154
|
+
expect(limiter.overloaded?).to be false
|
155
|
+
limiter.limit
|
156
|
+
expect(limiter.overloaded?).to be true
|
157
|
+
Timecop.freeze(1)
|
158
|
+
expect(limiter.overloaded?).to be false
|
152
159
|
end
|
153
160
|
end
|
154
161
|
|
155
162
|
describe '#to_s' do
|
156
|
-
def check(
|
163
|
+
def check(capacity, interval, expected)
|
157
164
|
expect(
|
158
|
-
described_class.new(:key,
|
165
|
+
described_class.new(:key, capacity, interval).to_s
|
159
166
|
).to match(expected)
|
160
167
|
end
|
161
168
|
|
@@ -171,16 +178,6 @@ describe Berater::RateLimiter do
|
|
171
178
|
check(1, 'hour', /1 per hour/)
|
172
179
|
end
|
173
180
|
|
174
|
-
it 'normalizes' do
|
175
|
-
check(1, :sec, /1 per second/)
|
176
|
-
check(1, :seconds, /1 per second/)
|
177
|
-
|
178
|
-
check(1, :min, /1 per minute/)
|
179
|
-
check(1, :minutes, /1 per minute/)
|
180
|
-
|
181
|
-
check(1, :hours, /1 per hour/)
|
182
|
-
end
|
183
|
-
|
184
181
|
it 'works with integers' do
|
185
182
|
check(1, 1, /1 every second/)
|
186
183
|
check(1, 2, /1 every 2 seconds/)
|
data/spec/riddle_spec.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# Can you build a rate limiter from a concurrency limiter? Yes!
|
2
|
+
|
3
|
+
class RateRiddler
|
4
|
+
def self.limit(capacity, interval)
|
5
|
+
lock = Berater::ConcurrencyLimiter.new(:key, capacity, timeout: interval).limit
|
6
|
+
yield if block_given?
|
7
|
+
# allow lock to time out rather than be released
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
describe 'a ConcurrencyLimiter-derived rate limiter' do
|
13
|
+
def limit(&block)
|
14
|
+
RateRiddler.limit(1, :second, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'works' do
|
18
|
+
expect(limit { 123 }).to eq 123
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'respects limits' do
|
22
|
+
limit
|
23
|
+
expect { limit }.to be_overloaded
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'resets over time' do
|
27
|
+
limit
|
28
|
+
expect { limit }.to be_overloaded
|
29
|
+
|
30
|
+
Timecop.freeze(1)
|
31
|
+
|
32
|
+
limit
|
33
|
+
expect { limit }.to be_overloaded
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
# Can you build a concurrency limiter from a rate limiter? Almost...
|
39
|
+
|
40
|
+
class ConcurrenyRiddler
|
41
|
+
def self.limit(capacity, timeout: nil)
|
42
|
+
timeout ||= 1_000 # fake infinity
|
43
|
+
|
44
|
+
limiter = Berater::RateLimiter.new(:key, capacity, timeout)
|
45
|
+
limiter.limit
|
46
|
+
yield if block_given?
|
47
|
+
ensure
|
48
|
+
# decrement counter
|
49
|
+
limiter.redis.decr(limiter.send(:cache_key, :key))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
describe 'a RateLimiter-derived concurrency limiter' do
|
55
|
+
def limit(capacity = 1, timeout: nil, &block)
|
56
|
+
ConcurrenyRiddler.limit(capacity, timeout: timeout, &block)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'works' do
|
60
|
+
expect(limit { 123 }).to eq 123
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'respects limits' do
|
64
|
+
limit do
|
65
|
+
# a second, simultaneous request isn't allowed
|
66
|
+
expect { limit }.to be_overloaded
|
67
|
+
end
|
68
|
+
|
69
|
+
# but now it'll work
|
70
|
+
limit
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'resets over time' do
|
74
|
+
limit(timeout: 1) do
|
75
|
+
expect { limit }.to be_overloaded
|
76
|
+
|
77
|
+
# ...wait for it
|
78
|
+
Timecop.freeze(10)
|
79
|
+
|
80
|
+
limit(timeout: 1)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
it "has no memory of the order, so timeouts don't work quite right" do
|
85
|
+
limit(2, timeout: 1) do
|
86
|
+
Timecop.freeze(0.5)
|
87
|
+
|
88
|
+
limit(2, timeout: 1) do
|
89
|
+
# this is where the masquerading breaks. the first lock is still
|
90
|
+
# being held and within it's timeout limit, however the RaterLimiter
|
91
|
+
# decremented the count internally since enough time has passed.
|
92
|
+
# This next call *should* fail, but doesn't.
|
93
|
+
|
94
|
+
expect {
|
95
|
+
expect { limit(2, timeout: 1) }.to be_overloaded
|
96
|
+
}.to fail
|
97
|
+
|
98
|
+
# ...close, but not quite!
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/spec/test_mode_spec.rb
CHANGED
@@ -1,84 +1,138 @@
|
|
1
|
-
|
1
|
+
describe Berater::TestMode, order: :defined do
|
2
|
+
let(:reset_test_mode) { true }
|
2
3
|
|
3
|
-
|
4
|
-
|
4
|
+
after do
|
5
|
+
Berater.test_mode = nil if reset_test_mode
|
6
|
+
end
|
7
|
+
|
8
|
+
context 'after test_mode.rb was required, but not used' do
|
9
|
+
let(:reset_test_mode) { false }
|
10
|
+
|
11
|
+
it 'has already been loaded by "berater/rspec", unfortunately' do
|
12
|
+
expect {
|
13
|
+
expect { Berater.test_mode }.to raise_error(NoMethodError)
|
14
|
+
}.to fail
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'defaults to off' do
|
18
|
+
expect(Berater.test_mode).to be nil
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'did not prepend .new yet' do
|
22
|
+
expect(Berater::Limiter.singleton_class.ancestors).not_to include(described_class)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'prepends when first turned on' do
|
26
|
+
Berater.test_mode = :pass
|
27
|
+
|
28
|
+
expect(Berater::Limiter.singleton_class.ancestors).to include(described_class)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'preserves the original functionality via super' do
|
32
|
+
expect { Berater::Limiter.new }.to raise_error(NotImplementedError)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '.test_mode' do
|
37
|
+
it 'can be turned on' do
|
38
|
+
Berater.test_mode = :pass
|
39
|
+
expect(Berater.test_mode).to be :pass
|
40
|
+
|
41
|
+
Berater.test_mode = :fail
|
42
|
+
expect(Berater.test_mode).to be :fail
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'can be turned off' do
|
46
|
+
Berater.test_mode = nil
|
47
|
+
expect(Berater.test_mode).to be nil
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'validates input' do
|
51
|
+
expect { Berater.test_mode = :foo }.to raise_error(ArgumentError)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
shared_examples 'it always works, without redis' do
|
56
|
+
before do
|
57
|
+
Berater.redis = nil
|
58
|
+
expect_any_instance_of(Berater::LuaScript).not_to receive(:eval)
|
59
|
+
end
|
60
|
+
|
61
|
+
it_behaves_like 'it is not overloaded'
|
62
|
+
|
63
|
+
it 'always works' do
|
64
|
+
10.times { subject.limit }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
shared_examples 'it never works, without redis' do
|
69
|
+
before do
|
70
|
+
Berater.redis = nil
|
71
|
+
expect_any_instance_of(Berater::LuaScript).not_to receive(:eval)
|
72
|
+
end
|
73
|
+
|
74
|
+
it_behaves_like 'it is overloaded'
|
75
|
+
|
76
|
+
it 'never works' do
|
77
|
+
expect { subject }.to be_overloaded
|
78
|
+
end
|
79
|
+
end
|
5
80
|
|
6
81
|
describe 'Unlimiter' do
|
7
|
-
|
82
|
+
subject { Berater::Unlimiter.new }
|
8
83
|
|
9
84
|
context 'when test_mode = nil' do
|
10
85
|
before { Berater.test_mode = nil }
|
11
86
|
|
12
|
-
it {
|
13
|
-
|
14
|
-
it 'works per usual' do
|
15
|
-
expect {|block| limiter.limit(&block) }.to yield_control
|
16
|
-
10.times { expect(limiter.limit).to be_a Berater::Lock }
|
17
|
-
end
|
87
|
+
it { is_expected.to be_a Berater::Unlimiter }
|
88
|
+
it_behaves_like 'it always works, without redis'
|
18
89
|
end
|
19
90
|
|
20
91
|
context 'when test_mode = :pass' do
|
21
92
|
before { Berater.test_mode = :pass }
|
22
93
|
|
23
|
-
it {
|
24
|
-
|
25
|
-
it 'always works' do
|
26
|
-
expect {|block| limiter.limit(&block) }.to yield_control
|
27
|
-
10.times { expect(limiter.limit).to be_a Berater::Lock }
|
28
|
-
end
|
94
|
+
it { is_expected.to be_a Berater::Unlimiter }
|
95
|
+
it_behaves_like 'it always works, without redis'
|
29
96
|
end
|
30
97
|
|
31
98
|
context 'when test_mode = :fail' do
|
32
99
|
before { Berater.test_mode = :fail }
|
33
100
|
|
34
|
-
it {
|
35
|
-
|
36
|
-
it 'never works' do
|
37
|
-
expect { limiter }.to be_overloaded
|
38
|
-
end
|
101
|
+
it { is_expected.to be_a Berater::Unlimiter }
|
102
|
+
it_behaves_like 'it never works, without redis'
|
39
103
|
end
|
40
104
|
end
|
41
105
|
|
42
106
|
describe 'Inhibitor' do
|
43
|
-
|
107
|
+
subject { Berater::Inhibitor.new }
|
44
108
|
|
45
109
|
context 'when test_mode = nil' do
|
46
110
|
before { Berater.test_mode = nil }
|
47
111
|
|
48
|
-
it {
|
49
|
-
|
50
|
-
it 'works per usual' do
|
51
|
-
expect { limiter }.to be_overloaded
|
52
|
-
end
|
112
|
+
it { is_expected.to be_a Berater::Inhibitor }
|
113
|
+
it_behaves_like 'it never works, without redis'
|
53
114
|
end
|
54
115
|
|
55
116
|
context 'when test_mode = :pass' do
|
56
117
|
before { Berater.test_mode = :pass }
|
57
118
|
|
58
|
-
it {
|
59
|
-
|
60
|
-
it 'always works' do
|
61
|
-
expect {|block| limiter.limit(&block) }.to yield_control
|
62
|
-
10.times { expect(limiter.limit).to be_a Berater::Lock }
|
63
|
-
end
|
119
|
+
it { is_expected.to be_a Berater::Inhibitor }
|
120
|
+
it_behaves_like 'it always works, without redis'
|
64
121
|
end
|
65
122
|
|
66
123
|
context 'when test_mode = :fail' do
|
67
124
|
before { Berater.test_mode = :fail }
|
68
125
|
|
69
|
-
it {
|
70
|
-
|
71
|
-
it 'never works' do
|
72
|
-
expect { limiter }.to be_overloaded
|
73
|
-
end
|
126
|
+
it { is_expected.to be_a Berater::Inhibitor }
|
127
|
+
it_behaves_like 'it never works, without redis'
|
74
128
|
end
|
75
129
|
end
|
76
130
|
|
77
131
|
describe 'RateLimiter' do
|
78
|
-
|
132
|
+
subject { Berater::RateLimiter.new(:key, 1, :second) }
|
79
133
|
|
80
134
|
shared_examples 'a RateLimiter' do
|
81
|
-
it {
|
135
|
+
it { is_expected.to be_a Berater::RateLimiter }
|
82
136
|
|
83
137
|
it 'checks arguments' do
|
84
138
|
expect {
|
@@ -91,15 +145,12 @@ describe 'Berater.test_mode' do
|
|
91
145
|
before { Berater.test_mode = nil }
|
92
146
|
|
93
147
|
it_behaves_like 'a RateLimiter'
|
148
|
+
it_behaves_like 'it is not overloaded'
|
94
149
|
|
95
150
|
it 'works per usual' do
|
96
|
-
expect(
|
97
|
-
expect(
|
98
|
-
expect {
|
99
|
-
end
|
100
|
-
|
101
|
-
it 'yields per usual' do
|
102
|
-
expect {|block| limiter.limit(&block) }.to yield_control
|
151
|
+
expect(Berater::RateLimiter::LUA_SCRIPT).to receive(:eval).twice.and_call_original
|
152
|
+
expect(subject.limit).to be_a Berater::Lock
|
153
|
+
expect { subject.limit }.to be_overloaded
|
103
154
|
end
|
104
155
|
end
|
105
156
|
|
@@ -107,31 +158,22 @@ describe 'Berater.test_mode' do
|
|
107
158
|
before { Berater.test_mode = :pass }
|
108
159
|
|
109
160
|
it_behaves_like 'a RateLimiter'
|
110
|
-
|
111
|
-
it 'always works and without calling redis' do
|
112
|
-
expect(limiter.redis).not_to receive(:eval)
|
113
|
-
expect {|block| limiter.limit(&block) }.to yield_control
|
114
|
-
10.times { expect(limiter.limit).to be_a Berater::Lock }
|
115
|
-
end
|
161
|
+
it_behaves_like 'it always works, without redis'
|
116
162
|
end
|
117
163
|
|
118
164
|
context 'when test_mode = :fail' do
|
119
165
|
before { Berater.test_mode = :fail }
|
120
166
|
|
121
167
|
it_behaves_like 'a RateLimiter'
|
122
|
-
|
123
|
-
it 'never works and without calling redis' do
|
124
|
-
expect(limiter.redis).not_to receive(:eval)
|
125
|
-
expect { limiter }.to be_overloaded
|
126
|
-
end
|
168
|
+
it_behaves_like 'it never works, without redis'
|
127
169
|
end
|
128
170
|
end
|
129
171
|
|
130
172
|
describe 'ConcurrencyLimiter' do
|
131
|
-
|
173
|
+
subject { Berater::ConcurrencyLimiter.new(:key, 1) }
|
132
174
|
|
133
175
|
shared_examples 'a ConcurrencyLimiter' do
|
134
|
-
it { expect(
|
176
|
+
it { expect(subject).to be_a Berater::ConcurrencyLimiter }
|
135
177
|
|
136
178
|
it 'checks arguments' do
|
137
179
|
expect {
|
@@ -144,15 +186,12 @@ describe 'Berater.test_mode' do
|
|
144
186
|
before { Berater.test_mode = nil }
|
145
187
|
|
146
188
|
it_behaves_like 'a ConcurrencyLimiter'
|
189
|
+
it_behaves_like 'it is not overloaded'
|
147
190
|
|
148
191
|
it 'works per usual' do
|
149
|
-
expect(
|
150
|
-
expect(
|
151
|
-
expect {
|
152
|
-
end
|
153
|
-
|
154
|
-
it 'yields per usual' do
|
155
|
-
expect {|block| limiter.limit(&block) }.to yield_control
|
192
|
+
expect(Berater::ConcurrencyLimiter::LUA_SCRIPT).to receive(:eval).twice.and_call_original
|
193
|
+
expect(subject.limit).to be_a Berater::Lock
|
194
|
+
expect { subject.limit }.to be_overloaded
|
156
195
|
end
|
157
196
|
end
|
158
197
|
|
@@ -160,23 +199,14 @@ describe 'Berater.test_mode' do
|
|
160
199
|
before { Berater.test_mode = :pass }
|
161
200
|
|
162
201
|
it_behaves_like 'a ConcurrencyLimiter'
|
163
|
-
|
164
|
-
it 'always works and without calling redis' do
|
165
|
-
expect(limiter.redis).not_to receive(:eval)
|
166
|
-
expect {|block| limiter.limit(&block) }.to yield_control
|
167
|
-
10.times { expect(limiter.limit).to be_a Berater::Lock }
|
168
|
-
end
|
202
|
+
it_behaves_like 'it always works, without redis'
|
169
203
|
end
|
170
204
|
|
171
205
|
context 'when test_mode = :fail' do
|
172
206
|
before { Berater.test_mode = :fail }
|
173
207
|
|
174
208
|
it_behaves_like 'a ConcurrencyLimiter'
|
175
|
-
|
176
|
-
it 'never works and without calling redis' do
|
177
|
-
expect(limiter.redis).not_to receive(:eval)
|
178
|
-
expect { limiter }.to be_overloaded
|
179
|
-
end
|
209
|
+
it_behaves_like 'it never works, without redis'
|
180
210
|
end
|
181
211
|
end
|
182
212
|
|