berater 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,12 @@
1
+ require 'berater'
2
+ require 'berater/rspec/matchers'
3
+ require 'berater/test_mode'
4
+ require 'rspec'
5
+
6
+ RSpec.configure do |config|
7
+ config.include(BeraterMatchers)
8
+
9
+ config.after do
10
+ Berater.expunge rescue nil
11
+ end
12
+ end
@@ -0,0 +1,60 @@
1
+ module BeraterMatchers
2
+ class Overloaded
3
+ def initialize(type)
4
+ @type = type
5
+ end
6
+
7
+ def supports_block_expectations?
8
+ true
9
+ end
10
+
11
+ def matches?(obj)
12
+ begin
13
+ case obj
14
+ when Proc
15
+ # eg. expect { ... }.to be_overrated
16
+ res = obj.call
17
+
18
+ if res.is_a? Berater::Limiter
19
+ # eg. expect { Berater.new(...) }.to be_overrated
20
+ res.limit {}
21
+ end
22
+ when Berater::Limiter
23
+ # eg. expect(Berater.new(...)).to be_overrated
24
+ obj.limit {}
25
+ end
26
+
27
+ false
28
+ rescue @type
29
+ true
30
+ end
31
+ end
32
+
33
+ # def description
34
+ # it { expect { Berater.new(:inhibitor) }.not_to be_overrated }
35
+
36
+ def failure_message
37
+ "expected #{@type} to be raised"
38
+ end
39
+
40
+ def failure_message_when_negated
41
+ "did not expect #{@type} to be raised"
42
+ end
43
+ end
44
+
45
+ def be_overloaded
46
+ Overloaded.new(Berater::Overloaded)
47
+ end
48
+
49
+ def be_overrated
50
+ Overloaded.new(Berater::RateLimiter::Overrated)
51
+ end
52
+
53
+ def be_incapacitated
54
+ Overloaded.new(Berater::ConcurrencyLimiter::Incapacitated)
55
+ end
56
+
57
+ def be_inhibited
58
+ Overloaded.new(Berater::Inhibitor::Inhibited)
59
+ end
60
+ end
@@ -0,0 +1,43 @@
1
+ require 'berater'
2
+
3
+ module Berater
4
+ extend self
5
+
6
+ attr_reader :test_mode
7
+
8
+ def test_mode=(mode)
9
+ unless [ nil, :pass, :fail ].include?(mode)
10
+ raise ArgumentError, "invalid mode: #{Berater.test_mode}"
11
+ end
12
+
13
+ @test_mode = mode
14
+ end
15
+
16
+ class Limiter
17
+ def self.new(*args, **opts)
18
+ return super unless Berater.test_mode
19
+
20
+ # chose a stub class with desired behavior
21
+ stub_klass = case Berater.test_mode
22
+ when :pass
23
+ Berater::Unlimiter
24
+ when :fail
25
+ Berater::Inhibitor
26
+ end
27
+
28
+ # don't stub self
29
+ return super if self < stub_klass
30
+
31
+ # swap out limit method with stub
32
+ super.tap do |instance|
33
+ stub = stub_klass.allocate
34
+ stub.send(:initialize, *args, **opts)
35
+
36
+ instance.define_singleton_method(:limit) do |&block|
37
+ stub.limit(&block)
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ end
@@ -1,18 +1,22 @@
1
1
  module Berater
2
- class Unlimiter < BaseLimiter
2
+ class Unlimiter < Limiter
3
3
 
4
- def initialize(*args, **opts)
5
- super(**opts)
4
+ def initialize(key = :unlimiter, *args, **opts)
5
+ super(key, **opts)
6
6
  end
7
7
 
8
- def limit(**opts, &block)
9
- unless opts.empty?
10
- return self.class.new(
11
- **options.merge(opts)
12
- ).limit(&block)
13
- end
8
+ def limit
9
+ lock = Lock.new(self, 0, 0)
14
10
 
15
- yield if block_given?
11
+ if block_given?
12
+ begin
13
+ yield lock
14
+ ensure
15
+ lock.release
16
+ end
17
+ else
18
+ lock
19
+ end
16
20
  end
17
21
 
18
22
  end
@@ -1,3 +1,3 @@
1
1
  module Berater
2
- VERSION = '0.1.1'
2
+ VERSION = '0.3.0'
3
3
  end
data/spec/berater_spec.rb CHANGED
@@ -9,10 +9,10 @@ describe Berater do
9
9
  describe '.configure' do
10
10
  it 'can be set via configure' do
11
11
  Berater.configure do |c|
12
- c.mode = :rate
12
+ c.redis = :redis
13
13
  end
14
14
 
15
- expect(Berater.mode).to eq :rate
15
+ expect(Berater.redis).to eq :redis
16
16
  end
17
17
  end
18
18
 
@@ -24,20 +24,13 @@ describe Berater do
24
24
  end
25
25
  end
26
26
 
27
- describe '.mode' do
28
- it 'validates inputs' do
29
- expect { Berater.mode = :foo }.to raise_error(ArgumentError)
30
- end
31
- end
32
-
33
- context 'unlimited mode' do
34
- before { Berater.mode = :unlimited }
35
-
36
- describe '.new' do
37
- let(:limiter) { Berater.new(:unlimited) }
27
+ describe '.new' do
28
+ context 'unlimited mode' do
29
+ let(:limiter) { Berater.new(:key, :unlimited) }
38
30
 
39
31
  it 'instantiates an Unlimiter' do
40
32
  expect(limiter).to be_a Berater::Unlimiter
33
+ expect(limiter.key).to be :key
41
34
  end
42
35
 
43
36
  it 'inherits redis' do
@@ -46,35 +39,22 @@ describe Berater do
46
39
 
47
40
  it 'accepts options' do
48
41
  redis = double('Redis')
49
- limiter = Berater.new(:unlimited, key: 'key', redis: redis)
50
- expect(limiter.key).to match(/key/)
42
+ limiter = Berater.new(:key, :unlimited, redis: redis)
51
43
  expect(limiter.redis).to be redis
52
44
  end
53
- end
54
-
55
- describe '.limit' do
56
- it 'works' do
57
- expect(Berater.limit).to be_nil
58
- end
59
45
 
60
- it 'yields' do
61
- expect {|b| Berater.limit(&b) }.to yield_control
62
- end
63
-
64
- it 'never limits' do
65
- 10.times { expect(Berater.limit { 123 } ).to eq 123 }
46
+ it 'works with convinience' do
47
+ expect(Berater).to receive(:new).and_return(limiter)
48
+ expect {|b| Berater(:key, :unlimited, &b) }.to yield_control
66
49
  end
67
50
  end
68
- end
69
51
 
70
- context 'inhibited mode' do
71
- before { Berater.mode = :inhibited }
72
-
73
- describe '.new' do
74
- let(:limiter) { Berater.new(:inhibited) }
52
+ context 'inhibited mode' do
53
+ let(:limiter) { Berater.new(:key, :inhibited) }
75
54
 
76
55
  it 'instantiates an Inhibitor' do
77
56
  expect(limiter).to be_a Berater::Inhibitor
57
+ expect(limiter.key).to be :key
78
58
  end
79
59
 
80
60
  it 'inherits redis' do
@@ -83,27 +63,22 @@ describe Berater do
83
63
 
84
64
  it 'accepts options' do
85
65
  redis = double('Redis')
86
- limiter = Berater.new(:inhibited, key: 'key', redis: redis)
87
- expect(limiter.key).to match(/key/)
66
+ limiter = Berater.new(:key, :inhibited, redis: redis)
88
67
  expect(limiter.redis).to be redis
89
68
  end
90
- end
91
69
 
92
- describe '.limit' do
93
- it 'always limits' do
94
- expect { Berater.limit }.to be_inhibited
70
+ it 'works with convinience' do
71
+ expect(Berater).to receive(:new).and_return(limiter)
72
+ expect { Berater(:key, :inhibited) }.to be_inhibited
95
73
  end
96
74
  end
97
- end
98
75
 
99
- context 'rate mode' do
100
- before { Berater.mode = :rate }
101
-
102
- describe '.limiter' do
103
- let(:limiter) { Berater.new(:rate, 1, :second) }
76
+ context 'rate mode' do
77
+ let(:limiter) { Berater.new(:key, :rate, 1, :second) }
104
78
 
105
79
  it 'instantiates a RateLimiter' do
106
80
  expect(limiter).to be_a Berater::RateLimiter
81
+ expect(limiter.key).to be :key
107
82
  end
108
83
 
109
84
  it 'inherits redis' do
@@ -112,44 +87,22 @@ describe Berater do
112
87
 
113
88
  it 'accepts options' do
114
89
  redis = double('Redis')
115
- limiter = Berater.new(:rate, 1, :second, key: 'key', redis: redis)
116
- expect(limiter.key).to match(/key/)
90
+ limiter = Berater.new(:key, :rate, 1, :second, redis: redis)
117
91
  expect(limiter.redis).to be redis
118
92
  end
119
- end
120
-
121
- describe '.limit' do
122
- it 'works' do
123
- expect(Berater.limit(1, :second)).to eq 1
124
- end
125
-
126
- it 'yields' do
127
- expect {|b| Berater.limit(2, :second, &b) }.to yield_control
128
- expect(Berater.limit(2, :second) { 123 }).to eq 123
129
- end
130
93
 
131
- it 'limits excessive calls' do
132
- expect(Berater.limit(1, :second)).to eq 1
133
- expect { Berater.limit(1, :second) }.to be_overrated
134
- end
135
-
136
- it 'accepts options' do
137
- redis = double('Redis')
138
- expect(redis).to receive(:multi)
139
-
140
- Berater.limit(1, :second, redis: redis) rescue nil
94
+ it 'works with convinience' do
95
+ expect(Berater).to receive(:new).and_return(limiter)
96
+ expect {|b| Berater(:key, :rate, 1, :second, &b) }.to yield_control
141
97
  end
142
98
  end
143
- end
144
-
145
- context 'concurrency mode' do
146
- before { Berater.mode = :concurrency }
147
99
 
148
- describe '.limiter' do
149
- let(:limiter) { Berater.new(:concurrency, 1) }
100
+ context 'concurrency mode' do
101
+ let(:limiter) { Berater.new(:key, :concurrency, 1) }
150
102
 
151
103
  it 'instantiates a ConcurrencyLimiter' do
152
104
  expect(limiter).to be_a Berater::ConcurrencyLimiter
105
+ expect(limiter.key).to be :key
153
106
  end
154
107
 
155
108
  it 'inherits redis' do
@@ -158,33 +111,59 @@ describe Berater do
158
111
 
159
112
  it 'accepts options' do
160
113
  redis = double('Redis')
161
- limiter = Berater.new(:concurrency, 1, key: 'key', redis: redis)
162
- expect(limiter.key).to match(/key/)
114
+ limiter = Berater.new(:key, :concurrency, 1, redis: redis)
163
115
  expect(limiter.redis).to be redis
164
116
  end
117
+
118
+ it 'works with convinience' do
119
+ expect(Berater).to receive(:new).and_return(limiter)
120
+ expect {|b| Berater(:key, :concurrency, 1, &b) }.to yield_control
121
+ end
165
122
  end
166
123
 
167
- describe '.limit' do
168
- it 'works (without blocks by returning a lock)' do
169
- lock = Berater.limit(1)
170
- expect(lock).to be_a Berater::ConcurrencyLimiter::Lock
171
- expect(lock.release).to be true
124
+ context 'with DSL' do
125
+ it 'instatiates an Unlimiter' do
126
+ limiter = Berater.new(:key) { unlimited }
127
+ expect(limiter).to be_a Berater::Unlimiter
128
+ expect(limiter.key).to be :key
172
129
  end
173
130
 
174
- it 'yields' do
175
- expect {|b| Berater.limit(1, &b) }.to yield_control
131
+ it 'instatiates an Inhibiter' do
132
+ limiter = Berater.new(:key) { inhibited }
133
+ expect(limiter).to be_a Berater::Inhibitor
134
+ expect(limiter.key).to be :key
176
135
  end
177
136
 
178
- it 'limits excessive calls' do
179
- Berater.limit(1)
180
- expect { Berater.limit(1) }.to be_incapacitated
137
+ it 'instatiates a RateLimiter' do
138
+ limiter = Berater.new(:key) { 1.per second }
139
+ expect(limiter).to be_a Berater::RateLimiter
140
+ expect(limiter.key).to be :key
141
+ expect(limiter.count).to be 1
142
+ expect(limiter.interval).to be :second
181
143
  end
182
144
 
183
- it 'accepts options' do
184
- redis = double('Redis')
185
- expect(redis).to receive(:eval)
145
+ it 'instatiates a ConcurrencyLimiter' do
146
+ limiter = Berater.new(:key, timeout: 2) { 1.at_once }
147
+ expect(limiter).to be_a Berater::ConcurrencyLimiter
148
+ expect(limiter.key).to be :key
149
+ expect(limiter.capacity).to be 1
150
+ expect(limiter.timeout).to be 2
151
+ end
152
+
153
+ it 'does not accept mode/args and dsl block' do
154
+ expect {
155
+ Berater.new(:key, :rate) { 1.per second }
156
+ }.to raise_error(ArgumentError)
157
+
158
+ expect {
159
+ Berater.new(:key, :concurrency, 2) { 3.at_once }
160
+ }.to raise_error(ArgumentError)
161
+ end
186
162
 
187
- Berater.limit(1, redis: redis) rescue nil
163
+ it 'requires either mode or dsl block' do
164
+ expect {
165
+ Berater.new(:key)
166
+ }.to raise_error(ArgumentError)
188
167
  end
189
168
  end
190
169
  end
@@ -1,22 +1,23 @@
1
1
  describe Berater::ConcurrencyLimiter do
2
- before { Berater.mode = :concurrency }
2
+ it_behaves_like 'a limiter', described_class.new(:key, 1)
3
+ it_behaves_like 'a limiter', described_class.new(:key, 1, timeout: 1)
3
4
 
4
5
  describe '.new' do
5
- let(:limiter) { described_class.new(1) }
6
+ let(:limiter) { described_class.new(:key, 1) }
6
7
 
7
8
  it 'initializes' do
9
+ expect(limiter.key).to be :key
8
10
  expect(limiter.capacity).to be 1
9
11
  end
10
12
 
11
13
  it 'has default values' do
12
- expect(limiter.key).to eq described_class.to_s
13
14
  expect(limiter.redis).to be Berater.redis
14
15
  end
15
16
  end
16
17
 
17
18
  describe '#capacity' do
18
19
  def expect_capacity(capacity)
19
- limiter = described_class.new(capacity)
20
+ limiter = described_class.new(:key, capacity)
20
21
  expect(limiter.capacity).to eq capacity
21
22
  end
22
23
 
@@ -27,7 +28,7 @@ describe Berater::ConcurrencyLimiter do
27
28
  context 'with erroneous values' do
28
29
  def expect_bad_capacity(capacity)
29
30
  expect do
30
- described_class.new(capacity)
31
+ described_class.new(:key, capacity)
31
32
  end.to raise_error ArgumentError
32
33
  end
33
34
 
@@ -40,7 +41,7 @@ describe Berater::ConcurrencyLimiter do
40
41
 
41
42
  describe '#timeout' do
42
43
  def expect_timeout(timeout)
43
- limiter = described_class.new(1, timeout: timeout)
44
+ limiter = described_class.new(:key, 1, timeout: timeout)
44
45
  expect(limiter.timeout).to eq timeout
45
46
  end
46
47
 
@@ -51,7 +52,7 @@ describe Berater::ConcurrencyLimiter do
51
52
  context 'with erroneous values' do
52
53
  def expect_bad_timeout(timeout)
53
54
  expect do
54
- described_class.new(1, timeout: timeout)
55
+ described_class.new(:key, 1, timeout: timeout)
55
56
  end.to raise_error ArgumentError
56
57
  end
57
58
 
@@ -63,126 +64,114 @@ describe Berater::ConcurrencyLimiter do
63
64
  end
64
65
 
65
66
  describe '#limit' do
66
- let(:limiter) { described_class.new(2, timeout: 1) }
67
+ let(:limiter) { described_class.new(:key, 2) }
67
68
 
68
69
  it 'works' do
69
70
  expect {|b| limiter.limit(&b) }.to yield_control
70
71
  end
71
72
 
72
- it 'works many times if workers complete and return locks' do
73
+ it 'works many times if workers release locks' do
73
74
  30.times do
74
75
  expect {|b| limiter.limit(&b) }.to yield_control
75
76
  end
77
+
78
+ 30.times do
79
+ lock = limiter.limit
80
+ lock.release
81
+ end
76
82
  end
77
83
 
78
84
  it 'limits excessive calls' do
79
- expect(limiter.limit).to be_a Berater::ConcurrencyLimiter::Lock
80
- expect(limiter.limit).to be_a Berater::ConcurrencyLimiter::Lock
85
+ expect(limiter.limit).to be_a Berater::Lock
86
+ expect(limiter.limit).to be_a Berater::Lock
81
87
 
82
- expect { limiter }.to be_incapacitated
88
+ expect(limiter).to be_incapacitated
83
89
  end
84
90
 
85
- it 'times out locks' do
86
- expect(limiter.limit).to be_a Berater::ConcurrencyLimiter::Lock
87
- expect(limiter.limit).to be_a Berater::ConcurrencyLimiter::Lock
88
- expect { limiter }.to be_incapacitated
91
+ context 'with capacity 0' do
92
+ let(:limiter) { described_class.new(:key, 0) }
89
93
 
90
- sleep(1)
91
-
92
- expect(limiter.limit).to be_a Berater::ConcurrencyLimiter::Lock
93
- expect(limiter.limit).to be_a Berater::ConcurrencyLimiter::Lock
94
- expect { limiter }.to be_incapacitated
94
+ it 'always fails' do
95
+ expect(limiter).to be_incapacitated
96
+ end
95
97
  end
96
98
  end
97
99
 
98
100
  context 'with same key, different limiters' do
99
- let(:limiter_one) { described_class.new(1) }
100
- let(:limiter_two) { described_class.new(1) }
101
+ let(:limiter_one) { described_class.new(:key, 1) }
102
+ let(:limiter_two) { described_class.new(:key, 1) }
101
103
 
102
104
  it { expect(limiter_one.key).to eq limiter_two.key }
103
105
 
104
106
  it 'works as expected' do
105
- expect(limiter_one.limit).to be_a Berater::ConcurrencyLimiter::Lock
107
+ expect(limiter_one.limit).to be_a Berater::Lock
106
108
 
107
- expect { limiter_one }.to be_incapacitated
108
- expect { limiter_two }.to be_incapacitated
109
- end
110
- end
111
-
112
- context 'with different keys, same limiter' do
113
- let(:limiter) { described_class.new(1) }
114
-
115
- it 'works as expected' do
116
- one_lock = limiter.limit(key: :one)
117
- expect(one_lock).to be_a Berater::ConcurrencyLimiter::Lock
118
-
119
- expect { limiter.limit(key: :one) {} }.to be_incapacitated
120
- expect { limiter.limit(key: :two) {} }.not_to be_incapacitated
121
-
122
- two_lock = limiter.limit(key: :two)
123
- expect(two_lock).to be_a Berater::ConcurrencyLimiter::Lock
124
-
125
- expect { limiter.limit(key: :one) {} }.to be_incapacitated
126
- expect { limiter.limit(key: :two) {} }.to be_incapacitated
127
-
128
- one_lock.release
129
- expect { limiter.limit(key: :one) {} }.not_to be_incapacitated
130
- expect { limiter.limit(key: :two) {} }.to be_incapacitated
131
-
132
- two_lock.release
133
- expect { limiter.limit(key: :one) {} }.not_to be_incapacitated
134
- expect { limiter.limit(key: :two) {} }.not_to be_incapacitated
109
+ expect(limiter_one).to be_incapacitated
110
+ expect(limiter_two).to be_incapacitated
135
111
  end
136
112
  end
137
113
 
138
114
  context 'with same key, different capacities' do
139
- let(:limiter_one) { described_class.new(1) }
140
- let(:limiter_two) { described_class.new(2) }
115
+ let(:limiter_one) { described_class.new(:key, 1) }
116
+ let(:limiter_two) { described_class.new(:key, 2) }
141
117
 
142
118
  it { expect(limiter_one.capacity).not_to eq limiter_two.capacity }
143
119
 
144
120
  it 'works as expected' do
145
121
  one_lock = limiter_one.limit
146
- expect(one_lock).to be_a Berater::ConcurrencyLimiter::Lock
122
+ expect(one_lock).to be_a Berater::Lock
147
123
 
148
- expect { limiter_one }.to be_incapacitated
149
- expect { limiter_two }.not_to be_incapacitated
124
+ expect(limiter_one).to be_incapacitated
125
+ expect(limiter_two).not_to be_incapacitated
150
126
 
151
127
  two_lock = limiter_two.limit
152
- expect(two_lock).to be_a Berater::ConcurrencyLimiter::Lock
128
+ expect(two_lock).to be_a Berater::Lock
153
129
 
154
- expect { limiter_one }.to be_incapacitated
155
- expect { limiter_two }.to be_incapacitated
130
+ expect(limiter_one).to be_incapacitated
131
+ expect(limiter_two).to be_incapacitated
156
132
 
157
133
  one_lock.release
158
- expect { limiter_one }.to be_incapacitated
159
- expect { limiter_two }.not_to be_incapacitated
134
+ expect(limiter_one).to be_incapacitated
135
+ expect(limiter_two).not_to be_incapacitated
160
136
 
161
137
  two_lock.release
162
- expect { limiter_one }.not_to be_incapacitated
163
- expect { limiter_two }.not_to be_incapacitated
138
+ expect(limiter_one).not_to be_incapacitated
139
+ expect(limiter_two).not_to be_incapacitated
164
140
  end
165
141
  end
166
142
 
167
143
  context 'with different keys, different limiters' do
168
- let(:limiter_one) { described_class.new(1, key: :one) }
169
- let(:limiter_two) { described_class.new(1, key: :two) }
144
+ let(:limiter_one) { described_class.new(:one, 1) }
145
+ let(:limiter_two) { described_class.new(:two, 1) }
170
146
 
171
147
  it 'works as expected' do
172
- expect { limiter_one }.not_to be_incapacitated
173
- expect { limiter_two }.not_to be_incapacitated
148
+ expect(limiter_one).not_to be_incapacitated
149
+ expect(limiter_two).not_to be_incapacitated
174
150
 
175
151
  one_lock = limiter_one.limit
176
- expect(one_lock).to be_a Berater::ConcurrencyLimiter::Lock
152
+ expect(one_lock).to be_a Berater::Lock
177
153
 
178
- expect { limiter_one }.to be_incapacitated
179
- expect { limiter_two }.not_to be_incapacitated
154
+ expect(limiter_one).to be_incapacitated
155
+ expect(limiter_two).not_to be_incapacitated
180
156
 
181
157
  two_lock = limiter_two.limit
182
- expect(two_lock).to be_a Berater::ConcurrencyLimiter::Lock
158
+ expect(two_lock).to be_a Berater::Lock
183
159
 
184
- expect { limiter_one }.to be_incapacitated
185
- expect { limiter_two }.to be_incapacitated
160
+ expect(limiter_one).to be_incapacitated
161
+ expect(limiter_two).to be_incapacitated
162
+ end
163
+ end
164
+
165
+ describe '#to_s' do
166
+ def check(capacity, expected)
167
+ expect(
168
+ described_class.new(:key, capacity).to_s
169
+ ).to match(expected)
170
+ end
171
+
172
+ it 'works' do
173
+ check(1, /1 at a time/)
174
+ check(3, /3 at a time/)
186
175
  end
187
176
  end
188
177