berater 0.1.1 → 0.3.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 +35 -24
- data/lib/berater/concurrency_limiter.rb +31 -88
- data/lib/berater/dsl.rb +57 -0
- data/lib/berater/inhibitor.rb +4 -10
- data/lib/berater/limiter.rb +30 -0
- data/lib/berater/lock.rb +36 -0
- data/lib/berater/rate_limiter.rb +37 -23
- data/lib/berater/rspec.rb +12 -0
- data/lib/berater/rspec/matchers.rb +60 -0
- data/lib/berater/test_mode.rb +43 -0
- data/lib/berater/unlimiter.rb +14 -10
- data/lib/berater/version.rb +1 -1
- data/spec/berater_spec.rb +69 -90
- data/spec/concurrency_limiter_spec.rb +63 -74
- data/spec/inhibitor_spec.rb +3 -15
- data/spec/{matcher_spec.rb → matchers_spec.rb} +4 -4
- data/spec/rate_limiter_spec.rb +83 -58
- data/spec/test_mode_spec.rb +183 -0
- data/spec/unlimiter_spec.rb +6 -31
- metadata +12 -7
- data/lib/berater/base_limiter.rb +0 -34
- data/spec/concurrency_lock_spec.rb +0 -50
data/spec/inhibitor_spec.rb
CHANGED
@@ -1,36 +1,24 @@
|
|
1
1
|
describe Berater::Inhibitor do
|
2
|
-
before { Berater.mode = :inhibited }
|
3
|
-
|
4
2
|
describe '.new' do
|
5
3
|
it 'initializes without any arguments or options' do
|
6
4
|
expect(described_class.new).to be_a described_class
|
7
5
|
end
|
8
6
|
|
9
7
|
it 'initializes with any arguments and options' do
|
10
|
-
expect(described_class.new(:abc, x: 123)).to be_a described_class
|
8
|
+
expect(described_class.new(:abc, :def, x: 123)).to be_a described_class
|
11
9
|
end
|
12
10
|
|
13
11
|
it 'has default values' do
|
14
|
-
expect(described_class.new.key).to
|
12
|
+
expect(described_class.new.key).to be :inhibitor
|
15
13
|
expect(described_class.new.redis).to be Berater.redis
|
16
14
|
end
|
17
15
|
end
|
18
16
|
|
19
|
-
describe '.limit' do
|
20
|
-
it 'always limits' do
|
21
|
-
expect { described_class.limit }.to be_inhibited
|
22
|
-
end
|
23
|
-
|
24
|
-
it 'works with any arguments or options' do
|
25
|
-
expect { described_class.limit(:abc, x: 123) }.to be_inhibited
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
17
|
describe '#limit' do
|
30
18
|
let(:limiter) { described_class.new }
|
31
19
|
|
32
20
|
it 'always limits' do
|
33
|
-
expect {
|
21
|
+
expect { limiter.limit }.to be_inhibited
|
34
22
|
end
|
35
23
|
end
|
36
24
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
describe 'be_overloaded' do
|
2
2
|
context 'Berater::Unlimiter' do
|
3
|
-
let(:limiter) { Berater.new(:unlimited) }
|
3
|
+
let(:limiter) { Berater.new(:key, :unlimited) }
|
4
4
|
|
5
5
|
it { expect(limiter).not_to be_overloaded }
|
6
6
|
it { expect(limiter).not_to be_inhibited }
|
@@ -19,7 +19,7 @@ describe 'be_overloaded' do
|
|
19
19
|
end
|
20
20
|
|
21
21
|
context 'Berater::Inhibitor' do
|
22
|
-
let(:limiter) { Berater.new(:inhibited) }
|
22
|
+
let(:limiter) { Berater.new(:key, :inhibited) }
|
23
23
|
|
24
24
|
it { expect(limiter).to be_overloaded }
|
25
25
|
it { expect(limiter).to be_inhibited }
|
@@ -32,7 +32,7 @@ describe 'be_overloaded' do
|
|
32
32
|
end
|
33
33
|
|
34
34
|
context 'Berater::RateLimiter' do
|
35
|
-
let(:limiter) { Berater.new(:rate, 1, :second) }
|
35
|
+
let(:limiter) { Berater.new(:key, :rate, 1, :second) }
|
36
36
|
|
37
37
|
it { expect(limiter).not_to be_overloaded }
|
38
38
|
it { expect(limiter).not_to be_inhibited }
|
@@ -67,7 +67,7 @@ describe 'be_overloaded' do
|
|
67
67
|
end
|
68
68
|
|
69
69
|
context 'Berater::ConcurrencyLimiter' do
|
70
|
-
let(:limiter) { Berater.new(:concurrency, 1) }
|
70
|
+
let(:limiter) { Berater.new(:key, :concurrency, 1) }
|
71
71
|
|
72
72
|
it { expect(limiter).not_to be_overloaded }
|
73
73
|
it { expect(limiter).not_to be_inhibited }
|
data/spec/rate_limiter_spec.rb
CHANGED
@@ -1,23 +1,23 @@
|
|
1
1
|
describe Berater::RateLimiter do
|
2
|
-
|
2
|
+
it_behaves_like 'a limiter', Berater.new(:key, :rate, 3, :second)
|
3
3
|
|
4
4
|
describe '.new' do
|
5
|
-
let(:limiter) { described_class.new(1, :second) }
|
5
|
+
let(:limiter) { described_class.new(:key, 1, :second) }
|
6
6
|
|
7
7
|
it 'initializes' do
|
8
|
+
expect(limiter.key).to be :key
|
8
9
|
expect(limiter.count).to eq 1
|
9
|
-
expect(limiter.interval).to eq
|
10
|
+
expect(limiter.interval).to eq :second
|
10
11
|
end
|
11
12
|
|
12
13
|
it 'has default values' do
|
13
|
-
expect(limiter.key).to eq described_class.to_s
|
14
14
|
expect(limiter.redis).to be Berater.redis
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
18
|
describe '#count' do
|
19
19
|
def expect_count(count)
|
20
|
-
limiter = described_class.new(count, :second)
|
20
|
+
limiter = described_class.new(:key, count, :second)
|
21
21
|
expect(limiter.count).to eq count
|
22
22
|
end
|
23
23
|
|
@@ -28,7 +28,7 @@ describe Berater::RateLimiter do
|
|
28
28
|
context 'with erroneous values' do
|
29
29
|
def expect_bad_count(count)
|
30
30
|
expect do
|
31
|
-
described_class.new(count, :second)
|
31
|
+
described_class.new(:key, count, :second)
|
32
32
|
end.to raise_error ArgumentError
|
33
33
|
end
|
34
34
|
|
@@ -41,7 +41,7 @@ describe Berater::RateLimiter do
|
|
41
41
|
|
42
42
|
describe '#interval' do
|
43
43
|
def expect_interval(interval, expected)
|
44
|
-
limiter = described_class.new(1, interval)
|
44
|
+
limiter = described_class.new(:key, 1, interval)
|
45
45
|
expect(limiter.interval).to eq expected
|
46
46
|
end
|
47
47
|
|
@@ -52,28 +52,28 @@ describe Berater::RateLimiter do
|
|
52
52
|
end
|
53
53
|
|
54
54
|
context 'with symbols' do
|
55
|
-
it { expect_interval(:sec,
|
56
|
-
it { expect_interval(:second,
|
57
|
-
it { expect_interval(:seconds,
|
55
|
+
it { expect_interval(:sec, :second) }
|
56
|
+
it { expect_interval(:second, :second) }
|
57
|
+
it { expect_interval(:seconds, :second) }
|
58
58
|
|
59
|
-
it { expect_interval(:min,
|
60
|
-
it { expect_interval(:minute,
|
61
|
-
it { expect_interval(:minutes,
|
59
|
+
it { expect_interval(:min, :minute) }
|
60
|
+
it { expect_interval(:minute, :minute) }
|
61
|
+
it { expect_interval(:minutes, :minute) }
|
62
62
|
|
63
|
-
it { expect_interval(:hour,
|
64
|
-
it { expect_interval(:hours,
|
63
|
+
it { expect_interval(:hour, :hour) }
|
64
|
+
it { expect_interval(:hours, :hour) }
|
65
65
|
end
|
66
66
|
|
67
67
|
context 'with strings' do
|
68
|
-
it { expect_interval('sec',
|
69
|
-
it { expect_interval('minute',
|
70
|
-
it { expect_interval('hours',
|
68
|
+
it { expect_interval('sec', :second) }
|
69
|
+
it { expect_interval('minute', :minute) }
|
70
|
+
it { expect_interval('hours', :hour) }
|
71
71
|
end
|
72
72
|
|
73
73
|
context 'with erroneous values' do
|
74
74
|
def expect_bad_interval(interval)
|
75
75
|
expect do
|
76
|
-
described_class.new(1, interval)
|
76
|
+
described_class.new(:key, 1, interval)
|
77
77
|
end.to raise_error(ArgumentError)
|
78
78
|
end
|
79
79
|
|
@@ -81,85 +81,110 @@ describe Berater::RateLimiter do
|
|
81
81
|
it { expect_bad_interval(:secondz) }
|
82
82
|
it { expect_bad_interval('huor') }
|
83
83
|
end
|
84
|
-
end
|
85
84
|
|
86
|
-
|
87
|
-
|
85
|
+
context 'interprets values' do
|
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
|
88
90
|
|
89
|
-
|
90
|
-
|
91
|
+
it { expect_sec(:second, 1) }
|
92
|
+
it { expect_sec(:minute, 60) }
|
93
|
+
it { expect_sec(:hour, 3600) }
|
91
94
|
end
|
95
|
+
end
|
92
96
|
|
93
|
-
|
94
|
-
|
95
|
-
expect(limiter.limit).to eq 2
|
96
|
-
expect(limiter.limit).to eq 3
|
97
|
-
end
|
97
|
+
describe '#limit' do
|
98
|
+
let(:limiter) { described_class.new(:key, 3, :second) }
|
98
99
|
|
99
|
-
it '
|
100
|
+
it 'works' do
|
100
101
|
expect {|b| limiter.limit(&b) }.to yield_control
|
101
102
|
expect(limiter.limit { 123 }).to eq 123
|
102
103
|
end
|
103
104
|
|
105
|
+
it 'works without a block' do
|
106
|
+
expect(limiter.limit).to be_a Berater::Lock
|
107
|
+
end
|
108
|
+
|
104
109
|
it 'limits excessive calls' do
|
105
110
|
3.times { limiter.limit }
|
106
111
|
|
107
|
-
expect
|
112
|
+
expect(limiter).to be_overrated
|
108
113
|
end
|
109
114
|
|
110
115
|
it 'limit resets over time' do
|
111
|
-
|
112
|
-
expect(limiter.limit).to eq 2
|
113
|
-
expect(limiter.limit).to eq 3
|
116
|
+
3.times { limiter.limit }
|
114
117
|
expect(limiter).to be_overrated
|
115
118
|
|
116
119
|
# travel forward a second
|
117
120
|
Timecop.freeze(1)
|
118
121
|
|
119
|
-
|
120
|
-
expect(limiter.limit).to eq 2
|
121
|
-
expect(limiter.limit).to eq 3
|
122
|
+
3.times { limiter.limit }
|
122
123
|
expect(limiter).to be_overrated
|
123
124
|
end
|
124
125
|
end
|
125
126
|
|
126
127
|
context 'with same key, different limiters' do
|
127
|
-
let(:limiter_one) { described_class.new(1, :second) }
|
128
|
-
let(:limiter_two) { described_class.new(1, :second) }
|
128
|
+
let(:limiter_one) { described_class.new(:key, 1, :second) }
|
129
|
+
let(:limiter_two) { described_class.new(:key, 1, :second) }
|
129
130
|
|
130
131
|
it 'works as expected' do
|
131
|
-
expect(limiter_one.limit).
|
132
|
+
expect(limiter_one.limit).not_to be_overrated
|
132
133
|
|
133
|
-
expect
|
134
|
-
expect
|
134
|
+
expect(limiter_one).to be_overrated
|
135
|
+
expect(limiter_two).to be_overrated
|
135
136
|
end
|
136
137
|
end
|
137
138
|
|
138
|
-
context 'with different keys,
|
139
|
-
let(:
|
139
|
+
context 'with different keys, different limiters' do
|
140
|
+
let(:limiter_one) { described_class.new(:one, 1, :second) }
|
141
|
+
let(:limiter_two) { described_class.new(:two, 2, :second) }
|
140
142
|
|
141
143
|
it 'works as expected' do
|
142
|
-
expect
|
143
|
-
expect
|
144
|
+
expect(limiter_one.limit).not_to be_overrated
|
145
|
+
expect(limiter_two.limit).not_to be_overrated
|
146
|
+
|
147
|
+
expect(limiter_one).to be_overrated
|
148
|
+
expect(limiter_two.limit).not_to be_overrated
|
144
149
|
|
145
|
-
expect
|
146
|
-
expect
|
150
|
+
expect(limiter_one).to be_overrated
|
151
|
+
expect(limiter_two).to be_overrated
|
147
152
|
end
|
148
153
|
end
|
149
154
|
|
150
|
-
|
151
|
-
|
152
|
-
|
155
|
+
describe '#to_s' do
|
156
|
+
def check(count, interval, expected)
|
157
|
+
expect(
|
158
|
+
described_class.new(:key, count, interval).to_s
|
159
|
+
).to match(expected)
|
160
|
+
end
|
153
161
|
|
154
|
-
it 'works
|
155
|
-
|
156
|
-
|
162
|
+
it 'works with symbols' do
|
163
|
+
check(1, :second, /1 per second/)
|
164
|
+
check(1, :minute, /1 per minute/)
|
165
|
+
check(1, :hour, /1 per hour/)
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'works with strings' do
|
169
|
+
check(1, 'second', /1 per second/)
|
170
|
+
check(1, 'minute', /1 per minute/)
|
171
|
+
check(1, 'hour', /1 per hour/)
|
172
|
+
end
|
157
173
|
|
158
|
-
|
159
|
-
|
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
|
160
183
|
|
161
|
-
|
162
|
-
|
184
|
+
it 'works with integers' do
|
185
|
+
check(1, 1, /1 every second/)
|
186
|
+
check(1, 2, /1 every 2 seconds/)
|
187
|
+
check(2, 3, /2 every 3 seconds/)
|
163
188
|
end
|
164
189
|
end
|
165
190
|
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'berater/test_mode'
|
2
|
+
|
3
|
+
describe 'Berater.test_mode' do
|
4
|
+
after { Berater.test_mode = nil }
|
5
|
+
|
6
|
+
describe 'Unlimiter' do
|
7
|
+
let(:limiter) { Berater::Unlimiter.new }
|
8
|
+
|
9
|
+
context 'when test_mode = nil' do
|
10
|
+
before { Berater.test_mode = nil }
|
11
|
+
|
12
|
+
it { expect(limiter).to be_a Berater::Unlimiter }
|
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
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'when test_mode = :pass' do
|
21
|
+
before { Berater.test_mode = :pass }
|
22
|
+
|
23
|
+
it { expect(limiter).to be_a Berater::Unlimiter }
|
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
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'when test_mode = :fail' do
|
32
|
+
before { Berater.test_mode = :fail }
|
33
|
+
|
34
|
+
it { expect(limiter).to be_a Berater::Unlimiter }
|
35
|
+
|
36
|
+
it 'never works' do
|
37
|
+
expect { limiter }.to be_overloaded
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe 'Inhibitor' do
|
43
|
+
let(:limiter) { Berater::Inhibitor.new }
|
44
|
+
|
45
|
+
context 'when test_mode = nil' do
|
46
|
+
before { Berater.test_mode = nil }
|
47
|
+
|
48
|
+
it { expect(limiter).to be_a Berater::Inhibitor }
|
49
|
+
|
50
|
+
it 'works per usual' do
|
51
|
+
expect { limiter }.to be_overloaded
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'when test_mode = :pass' do
|
56
|
+
before { Berater.test_mode = :pass }
|
57
|
+
|
58
|
+
it { expect(limiter).to be_a Berater::Inhibitor }
|
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
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'when test_mode = :fail' do
|
67
|
+
before { Berater.test_mode = :fail }
|
68
|
+
|
69
|
+
it { expect(limiter).to be_a Berater::Inhibitor }
|
70
|
+
|
71
|
+
it 'never works' do
|
72
|
+
expect { limiter }.to be_overloaded
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe 'RateLimiter' do
|
78
|
+
let(:limiter) { Berater::RateLimiter.new(:key, 1, :second) }
|
79
|
+
|
80
|
+
shared_examples 'a RateLimiter' do
|
81
|
+
it { expect(limiter).to be_a Berater::RateLimiter }
|
82
|
+
|
83
|
+
it 'checks arguments' do
|
84
|
+
expect {
|
85
|
+
Berater::RateLimiter.new(:key, 1)
|
86
|
+
}.to raise_error(ArgumentError)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'when test_mode = nil' do
|
91
|
+
before { Berater.test_mode = nil }
|
92
|
+
|
93
|
+
it_behaves_like 'a RateLimiter'
|
94
|
+
|
95
|
+
it 'works per usual' do
|
96
|
+
expect(limiter.redis).to receive(:multi).twice.and_call_original
|
97
|
+
expect(limiter.limit).to be_a Berater::Lock
|
98
|
+
expect { limiter.limit }.to be_overloaded
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'yields per usual' do
|
102
|
+
expect {|block| limiter.limit(&block) }.to yield_control
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'when test_mode = :pass' do
|
107
|
+
before { Berater.test_mode = :pass }
|
108
|
+
|
109
|
+
it_behaves_like 'a RateLimiter'
|
110
|
+
|
111
|
+
it 'always works and without calling redis' do
|
112
|
+
expect(limiter.redis).not_to receive(:multi)
|
113
|
+
expect {|block| limiter.limit(&block) }.to yield_control
|
114
|
+
10.times { expect(limiter.limit).to be_a Berater::Lock }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
context 'when test_mode = :fail' do
|
119
|
+
before { Berater.test_mode = :fail }
|
120
|
+
|
121
|
+
it_behaves_like 'a RateLimiter'
|
122
|
+
|
123
|
+
it 'never works and without calling redis' do
|
124
|
+
expect(limiter.redis).not_to receive(:multi)
|
125
|
+
expect { limiter }.to be_overloaded
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe 'ConcurrencyLimiter' do
|
131
|
+
let(:limiter) { Berater::ConcurrencyLimiter.new(:key, 1) }
|
132
|
+
|
133
|
+
shared_examples 'a ConcurrencyLimiter' do
|
134
|
+
it { expect(limiter).to be_a Berater::ConcurrencyLimiter }
|
135
|
+
|
136
|
+
it 'checks arguments' do
|
137
|
+
expect {
|
138
|
+
Berater::ConcurrencyLimiter.new(:key, 1.0)
|
139
|
+
}.to raise_error(ArgumentError)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context 'when test_mode = nil' do
|
144
|
+
before { Berater.test_mode = nil }
|
145
|
+
|
146
|
+
it_behaves_like 'a ConcurrencyLimiter'
|
147
|
+
|
148
|
+
it 'works per usual' do
|
149
|
+
expect(limiter.redis).to receive(:eval).twice.and_call_original
|
150
|
+
expect(limiter.limit).to be_a Berater::Lock
|
151
|
+
expect { limiter.limit }.to be_overloaded
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'yields per usual' do
|
155
|
+
expect {|block| limiter.limit(&block) }.to yield_control
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context 'when test_mode = :pass' do
|
160
|
+
before { Berater.test_mode = :pass }
|
161
|
+
|
162
|
+
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
|
169
|
+
end
|
170
|
+
|
171
|
+
context 'when test_mode = :fail' do
|
172
|
+
before { Berater.test_mode = :fail }
|
173
|
+
|
174
|
+
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
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|