berater 0.6.0 → 0.6.1

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: 87d3bdb61a68a8b69dd7cb285dafb174a403acf19ff7586f90ed8b7cb50b7f76
4
- data.tar.gz: fff71dd4d8ae28baf7c504efdc27df71f03219b32c1a959dddfd3d035845d058
3
+ metadata.gz: 5c5cc50ba02c18bf9ef9f6b9c1dd6ccb8f8af0297df95757ac13406dc4539c40
4
+ data.tar.gz: f80341dccff30ca3f344f3bdf21de44b1ef1715bfaf4140933a91252ddb7231e
5
5
  SHA512:
6
- metadata.gz: c9fb5c0c7ec100d2f50a3b6c6cfa449af052c1b78cff174b545e179c5fcc1324c2a5faafc90e7f7ec64ce200e47571d51b02eb56398d716c487abc7731c0cece
7
- data.tar.gz: 148c566413d6593f229143e2871116b45947eae5b101d8bb2f59ce3bdfe89ed4ee1b7aaedfcaedbc80eef8a25a14fab83ad5e540172c41f31f27d7f296161235
6
+ metadata.gz: ada3fffc841e71498f652dc990eed482bccc942b86280fc5ee736d49fdf99d8ab2f921b5b52d96f3588c268c34343669c00e0be8822c462a3e6f9dcf954a4005
7
+ data.tar.gz: 0363abef69cfdad8140d89373b5088cfa8a6259f16716011c82ceb1e2e9e948cdb4d44ddd36aaf1d05b121dc7d275d0ee34571a0a3ee9b964f4286375ec356cf
@@ -63,15 +63,10 @@ module Berater
63
63
  LUA
64
64
  )
65
65
 
66
- def limit(capacity: nil, cost: 1, &block)
67
- capacity ||= @capacity
68
-
69
- # since fractional cost is not supported, capacity behaves like int
66
+ protected def acquire_lock(capacity, cost)
67
+ # fractional cost is not supported, but make it work
70
68
  capacity = capacity.to_i
71
-
72
- unless cost.is_a?(Integer) && cost >= 0
73
- raise ArgumentError, "invalid cost: #{cost}"
74
- end
69
+ cost = cost.ceil
75
70
 
76
71
  # timestamp in milliseconds
77
72
  ts = (Time.now.to_f * 10**3).to_i
@@ -87,16 +82,10 @@ module Berater
87
82
  release_fn = if cost > 0
88
83
  proc { release(lock_ids) }
89
84
  end
90
- lock = Lock.new(capacity, count, release_fn)
91
85
 
92
- yield_lock(lock, &block)
86
+ Lock.new(capacity, count, release_fn)
93
87
  end
94
88
 
95
- def overloaded?
96
- limit(cost: 0) { false }
97
- rescue Overloaded
98
- true
99
- end
100
89
  alias incapacitated? overloaded?
101
90
 
102
91
  private def release(lock_ids)
@@ -7,14 +7,11 @@ module Berater
7
7
  super(key, 0, **opts)
8
8
  end
9
9
 
10
- def limit(**opts)
11
- raise Inhibited
12
- end
10
+ alias inhibited? overloaded?
13
11
 
14
- def overloaded?
15
- true
12
+ protected def acquire_lock(*)
13
+ raise Inhibited
16
14
  end
17
- alias inhibited? overloaded?
18
15
 
19
16
  end
20
17
  end
@@ -7,12 +7,34 @@ module Berater
7
7
  options[:redis] || Berater.redis
8
8
  end
9
9
 
10
- def limit
11
- raise NotImplementedError
10
+ def limit(capacity: nil, cost: 1, &block)
11
+ capacity ||= @capacity
12
+
13
+ unless capacity.is_a?(Numeric)
14
+ raise ArgumentError, "invalid capacity: #{capacity}"
15
+ end
16
+
17
+ unless cost.is_a?(Numeric) && cost >= 0
18
+ raise ArgumentError, "invalid cost: #{cost}"
19
+ end
20
+
21
+ lock = acquire_lock(capacity, cost)
22
+
23
+ if block_given?
24
+ begin
25
+ yield lock
26
+ ensure
27
+ lock.release
28
+ end
29
+ else
30
+ lock
31
+ end
12
32
  end
13
33
 
14
34
  def overloaded?
15
- raise NotImplementedError
35
+ limit(cost: 0) { false }
36
+ rescue Overloaded
37
+ true
16
38
  end
17
39
 
18
40
  def to_s
@@ -60,20 +82,12 @@ module Berater
60
82
  @capacity = capacity
61
83
  end
62
84
 
63
- def cache_key(key)
64
- "#{self.class}:#{key}"
85
+ def acquire_lock(capacity, cost)
86
+ raise NotImplementedError
65
87
  end
66
88
 
67
- def yield_lock(lock, &block)
68
- if block_given?
69
- begin
70
- yield lock
71
- ensure
72
- lock.release
73
- end
74
- else
75
- lock
76
- end
89
+ def cache_key(key)
90
+ "#{self.class}:#{key}"
77
91
  end
78
92
 
79
93
  end
@@ -63,9 +63,7 @@ module Berater
63
63
  LUA
64
64
  )
65
65
 
66
- def limit(capacity: nil, cost: 1, &block)
67
- capacity ||= @capacity
68
-
66
+ protected def acquire_lock(capacity, cost)
69
67
  # timestamp in milliseconds
70
68
  ts = (Time.now.to_f * 10**3).to_i
71
69
 
@@ -77,15 +75,9 @@ module Berater
77
75
 
78
76
  raise Overrated unless allowed
79
77
 
80
- lock = Lock.new(capacity, count)
81
- yield_lock(lock, &block)
78
+ Lock.new(capacity, count)
82
79
  end
83
80
 
84
- def overloaded?
85
- limit(cost: 0) { false }
86
- rescue Overrated
87
- true
88
- end
89
81
  alias overrated? overloaded?
90
82
 
91
83
  def to_s
@@ -10,29 +10,27 @@ module Berater
10
10
  end
11
11
 
12
12
  def matches?(obj)
13
- begin
14
- case obj
15
- when Proc
16
- # eg. expect { ... }.to be_overrated
17
- res = obj.call
13
+ case obj
14
+ when Proc
15
+ # eg. expect { ... }.to be_overrated
16
+ res = obj.call
18
17
 
19
- if res.is_a? Berater::Limiter
20
- # eg. expect { Berater.new(...) }.to be_overloaded
21
- @limiter = res
22
- res.overloaded?
23
- else
24
- # eg. expect { Berater(...) }.to be_overloaded
25
- # eg. expect { limiter.limit }.to be_overloaded
26
- false
27
- end
28
- when Berater::Limiter
29
- # eg. expect(Berater.new(...)).to be_overloaded
30
- @limiter = obj
31
- obj.overloaded?
18
+ if res.is_a? Berater::Limiter
19
+ # eg. expect { Berater.new(...) }.to be_overloaded
20
+ @limiter = res
21
+ res.overloaded?
22
+ else
23
+ # eg. expect { Berater(...) }.to be_overloaded
24
+ # eg. expect { limiter.limit }.to be_overloaded
25
+ false
32
26
  end
33
- rescue @type
34
- true
27
+ when Berater::Limiter
28
+ # eg. expect(Berater.new(...)).to be_overloaded
29
+ @limiter = obj
30
+ obj.overloaded?
35
31
  end
32
+ rescue @type
33
+ true
36
34
  end
37
35
 
38
36
  def description
@@ -11,42 +11,33 @@ module Berater
11
11
  end
12
12
 
13
13
  @test_mode = mode
14
-
15
- # overload class methods
16
- unless Berater::Limiter.singleton_class.ancestors.include?(TestMode)
17
- Berater::Limiter.singleton_class.prepend(TestMode)
18
- end
19
14
  end
20
15
 
21
16
  module TestMode
22
- def new(*args, **opts)
23
- return super unless Berater.test_mode
24
-
25
- # chose a stub class with desired behavior
26
- stub_klass = case Berater.test_mode
17
+ def acquire_lock(*)
18
+ case Berater.test_mode
27
19
  when :pass
28
- Berater::Unlimiter
20
+ Lock.new(Float::INFINITY, 0)
29
21
  when :fail
30
- Berater::Inhibitor
31
- end
32
-
33
- # don't stub self
34
- return super if self < stub_klass
35
-
36
- # swap out limit and overloaded? methods with stub
37
- super.tap do |instance|
38
- stub = stub_klass.allocate
39
- stub.send(:initialize, *args, **opts)
40
-
41
- instance.define_singleton_method(:limit) do |**opts, &block|
42
- stub.limit(**opts, &block)
43
- end
44
-
45
- instance.define_singleton_method(:overloaded?) do
46
- stub.overloaded?
47
- end
22
+ # find class specific Overloaded error
23
+ e = self.class.constants.map do |name|
24
+ self.class.const_get(name)
25
+ end.find do |const|
26
+ const < Berater::Overloaded
27
+ end || Berater::Overloaded
28
+
29
+ raise e
30
+ else
31
+ super
48
32
  end
49
33
  end
50
34
  end
51
35
 
52
36
  end
37
+
38
+ # stub each Limiter subclass
39
+ ObjectSpace.each_object(Class).each do |klass|
40
+ next unless klass < Berater::Limiter
41
+
42
+ klass.prepend(Berater::TestMode)
43
+ end
@@ -5,16 +5,14 @@ module Berater
5
5
  super(key, Float::INFINITY, **opts)
6
6
  end
7
7
 
8
- def limit(**opts, &block)
9
- yield_lock(Lock.new(Float::INFINITY, 0), &block)
10
- end
8
+ protected
11
9
 
12
- def overloaded?
13
- false
10
+ def capacity=(*)
11
+ @capacity = Float::INFINITY
14
12
  end
15
13
 
16
- protected def capacity=(*)
17
- @capacity = Float::INFINITY
14
+ def acquire_lock(*)
15
+ Lock.new(Float::INFINITY, 0)
18
16
  end
19
17
 
20
18
  end
@@ -1,3 +1,3 @@
1
1
  module Berater
2
- VERSION = '0.6.0'
2
+ VERSION = '0.6.1'
3
3
  end
@@ -124,6 +124,12 @@ describe Berater::ConcurrencyLimiter do
124
124
  expect(limiter).to be_incapacitated
125
125
  end
126
126
 
127
+ it 'accepts a dynamic capacity' do
128
+ expect { limiter.limit(capacity: 0) }.to be_incapacitated
129
+ 5.times { limiter.limit(capacity: 10) }
130
+ expect { limiter }.to be_incapacitated
131
+ end
132
+
127
133
  context 'with cost parameter' do
128
134
  it { expect { limiter.limit(cost: 4) }.to be_incapacitated }
129
135
 
@@ -154,17 +160,21 @@ describe Berater::ConcurrencyLimiter do
154
160
  expect(limiter).to be_incapacitated
155
161
  end
156
162
 
157
- it 'accepts a dynamic capacity' do
158
- limiter = described_class.new(:key, 1)
159
-
160
- expect { limiter.limit(capacity: 0) }.to be_incapacitated
161
- 5.times { limiter.limit(capacity: 10) }
162
- expect { limiter }.to be_incapacitated
163
+ context 'with fractional costs' do
164
+ it 'rounds up' do
165
+ limiter.limit(cost: 1.5)
166
+ expect(limiter).to be_incapacitated
167
+ end
168
+
169
+ it 'accumulates correctly' do
170
+ limiter.limit(cost: 0.5) # => 1
171
+ limiter.limit(cost: 0.7) # => 2
172
+ expect(limiter).to be_incapacitated
173
+ end
163
174
  end
164
175
 
165
- it 'only allows positive, Integer values' do
176
+ it 'only allows positive values' do
166
177
  expect { limiter.limit(cost: -1) }.to raise_error(ArgumentError)
167
- expect { limiter.limit(cost: 1.5) }.to raise_error(ArgumentError)
168
178
  end
169
179
  end
170
180
 
@@ -1,31 +1,20 @@
1
- describe Berater::TestMode, order: :defined do
2
- let(:reset_test_mode) { true }
3
-
1
+ describe Berater::TestMode do
4
2
  after do
5
- Berater.test_mode = nil if reset_test_mode
3
+ Berater.test_mode = nil
6
4
  end
7
5
 
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
6
+ context 'after test_mode.rb has been loaded' do
7
+ it 'monkey patches Berater' do
8
+ expect(Berater).to respond_to(:test_mode)
15
9
  end
16
10
 
17
11
  it 'defaults to off' do
18
12
  expect(Berater.test_mode).to be nil
19
13
  end
20
14
 
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)
15
+ it 'prepends Limiter subclasses' do
16
+ expect(Berater::Unlimiter.ancestors).to include(described_class)
17
+ expect(Berater::Inhibitor.ancestors).to include(described_class)
29
18
  end
30
19
 
31
20
  it 'preserves the original functionality via super' do
@@ -50,6 +39,20 @@ describe Berater::TestMode, order: :defined do
50
39
  it 'validates input' do
51
40
  expect { Berater.test_mode = :foo }.to raise_error(ArgumentError)
52
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
53
56
  end
54
57
 
55
58
  shared_examples 'it always works, without redis' do
@@ -72,10 +75,6 @@ describe Berater::TestMode, order: :defined do
72
75
  end
73
76
 
74
77
  it_behaves_like 'it is overloaded'
75
-
76
- it 'never works' do
77
- expect { subject }.to be_overloaded
78
- end
79
78
  end
80
79
 
81
80
  describe 'Unlimiter' do
@@ -100,6 +99,11 @@ describe Berater::TestMode, order: :defined do
100
99
 
101
100
  it { is_expected.to be_a Berater::Unlimiter }
102
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
103
107
  end
104
108
  end
105
109
 
@@ -125,6 +129,11 @@ describe Berater::TestMode, order: :defined do
125
129
 
126
130
  it { is_expected.to be_a Berater::Inhibitor }
127
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
128
137
  end
129
138
  end
130
139
 
@@ -166,6 +175,11 @@ describe Berater::TestMode, order: :defined do
166
175
 
167
176
  it_behaves_like 'a RateLimiter'
168
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
169
183
  end
170
184
  end
171
185
 
@@ -200,6 +214,11 @@ describe Berater::TestMode, order: :defined do
200
214
  it { is_expected.to be_a Berater::ConcurrencyLimiter }
201
215
 
202
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
203
222
  end
204
223
  end
205
224
 
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.6.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-03-07 00:00:00.000000000 Z
11
+ date: 2021-03-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis