berater 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dec8c5d6d428f795d1489dace166451e2a3a42592d00d71155ed5cd0d5aaa909
4
- data.tar.gz: 6956b6c2804b6d616074e239439d4f2a8e551e900db7801226948b9eb764d0fc
3
+ metadata.gz: d007dbb664fa57c921047a28dd1e5a591cf5c51fba4931dbc367ab552e4b8a8d
4
+ data.tar.gz: c710d31a2d8cfdc50af4c2fae87cabad177096d13da52a7f9c48b61aa31b6038
5
5
  SHA512:
6
- metadata.gz: 327cd2f3a7430c2b81bd1988abd636bfc7d97048c762d3807555e9c1196153570dbe3045fae813852a907ec00b89b8d2c36e9269ed0562419c8d98dd6c1d2b26
7
- data.tar.gz: ea1cb39e9201091e4a1357e888ac36f630c326a271e3cfcafa13298b8f54424f776f4dd488e046e2178905d0df9fb5c3b1f0d454e331823cafe5dd338fe9ea6c
6
+ metadata.gz: 0d9c211bb65e7341b98e637d3f0ce0e31f0a879437c1c309fb01e1efab5e5c6b2d5044158931ab16a67cbbdebd00ddb5f10afc97430230b153d8cc236f7517ab
7
+ data.tar.gz: 5d8b38bfd7683df641fd6891cd1a493079453ea6c61b2880581eeb2d963fb171365fb3448443e2548b34297d8dcfa352e67cff5688cd24d25422efb5e55d4afd
data/lib/berater.rb CHANGED
@@ -15,7 +15,23 @@ module Berater
15
15
  yield self
16
16
  end
17
17
 
18
- def new(key, mode, *args, **opts)
18
+ def new(key, mode = nil, *args, **opts, &block)
19
+ if mode.nil?
20
+ unless args.empty?
21
+ raise ArgumentError, '0 arguments expected with block'
22
+ end
23
+
24
+ unless block_given?
25
+ raise ArgumentError, 'expected either mode or block'
26
+ end
27
+
28
+ mode, *args = DSL.eval(&block)
29
+ else
30
+ if block_given?
31
+ raise ArgumentError, 'expected either mode or block, not both'
32
+ end
33
+ end
34
+
19
35
  klass = MODES[mode.to_sym]
20
36
 
21
37
  unless klass
@@ -42,8 +58,8 @@ def Berater(key, mode, *args, **opts, &block)
42
58
  Berater.new(key, mode, *args, **opts).limit(&block)
43
59
  end
44
60
 
45
- # load and register limiters
46
- require 'berater/base_limiter'
61
+ # load limiters
62
+ require 'berater/limiter'
47
63
  require 'berater/concurrency_limiter'
48
64
  require 'berater/inhibitor'
49
65
  require 'berater/rate_limiter'
@@ -53,3 +69,5 @@ Berater.register(:concurrency, Berater::ConcurrencyLimiter)
53
69
  Berater.register(:inhibited, Berater::Inhibitor)
54
70
  Berater.register(:rate, Berater::RateLimiter)
55
71
  Berater.register(:unlimited, Berater::Unlimiter)
72
+
73
+ require 'berater/dsl'
@@ -1,5 +1,5 @@
1
1
  module Berater
2
- class ConcurrencyLimiter < BaseLimiter
2
+ class ConcurrencyLimiter < Limiter
3
3
 
4
4
  class Incapacitated < Overloaded; end
5
5
 
@@ -85,5 +85,9 @@ module Berater
85
85
  res == true || res == 1 # depending on which version of Redis
86
86
  end
87
87
 
88
+ def to_s
89
+ "#<#{self.class}(#{key}: #{capacity} at a time)>"
90
+ end
91
+
88
92
  end
89
93
  end
@@ -0,0 +1,57 @@
1
+ module Berater
2
+ module DSL
3
+ extend self
4
+
5
+ def eval &block
6
+ @keywords ||= Class.new do
7
+ # create a class where DSL keywords are methods
8
+ KEYWORDS.each do |keyword|
9
+ define_singleton_method(keyword) { keyword }
10
+ end
11
+ end
12
+
13
+ install
14
+ @keywords.class_eval &block
15
+ ensure
16
+ uninstall
17
+ end
18
+
19
+ private
20
+
21
+ def each &block
22
+ Berater::MODES.map do |mode, limiter|
23
+ next unless limiter.const_defined?(:DSL, false)
24
+ limiter.const_get(:DSL)
25
+ end.compact.each(&block)
26
+ end
27
+
28
+ KEYWORDS = [
29
+ :second, :minute, :hour,
30
+ :unlimited, :inhibited,
31
+ ].freeze
32
+
33
+ def install
34
+ Integer.class_eval do
35
+ def per(unit)
36
+ [ :rate, self, unit ]
37
+ end
38
+ alias every per
39
+
40
+ def at_once
41
+ [ :concurrency, self ]
42
+ end
43
+ alias concurrently at_once
44
+ alias at_a_time at_once
45
+ end
46
+ end
47
+
48
+ def uninstall
49
+ Integer.remove_method :per
50
+ Integer.remove_method :every
51
+
52
+ Integer.remove_method :at_once
53
+ Integer.remove_method :concurrently
54
+ Integer.remove_method :at_a_time
55
+ end
56
+ end
57
+ end
@@ -1,5 +1,5 @@
1
1
  module Berater
2
- class Inhibitor < BaseLimiter
2
+ class Inhibitor < Limiter
3
3
 
4
4
  class Inhibited < Overloaded; end
5
5
 
@@ -1,5 +1,5 @@
1
1
  module Berater
2
- class BaseLimiter
2
+ class Limiter
3
3
 
4
4
  attr_reader :key, :options
5
5
 
@@ -11,6 +11,10 @@ module Berater
11
11
  raise NotImplementedError
12
12
  end
13
13
 
14
+ def to_s
15
+ "#<#{self.class}>"
16
+ end
17
+
14
18
  protected
15
19
 
16
20
  def initialize(key, **opts)
data/lib/berater/lock.rb CHANGED
@@ -17,7 +17,7 @@ module Berater
17
17
  end
18
18
 
19
19
  def expired?
20
- timeout > 0 && @locked_at + timeout < Time.now
20
+ timeout ? @locked_at + timeout < Time.now : false
21
21
  end
22
22
 
23
23
  def release
@@ -28,8 +28,8 @@ module Berater
28
28
  @release_fn ? @release_fn.call : true
29
29
  end
30
30
 
31
- private def timeout
32
- limiter.respond_to?(:timeout) ? limiter.timeout : 0
31
+ def timeout
32
+ limiter.respond_to?(:timeout) ? limiter.timeout : nil
33
33
  end
34
34
 
35
35
  end
@@ -1,5 +1,5 @@
1
1
  module Berater
2
- class RateLimiter < BaseLimiter
2
+ class RateLimiter < Limiter
3
3
 
4
4
  class Overrated < Overloaded; end
5
5
 
@@ -28,6 +28,7 @@ module Berater
28
28
  case @interval
29
29
  when Integer
30
30
  raise ArgumentError, "interval must be >= 0" unless @interval >= 0
31
+ @interval_sec = @interval
31
32
  when String
32
33
  @interval = @interval.to_sym
33
34
  when Symbol
@@ -38,28 +39,29 @@ module Berater
38
39
  if @interval.is_a? Symbol
39
40
  case @interval
40
41
  when :sec, :second, :seconds
41
- @interval = 1
42
+ @interval = :second
43
+ @interval_sec = 1
42
44
  when :min, :minute, :minutes
43
- @interval = 60
45
+ @interval = :minute
46
+ @interval_sec = 60
44
47
  when :hour, :hours
45
- @interval = 60 * 60
48
+ @interval = :hour
49
+ @interval_sec = 60 * 60
46
50
  else
47
51
  raise ArgumentError, "unexpected interval value: #{interval}"
48
52
  end
49
53
  end
50
-
51
- @interval
52
54
  end
53
55
 
54
56
  def limit
55
57
  ts = Time.now.to_i
56
58
 
57
59
  # bucket into time slot
58
- rkey = "%s:%d" % [ cache_key(key), ts - ts % @interval ]
60
+ rkey = "%s:%d" % [ cache_key(key), ts - ts % @interval_sec ]
59
61
 
60
62
  count, _ = redis.multi do
61
63
  redis.incr rkey
62
- redis.expire rkey, @interval * 2
64
+ redis.expire rkey, @interval_sec * 2
63
65
  end
64
66
 
65
67
  raise Overrated if count > @count
@@ -77,6 +79,20 @@ module Berater
77
79
  end
78
80
  end
79
81
 
82
+ def to_s
83
+ msg = if @interval.is_a? Integer
84
+ if @interval == 1
85
+ "every second"
86
+ else
87
+ "every #{@interval} seconds"
88
+ end
89
+ else
90
+ "per #{@interval}"
91
+ end
92
+
93
+ "#<#{self.class}(#{key}: #{count} #{msg})>"
94
+ end
95
+
80
96
  end
81
97
  end
82
98
 
@@ -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,13 +1,12 @@
1
1
  module Berater
2
- class Unlimiter < BaseLimiter
2
+ class Unlimiter < Limiter
3
3
 
4
4
  def initialize(key = :unlimiter, *args, **opts)
5
5
  super(key, **opts)
6
6
  end
7
7
 
8
8
  def limit
9
- count = redis.incr(cache_key('count'))
10
- lock = Lock.new(self, count, count)
9
+ lock = Lock.new(self, 0, 0)
11
10
 
12
11
  if block_given?
13
12
  begin
@@ -1,3 +1,3 @@
1
1
  module Berater
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
data/spec/berater_spec.rb CHANGED
@@ -120,6 +120,52 @@ describe Berater do
120
120
  expect {|b| Berater(:key, :concurrency, 1, &b) }.to yield_control
121
121
  end
122
122
  end
123
+
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
129
+ end
130
+
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
135
+ end
136
+
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
143
+ end
144
+
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
162
+
163
+ it 'requires either mode or dsl block' do
164
+ expect {
165
+ Berater.new(:key)
166
+ }.to raise_error(ArgumentError)
167
+ end
168
+ end
123
169
  end
124
170
 
125
171
  end
@@ -1,4 +1,7 @@
1
1
  describe Berater::ConcurrencyLimiter do
2
+ it_behaves_like 'a limiter', described_class.new(:key, 1)
3
+ it_behaves_like 'a limiter', described_class.new(:key, 1, timeout: 1)
4
+
2
5
  describe '.new' do
3
6
  let(:limiter) { described_class.new(:key, 1) }
4
7
 
@@ -61,7 +64,7 @@ describe Berater::ConcurrencyLimiter do
61
64
  end
62
65
 
63
66
  describe '#limit' do
64
- let(:limiter) { described_class.new(:key, 2, timeout: 1) }
67
+ let(:limiter) { described_class.new(:key, 2) }
65
68
 
66
69
  it 'works' do
67
70
  expect {|b| limiter.limit(&b) }.to yield_control
@@ -85,18 +88,6 @@ describe Berater::ConcurrencyLimiter do
85
88
  expect(limiter).to be_incapacitated
86
89
  end
87
90
 
88
- it 'times out locks' do
89
- expect(limiter.limit).to be_a Berater::Lock
90
- expect(limiter.limit).to be_a Berater::Lock
91
- expect(limiter).to be_incapacitated
92
-
93
- Timecop.travel(1)
94
-
95
- expect(limiter.limit).to be_a Berater::Lock
96
- expect(limiter.limit).to be_a Berater::Lock
97
- expect(limiter).to be_incapacitated
98
- end
99
-
100
91
  context 'with capacity 0' do
101
92
  let(:limiter) { described_class.new(:key, 0) }
102
93
 
@@ -171,4 +162,17 @@ describe Berater::ConcurrencyLimiter do
171
162
  end
172
163
  end
173
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/)
175
+ end
176
+ end
177
+
174
178
  end
File without changes
@@ -1,4 +1,5 @@
1
1
  describe Berater::RateLimiter do
2
+ it_behaves_like 'a limiter', Berater.new(:key, :rate, 3, :second)
2
3
 
3
4
  describe '.new' do
4
5
  let(:limiter) { described_class.new(:key, 1, :second) }
@@ -6,7 +7,7 @@ describe Berater::RateLimiter do
6
7
  it 'initializes' do
7
8
  expect(limiter.key).to be :key
8
9
  expect(limiter.count).to eq 1
9
- expect(limiter.interval).to eq 1
10
+ expect(limiter.interval).to eq :second
10
11
  end
11
12
 
12
13
  it 'has default values' do
@@ -51,22 +52,22 @@ describe Berater::RateLimiter do
51
52
  end
52
53
 
53
54
  context 'with symbols' do
54
- it { expect_interval(:sec, 1) }
55
- it { expect_interval(:second, 1) }
56
- it { expect_interval(:seconds, 1) }
55
+ it { expect_interval(:sec, :second) }
56
+ it { expect_interval(:second, :second) }
57
+ it { expect_interval(:seconds, :second) }
57
58
 
58
- it { expect_interval(:min, 60) }
59
- it { expect_interval(:minute, 60) }
60
- it { expect_interval(:minutes, 60) }
59
+ it { expect_interval(:min, :minute) }
60
+ it { expect_interval(:minute, :minute) }
61
+ it { expect_interval(:minutes, :minute) }
61
62
 
62
- it { expect_interval(:hour, 3600) }
63
- it { expect_interval(:hours, 3600) }
63
+ it { expect_interval(:hour, :hour) }
64
+ it { expect_interval(:hours, :hour) }
64
65
  end
65
66
 
66
67
  context 'with strings' do
67
- it { expect_interval('sec', 1) }
68
- it { expect_interval('minute', 60) }
69
- it { expect_interval('hours', 3600) }
68
+ it { expect_interval('sec', :second) }
69
+ it { expect_interval('minute', :minute) }
70
+ it { expect_interval('hours', :hour) }
70
71
  end
71
72
 
72
73
  context 'with erroneous values' do
@@ -80,6 +81,17 @@ describe Berater::RateLimiter do
80
81
  it { expect_bad_interval(:secondz) }
81
82
  it { expect_bad_interval('huor') }
82
83
  end
84
+
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
90
+
91
+ it { expect_sec(:second, 1) }
92
+ it { expect_sec(:minute, 60) }
93
+ it { expect_sec(:hour, 3600) }
94
+ end
83
95
  end
84
96
 
85
97
  describe '#limit' do
@@ -140,4 +152,40 @@ describe Berater::RateLimiter do
140
152
  end
141
153
  end
142
154
 
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
161
+
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
173
+
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
+ 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/)
188
+ end
189
+ end
190
+
143
191
  end
@@ -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
@@ -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
@@ -31,7 +33,4 @@ describe Berater::Unlimiter do
31
33
  end
32
34
  end
33
35
  end
34
-
35
- it_behaves_like 'a lock', described_class.new
36
-
37
36
  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.2.0
4
+ version: 0.3.0
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-02-16 00:00:00.000000000 Z
11
+ date: 2021-02-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -115,20 +115,23 @@ extensions: []
115
115
  extra_rdoc_files: []
116
116
  files:
117
117
  - lib/berater.rb
118
- - lib/berater/base_limiter.rb
119
118
  - lib/berater/concurrency_limiter.rb
119
+ - lib/berater/dsl.rb
120
120
  - lib/berater/inhibitor.rb
121
+ - lib/berater/limiter.rb
121
122
  - lib/berater/lock.rb
122
123
  - lib/berater/rate_limiter.rb
124
+ - lib/berater/rspec.rb
125
+ - lib/berater/rspec/matchers.rb
126
+ - lib/berater/test_mode.rb
123
127
  - lib/berater/unlimiter.rb
124
128
  - lib/berater/version.rb
125
129
  - spec/berater_spec.rb
126
130
  - spec/concurrency_limiter_spec.rb
127
- - spec/concurrency_lock_spec.rb
128
131
  - spec/inhibitor_spec.rb
129
- - spec/matcher_spec.rb
132
+ - spec/matchers_spec.rb
130
133
  - spec/rate_limiter_spec.rb
131
- - spec/rate_lock_spec.rb
134
+ - spec/test_mode_spec.rb
132
135
  - spec/unlimiter_spec.rb
133
136
  homepage: https://github.com/dpep/berater_rb
134
137
  licenses:
@@ -155,10 +158,9 @@ specification_version: 4
155
158
  summary: Berater
156
159
  test_files:
157
160
  - spec/rate_limiter_spec.rb
158
- - spec/matcher_spec.rb
159
- - spec/rate_lock_spec.rb
161
+ - spec/matchers_spec.rb
162
+ - spec/test_mode_spec.rb
160
163
  - spec/concurrency_limiter_spec.rb
161
- - spec/concurrency_lock_spec.rb
162
164
  - spec/berater_spec.rb
163
165
  - spec/inhibitor_spec.rb
164
166
  - spec/unlimiter_spec.rb
@@ -1,39 +0,0 @@
1
- describe Berater::Lock do
2
- it_behaves_like 'a lock', Berater.new(:key, :concurrency, 3)
3
-
4
- let(:limiter) { Berater.new(:key, :concurrency, 3) }
5
-
6
- describe '#expired?' do
7
- let!(:lock) { limiter.limit }
8
-
9
- context 'when timeout is not set' do
10
- it { expect(limiter.timeout).to eq 0 }
11
-
12
- it 'never expires' do
13
- expect(lock.locked?).to be true
14
- expect(lock.expired?).to be false
15
-
16
- Timecop.travel(1_000)
17
-
18
- expect(lock.locked?).to be true
19
- expect(lock.expired?).to be false
20
- end
21
- end
22
-
23
- context 'when timeout is set and exceeded' do
24
- before { Timecop.travel(1) }
25
-
26
- let(:limiter) { Berater.new(:key, :concurrency, 3, timeout: 1) }
27
-
28
- it 'expires' do
29
- expect(lock.expired?).to be true
30
- expect(lock.locked?).to be false
31
- end
32
-
33
- it 'fails to release' do
34
- expect { lock.release }.to raise_error(RuntimeError, /expired/)
35
- end
36
- end
37
- end
38
-
39
- end
@@ -1,20 +0,0 @@
1
- describe Berater::Lock do
2
- it_behaves_like 'a lock', Berater.new(:key, :rate, 3, :second)
3
-
4
- let(:limiter) { Berater.new(:key, :rate, 3, :second) }
5
-
6
- describe '#expired?' do
7
- let!(:lock) { limiter.limit }
8
-
9
- it 'never expires' do
10
- expect(lock.locked?).to be true
11
- expect(lock.expired?).to be false
12
-
13
- Timecop.travel(1_000)
14
-
15
- expect(lock.locked?).to be true
16
- expect(lock.expired?).to be false
17
- end
18
- end
19
-
20
- end