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.
- checksums.yaml +4 -4
- data/lib/berater.rb +30 -23
- data/lib/berater/concurrency_limiter.rb +58 -46
- data/lib/berater/dsl.rb +68 -0
- data/lib/berater/inhibitor.rb +5 -3
- data/lib/berater/limiter.rb +94 -0
- data/lib/berater/lock.rb +4 -14
- data/lib/berater/lua_script.rb +55 -0
- data/lib/berater/rate_limiter.rb +69 -52
- data/lib/berater/rspec.rb +14 -0
- data/lib/berater/rspec/matchers.rb +81 -0
- data/lib/berater/test_mode.rb +43 -0
- data/lib/berater/unlimiter.rb +9 -14
- data/lib/berater/utils.rb +46 -0
- data/lib/berater/version.rb +1 -1
- data/spec/berater_spec.rb +37 -28
- data/spec/concurrency_limiter_spec.rb +179 -73
- 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/{matcher_spec.rb → matchers_spec.rb} +71 -3
- data/spec/rate_limiter_spec.rb +156 -70
- data/spec/riddle_spec.rb +102 -0
- data/spec/test_mode_spec.rb +225 -0
- data/spec/unlimiter_spec.rb +5 -12
- data/spec/utils_spec.rb +78 -0
- metadata +40 -10
- data/lib/berater/base_limiter.rb +0 -26
- data/spec/concurrency_lock_spec.rb +0 -39
- data/spec/rate_lock_spec.rb +0 -20
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
|
@@ -0,0 +1,225 @@
|
|
1
|
+
describe Berater::TestMode do
|
2
|
+
after do
|
3
|
+
Berater.test_mode = nil
|
4
|
+
end
|
5
|
+
|
6
|
+
context 'after test_mode.rb has been loaded' do
|
7
|
+
it 'monkey patches Berater' do
|
8
|
+
expect(Berater).to respond_to(:test_mode)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'defaults to off' do
|
12
|
+
expect(Berater.test_mode).to be nil
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'prepends Limiter subclasses' do
|
16
|
+
expect(Berater::Unlimiter.ancestors).to include(described_class)
|
17
|
+
expect(Berater::Inhibitor.ancestors).to include(described_class)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'preserves the original functionality via super' do
|
21
|
+
expect { Berater::Limiter.new }.to raise_error(NotImplementedError)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '.test_mode' do
|
26
|
+
it 'can be turned on' do
|
27
|
+
Berater.test_mode = :pass
|
28
|
+
expect(Berater.test_mode).to be :pass
|
29
|
+
|
30
|
+
Berater.test_mode = :fail
|
31
|
+
expect(Berater.test_mode).to be :fail
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'can be turned off' do
|
35
|
+
Berater.test_mode = nil
|
36
|
+
expect(Berater.test_mode).to be nil
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'validates input' do
|
40
|
+
expect { Berater.test_mode = :foo }.to raise_error(ArgumentError)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'works no matter when limiter was created' do
|
44
|
+
limiter = Berater::Unlimiter.new
|
45
|
+
expect(limiter).not_to be_overloaded
|
46
|
+
|
47
|
+
Berater.test_mode = :fail
|
48
|
+
expect(limiter).to be_overloaded
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'supports a generic expectation' do
|
52
|
+
Berater.test_mode = :pass
|
53
|
+
expect_any_instance_of(Berater::Limiter).to receive(:limit)
|
54
|
+
Berater::Unlimiter.new.limit
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
shared_examples 'it always works, without redis' do
|
59
|
+
before do
|
60
|
+
Berater.redis = nil
|
61
|
+
expect_any_instance_of(Berater::LuaScript).not_to receive(:eval)
|
62
|
+
end
|
63
|
+
|
64
|
+
it_behaves_like 'it is not overloaded'
|
65
|
+
|
66
|
+
it 'always works' do
|
67
|
+
10.times { subject.limit }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
shared_examples 'it never works, without redis' do
|
72
|
+
before do
|
73
|
+
Berater.redis = nil
|
74
|
+
expect_any_instance_of(Berater::LuaScript).not_to receive(:eval)
|
75
|
+
end
|
76
|
+
|
77
|
+
it_behaves_like 'it is overloaded'
|
78
|
+
end
|
79
|
+
|
80
|
+
describe 'Unlimiter' do
|
81
|
+
subject { Berater::Unlimiter.new }
|
82
|
+
|
83
|
+
context 'when test_mode = nil' do
|
84
|
+
before { Berater.test_mode = nil }
|
85
|
+
|
86
|
+
it { is_expected.to be_a Berater::Unlimiter }
|
87
|
+
it_behaves_like 'it always works, without redis'
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'when test_mode = :pass' do
|
91
|
+
before { Berater.test_mode = :pass }
|
92
|
+
|
93
|
+
it { is_expected.to be_a Berater::Unlimiter }
|
94
|
+
it_behaves_like 'it always works, without redis'
|
95
|
+
end
|
96
|
+
|
97
|
+
context 'when test_mode = :fail' do
|
98
|
+
before { Berater.test_mode = :fail }
|
99
|
+
|
100
|
+
it { is_expected.to be_a Berater::Unlimiter }
|
101
|
+
it_behaves_like 'it never works, without redis'
|
102
|
+
|
103
|
+
it 'supports class specific logic' do
|
104
|
+
expect(subject.overloaded?).to be true
|
105
|
+
expect { subject.limit }.to raise_error(Berater::Overloaded)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe 'Inhibitor' do
|
111
|
+
subject { Berater::Inhibitor.new }
|
112
|
+
|
113
|
+
context 'when test_mode = nil' do
|
114
|
+
before { Berater.test_mode = nil }
|
115
|
+
|
116
|
+
it { is_expected.to be_a Berater::Inhibitor }
|
117
|
+
it_behaves_like 'it never works, without redis'
|
118
|
+
end
|
119
|
+
|
120
|
+
context 'when test_mode = :pass' do
|
121
|
+
before { Berater.test_mode = :pass }
|
122
|
+
|
123
|
+
it { is_expected.to be_a Berater::Inhibitor }
|
124
|
+
it_behaves_like 'it always works, without redis'
|
125
|
+
end
|
126
|
+
|
127
|
+
context 'when test_mode = :fail' do
|
128
|
+
before { Berater.test_mode = :fail }
|
129
|
+
|
130
|
+
it { is_expected.to be_a Berater::Inhibitor }
|
131
|
+
it_behaves_like 'it never works, without redis'
|
132
|
+
|
133
|
+
it 'supports class specific logic' do
|
134
|
+
expect(subject.inhibited?).to be true
|
135
|
+
expect { subject.limit }.to raise_error(Berater::Inhibitor::Inhibited)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe 'RateLimiter' do
|
141
|
+
subject { Berater::RateLimiter.new(:key, 1, :second) }
|
142
|
+
|
143
|
+
shared_examples 'a RateLimiter' do
|
144
|
+
it { is_expected.to be_a Berater::RateLimiter }
|
145
|
+
|
146
|
+
it 'checks arguments' do
|
147
|
+
expect {
|
148
|
+
Berater::RateLimiter.new(:key, 1)
|
149
|
+
}.to raise_error(ArgumentError)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
context 'when test_mode = nil' do
|
154
|
+
before { Berater.test_mode = nil }
|
155
|
+
|
156
|
+
it_behaves_like 'a RateLimiter'
|
157
|
+
it_behaves_like 'it is not overloaded'
|
158
|
+
|
159
|
+
it 'works per usual' do
|
160
|
+
expect(Berater::RateLimiter::LUA_SCRIPT).to receive(:eval).twice.and_call_original
|
161
|
+
expect(subject.limit).to be_a Berater::Lock
|
162
|
+
expect { subject.limit }.to be_overloaded
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
context 'when test_mode = :pass' do
|
167
|
+
before { Berater.test_mode = :pass }
|
168
|
+
|
169
|
+
it_behaves_like 'a RateLimiter'
|
170
|
+
it_behaves_like 'it always works, without redis'
|
171
|
+
end
|
172
|
+
|
173
|
+
context 'when test_mode = :fail' do
|
174
|
+
before { Berater.test_mode = :fail }
|
175
|
+
|
176
|
+
it_behaves_like 'a RateLimiter'
|
177
|
+
it_behaves_like 'it never works, without redis'
|
178
|
+
|
179
|
+
it 'supports class specific logic' do
|
180
|
+
expect(subject.overrated?).to be true
|
181
|
+
expect { subject.limit }.to raise_error(Berater::RateLimiter::Overrated)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe 'ConcurrencyLimiter' do
|
187
|
+
subject { Berater::ConcurrencyLimiter.new(:key, 1) }
|
188
|
+
|
189
|
+
context 'when test_mode = nil' do
|
190
|
+
before { Berater.test_mode = nil }
|
191
|
+
|
192
|
+
it { is_expected.to be_a Berater::ConcurrencyLimiter }
|
193
|
+
|
194
|
+
it_behaves_like 'it is not overloaded'
|
195
|
+
|
196
|
+
it 'works per usual' do
|
197
|
+
expect(Berater::ConcurrencyLimiter::LUA_SCRIPT).to receive(:eval).twice.and_call_original
|
198
|
+
expect(subject.limit).to be_a Berater::Lock
|
199
|
+
expect { subject.limit }.to be_overloaded
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
context 'when test_mode = :pass' do
|
204
|
+
before { Berater.test_mode = :pass }
|
205
|
+
|
206
|
+
it { is_expected.to be_a Berater::ConcurrencyLimiter }
|
207
|
+
|
208
|
+
it_behaves_like 'it always works, without redis'
|
209
|
+
end
|
210
|
+
|
211
|
+
context 'when test_mode = :fail' do
|
212
|
+
before { Berater.test_mode = :fail }
|
213
|
+
|
214
|
+
it { is_expected.to be_a Berater::ConcurrencyLimiter }
|
215
|
+
|
216
|
+
it_behaves_like 'it never works, without redis'
|
217
|
+
|
218
|
+
it 'supports class specific logic' do
|
219
|
+
expect(subject.incapacitated?).to be true
|
220
|
+
expect { subject.limit }.to raise_error(Berater::ConcurrencyLimiter::Incapacitated)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
data/spec/unlimiter_spec.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
describe Berater::Unlimiter do
|
2
|
+
it_behaves_like 'a limiter', described_class.new
|
3
|
+
|
2
4
|
describe '.new' do
|
3
5
|
it 'initializes without any arguments or options' do
|
4
6
|
expect(described_class.new).to be_a described_class
|
@@ -15,23 +17,14 @@ describe Berater::Unlimiter do
|
|
15
17
|
end
|
16
18
|
|
17
19
|
describe '#limit' do
|
18
|
-
|
19
|
-
|
20
|
-
it 'works' do
|
21
|
-
expect {|b| limiter.limit(&b) }.to yield_control
|
22
|
-
end
|
20
|
+
subject { described_class.new }
|
23
21
|
|
24
|
-
|
25
|
-
expect(limiter.limit).to be_a Berater::Lock
|
26
|
-
end
|
22
|
+
it_behaves_like 'it is not overloaded'
|
27
23
|
|
28
24
|
it 'is never overloaded' do
|
29
25
|
10.times do
|
30
|
-
expect {
|
26
|
+
expect { subject.limit }.not_to be_overloaded
|
31
27
|
end
|
32
28
|
end
|
33
29
|
end
|
34
|
-
|
35
|
-
it_behaves_like 'a lock', described_class.new
|
36
|
-
|
37
30
|
end
|
data/spec/utils_spec.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
describe Berater::Utils do
|
2
|
+
using Berater::Utils
|
3
|
+
|
4
|
+
describe '.to_msec' do
|
5
|
+
def f(val)
|
6
|
+
(val * 10**3).to_i
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'works with integers' do
|
10
|
+
expect(0.to_msec).to be f(0)
|
11
|
+
expect(3.to_msec).to be f(3)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'works with floats' do
|
15
|
+
expect(0.1.to_msec).to be f(0.1)
|
16
|
+
expect(3.0.to_msec).to be f(3)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'truncates excessive precision' do
|
20
|
+
expect(0.123456.to_msec).to be 123
|
21
|
+
expect(123456.654321.to_msec).to be 123456654
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'works with symbols that are keywords' do
|
25
|
+
expect(:sec.to_msec).to be f(1)
|
26
|
+
expect(:second.to_msec).to be f(1)
|
27
|
+
expect(:seconds.to_msec).to be f(1)
|
28
|
+
|
29
|
+
expect(:min.to_msec).to be f(60)
|
30
|
+
expect(:minute.to_msec).to be f(60)
|
31
|
+
expect(:minutes.to_msec).to be f(60)
|
32
|
+
|
33
|
+
expect(:hour.to_msec).to be f(60 * 60)
|
34
|
+
expect(:hours.to_msec).to be f(60 * 60)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'works with strings that are keywords' do
|
38
|
+
expect('sec'.to_msec).to be f(1)
|
39
|
+
expect('second'.to_msec).to be f(1)
|
40
|
+
expect('seconds'.to_msec).to be f(1)
|
41
|
+
|
42
|
+
expect('min'.to_msec).to be f(60)
|
43
|
+
expect('minute'.to_msec).to be f(60)
|
44
|
+
expect('minutes'.to_msec).to be f(60)
|
45
|
+
|
46
|
+
expect('hour'.to_msec).to be f(60 * 60)
|
47
|
+
expect('hours'.to_msec).to be f(60 * 60)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'works with strings that are numeric' do
|
51
|
+
expect('0'.to_msec).to be f(0)
|
52
|
+
expect('3'.to_msec).to be f(3)
|
53
|
+
|
54
|
+
expect('0.1'.to_msec).to be f(0.1)
|
55
|
+
expect('3.0'.to_msec).to be f(3)
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'with erroneous values' do
|
59
|
+
def e(val)
|
60
|
+
expect { val.to_msec }.to raise_error(ArgumentError)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'rejects negative numbers' do
|
64
|
+
e(-1)
|
65
|
+
e(-1.2)
|
66
|
+
e('-1')
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'rejects bogus symbols and strings' do
|
70
|
+
e('abc')
|
71
|
+
e('1a')
|
72
|
+
e(:abc)
|
73
|
+
e(Float::INFINITY)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: berater
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Pepper
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-03-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: benchmark
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: byebug
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,28 +122,39 @@ dependencies:
|
|
108
122
|
- - ">="
|
109
123
|
- !ruby/object:Gem::Version
|
110
124
|
version: '0'
|
111
|
-
description:
|
125
|
+
description: work...within limits
|
112
126
|
email:
|
113
127
|
executables: []
|
114
128
|
extensions: []
|
115
129
|
extra_rdoc_files: []
|
116
130
|
files:
|
117
131
|
- lib/berater.rb
|
118
|
-
- lib/berater/base_limiter.rb
|
119
132
|
- lib/berater/concurrency_limiter.rb
|
133
|
+
- lib/berater/dsl.rb
|
120
134
|
- lib/berater/inhibitor.rb
|
135
|
+
- lib/berater/limiter.rb
|
121
136
|
- lib/berater/lock.rb
|
137
|
+
- lib/berater/lua_script.rb
|
122
138
|
- lib/berater/rate_limiter.rb
|
139
|
+
- lib/berater/rspec.rb
|
140
|
+
- lib/berater/rspec/matchers.rb
|
141
|
+
- lib/berater/test_mode.rb
|
123
142
|
- lib/berater/unlimiter.rb
|
143
|
+
- lib/berater/utils.rb
|
124
144
|
- lib/berater/version.rb
|
125
145
|
- spec/berater_spec.rb
|
126
146
|
- spec/concurrency_limiter_spec.rb
|
127
|
-
- spec/
|
147
|
+
- spec/dsl_refinement_spec.rb
|
148
|
+
- spec/dsl_spec.rb
|
128
149
|
- spec/inhibitor_spec.rb
|
129
|
-
- spec/
|
150
|
+
- spec/limiter_spec.rb
|
151
|
+
- spec/lua_script_spec.rb
|
152
|
+
- spec/matchers_spec.rb
|
130
153
|
- spec/rate_limiter_spec.rb
|
131
|
-
- spec/
|
154
|
+
- spec/riddle_spec.rb
|
155
|
+
- spec/test_mode_spec.rb
|
132
156
|
- spec/unlimiter_spec.rb
|
157
|
+
- spec/utils_spec.rb
|
133
158
|
homepage: https://github.com/dpep/berater_rb
|
134
159
|
licenses:
|
135
160
|
- MIT
|
@@ -155,10 +180,15 @@ specification_version: 4
|
|
155
180
|
summary: Berater
|
156
181
|
test_files:
|
157
182
|
- spec/rate_limiter_spec.rb
|
158
|
-
- spec/
|
159
|
-
- spec/
|
183
|
+
- spec/matchers_spec.rb
|
184
|
+
- spec/dsl_refinement_spec.rb
|
185
|
+
- spec/test_mode_spec.rb
|
186
|
+
- spec/dsl_spec.rb
|
187
|
+
- spec/lua_script_spec.rb
|
160
188
|
- spec/concurrency_limiter_spec.rb
|
161
|
-
- spec/
|
189
|
+
- spec/riddle_spec.rb
|
190
|
+
- spec/utils_spec.rb
|
162
191
|
- spec/berater_spec.rb
|
192
|
+
- spec/limiter_spec.rb
|
163
193
|
- spec/inhibitor_spec.rb
|
164
194
|
- spec/unlimiter_spec.rb
|