berater 0.4.0 → 0.7.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.
@@ -0,0 +1,34 @@
1
+ require 'berater/dsl'
2
+
3
+ describe Berater do
4
+ using Berater::DSL
5
+
6
+ it 'instatiates a RateLimiter' do
7
+ limiter = Berater.new(:key) { 1.per second }
8
+ expect(limiter).to be_a Berater::RateLimiter
9
+ expect(limiter.key).to be :key
10
+ expect(limiter.capacity).to be 1
11
+ expect(limiter.interval).to be :second
12
+ end
13
+
14
+ it 'instatiates a ConcurrencyLimiter' do
15
+ limiter = Berater.new(:key, timeout: 2) { 1.at_once }
16
+ expect(limiter).to be_a Berater::ConcurrencyLimiter
17
+ expect(limiter.key).to be :key
18
+ expect(limiter.capacity).to be 1
19
+ expect(limiter.timeout).to be 2
20
+ end
21
+
22
+ it 'does not accept args and dsl block' do
23
+ expect {
24
+ Berater.new(:key, 2) { 3.at_once }
25
+ }.to raise_error(ArgumentError)
26
+ end
27
+
28
+ it 'requires either mode or dsl block' do
29
+ expect {
30
+ Berater.new(:key)
31
+ }.to raise_error(ArgumentError)
32
+ end
33
+
34
+ end
data/spec/dsl_spec.rb ADDED
@@ -0,0 +1,60 @@
1
+ require 'berater/dsl'
2
+
3
+ describe Berater::DSL do
4
+ def check(expected, &block)
5
+ expect(Berater::DSL.eval(&block)).to eq expected
6
+ end
7
+
8
+ context 'rate mode' do
9
+ it 'has keywords' do
10
+ check(:second) { second }
11
+ check(:minute) { minute }
12
+ check(:hour) { hour }
13
+ end
14
+
15
+ it 'parses' do
16
+ check([ 1, interval: :second ]) { 1.per second }
17
+ check([ 3, interval: :minute ]) { 3.per minute }
18
+ check([ 5, interval: :hour ]) { 5.every hour }
19
+ end
20
+
21
+ it 'cleans up afterward' do
22
+ check([ 1, interval: :second ]) { 1.per second }
23
+
24
+ expect(Integer).not_to respond_to(:per)
25
+ expect(Integer).not_to respond_to(:every)
26
+ end
27
+
28
+ it 'works with variables' do
29
+ count = 1
30
+ interval = :second
31
+
32
+ check([ count, interval: interval ]) { count.per interval }
33
+ end
34
+ end
35
+
36
+ context 'concurrency mode' do
37
+ it 'parses' do
38
+ check([ 1 ]) { 1.at_once }
39
+ check([ 3 ]) { 3.at_a_time }
40
+ check([ 5 ]) { 5.concurrently }
41
+ end
42
+
43
+ it 'cleans up afterward' do
44
+ check([ 1 ]) { 1.at_once }
45
+
46
+ expect(Integer).not_to respond_to(:at_once)
47
+ expect(Integer).not_to respond_to(:at_a_time)
48
+ expect(Integer).not_to respond_to(:concurrently)
49
+ end
50
+
51
+ it 'works with constants' do
52
+ class Foo
53
+ CAPACITY = 3
54
+ end
55
+
56
+ check([ Foo::CAPACITY ]) { Foo::CAPACITY.at_once }
57
+ end
58
+ end
59
+
60
+ end
@@ -15,11 +15,9 @@ describe Berater::Inhibitor do
15
15
  end
16
16
 
17
17
  describe '#limit' do
18
- let(:limiter) { described_class.new }
18
+ subject { described_class.new }
19
19
 
20
- it 'always limits' do
21
- expect { limiter.limit }.to be_inhibited
22
- end
20
+ it_behaves_like 'it is overloaded'
23
21
  end
24
22
 
25
23
  end
@@ -0,0 +1,107 @@
1
+ describe Berater::Limiter do
2
+ it 'can not be initialized' do
3
+ expect { described_class.new }.to raise_error(NoMethodError)
4
+ end
5
+
6
+ describe 'abstract methods' do
7
+ let(:limiter) { Class.new(described_class).new(:key, 1) }
8
+
9
+ it do
10
+ expect { limiter.limit }.to raise_error(NotImplementedError)
11
+ expect { limiter.utilization }.to raise_error(NotImplementedError)
12
+ end
13
+ end
14
+
15
+ describe '#limit' do
16
+ subject { Berater::Unlimiter.new }
17
+
18
+ context 'with a capacity parameter' do
19
+ it 'overrides the stored value' do
20
+ is_expected.to receive(:acquire_lock).with(3, anything)
21
+
22
+ subject.limit(capacity: 3)
23
+ end
24
+
25
+ it 'validates the type' do
26
+ expect {
27
+ subject.limit(capacity: 'abc')
28
+ }.to raise_error(ArgumentError)
29
+ end
30
+ end
31
+
32
+ context 'with a cost parameter' do
33
+ it 'overrides the stored value' do
34
+ is_expected.to receive(:acquire_lock).with(anything, 2)
35
+
36
+ subject.limit(cost: 2)
37
+ end
38
+
39
+ it 'validates' do
40
+ expect {
41
+ subject.limit(cost: 'abc')
42
+ }.to raise_error(ArgumentError)
43
+
44
+ expect {
45
+ subject.limit(cost: -1)
46
+ }.to raise_error(ArgumentError)
47
+ end
48
+ end
49
+ end
50
+
51
+ describe '#==' do
52
+ let(:limiter) { Berater::RateLimiter.new(:key, 1, :second) }
53
+
54
+ it 'equals itself' do
55
+ expect(limiter).to eq limiter
56
+ end
57
+
58
+ it 'equals something with the same initialization parameters' do
59
+ expect(limiter).to eq(
60
+ Berater::RateLimiter.new(:key, 1, :second)
61
+ )
62
+ end
63
+
64
+ it 'equals something with equvalent initialization parameters' do
65
+ expect(limiter).to eq(
66
+ Berater::RateLimiter.new(:key, 1, 1)
67
+ )
68
+ end
69
+
70
+ it 'does not equal something different' do
71
+ expect(limiter).not_to eq(
72
+ Berater::RateLimiter.new(:key, 2, :second)
73
+ )
74
+
75
+ expect(limiter).not_to eq(
76
+ Berater::RateLimiter.new(:keyz, 1, :second)
77
+ )
78
+
79
+ expect(limiter).not_to eq(
80
+ Berater::RateLimiter.new(:key, 1, :minute)
81
+ )
82
+ end
83
+
84
+ it 'does not equal something altogether different' do
85
+ expect(limiter).not_to eq(
86
+ Berater::ConcurrencyLimiter.new(:key, 1)
87
+ )
88
+ end
89
+
90
+ it 'works for ConcurrencyLimiter too' do
91
+ limiter = Berater::ConcurrencyLimiter.new(:key, 1)
92
+ expect(limiter).to eq limiter
93
+
94
+ expect(limiter).not_to eq(
95
+ Berater::ConcurrencyLimiter.new(:key, 1, timeout: 1)
96
+ )
97
+ end
98
+
99
+ it 'and the others' do
100
+ unlimiter = Berater::Unlimiter.new
101
+ expect(unlimiter).to eq unlimiter
102
+
103
+ expect(unlimiter).not_to eq Berater::Inhibitor.new
104
+ end
105
+ end
106
+
107
+ end
@@ -0,0 +1,97 @@
1
+ describe Berater::LuaScript do
2
+ subject { Berater::LuaScript('return redis.call("PING")') }
3
+
4
+ before { redis.script(:flush) }
5
+
6
+ let(:redis) { Berater.redis }
7
+
8
+ it { is_expected.to be_a Berater::LuaScript }
9
+
10
+ describe '#eval' do
11
+ def ping
12
+ expect(subject.eval(redis)).to eq 'PONG'
13
+ end
14
+
15
+ it { ping }
16
+
17
+ it 'loads the script into redis' do
18
+ expect(redis).to receive(:evalsha).once.and_call_original
19
+ expect(redis).to receive(:eval).once.and_call_original
20
+ ping
21
+ expect(subject.loaded?(redis)).to be true
22
+ end
23
+ end
24
+
25
+ describe '#load' do
26
+ it 'loads script into redis' do
27
+ expect(redis.script(:exists, subject.sha)).to be false
28
+ subject.load(redis)
29
+ expect(redis.script(:exists, subject.sha)).to be true
30
+ end
31
+
32
+ it 'returns the sha' do
33
+ expect(subject.load(redis)).to eq subject.sha
34
+ end
35
+
36
+ it 'validates the returned sha' do
37
+ allow(redis).to receive(:script).with(:flush).and_call_original
38
+ expect(redis).to receive(:script).with(:load, String).and_return('abc')
39
+ expect { subject.load(redis) }.to raise_error(RuntimeError)
40
+ end
41
+ end
42
+
43
+ describe '#loaded?' do
44
+ it do
45
+ expect(subject.loaded?(redis)).to be false
46
+ subject.load(redis)
47
+ expect(subject.loaded?(redis)).to be true
48
+ end
49
+ end
50
+
51
+ describe '#to_s' do
52
+ it { expect(subject.to_s).to be subject.source }
53
+ end
54
+
55
+ describe '#minify' do
56
+ subject do
57
+ expect(
58
+ Berater::LuaScript(lua).send(:minify)
59
+ ).to eq expected
60
+ end
61
+
62
+ context do
63
+ let(:lua) do <<-LUA
64
+ -- this comment gets removed
65
+ redis.call('PING') -- this one too
66
+ LUA
67
+ end
68
+
69
+ let(:expected) { "redis.call('PING')" }
70
+
71
+ it { subject }
72
+ end
73
+
74
+ context 'with if statement' do
75
+ let(:lua) do <<~LUA
76
+ if condition then
77
+ call
78
+ end
79
+
80
+ return 123
81
+ LUA
82
+ end
83
+
84
+ let(:expected) do
85
+ [
86
+ 'if condition then',
87
+ 'call',
88
+ 'end',
89
+ 'return 123'
90
+ ].join "\n"
91
+ end
92
+
93
+ it { subject }
94
+ end
95
+ end
96
+
97
+ end
@@ -1,118 +1,122 @@
1
- describe 'be_overloaded' do
1
+ describe Berater::Matchers::Overloaded do
2
+
2
3
  context 'Berater::Unlimiter' do
3
- let(:limiter) { Berater.new(:key, :unlimited) }
4
+ let(:limiter) { Berater::Unlimiter.new }
4
5
 
5
6
  it { expect(limiter).not_to be_overloaded }
6
- it { expect(limiter).not_to be_inhibited }
7
- it { expect(limiter).not_to be_overrated }
8
- it { expect(limiter).not_to be_incapacitated }
9
-
10
7
  it { expect { limiter }.not_to be_overloaded }
11
- it { expect { limiter }.not_to be_inhibited }
12
- it { expect { limiter }.not_to be_overrated }
13
- it { expect { limiter }.not_to be_incapacitated }
14
-
15
8
  it { expect { limiter.limit }.not_to be_overloaded }
16
- it { expect { limiter.limit }.not_to be_inhibited }
17
- it { expect { limiter.limit }.not_to be_overrated }
18
- it { expect { limiter.limit }.not_to be_incapacitated }
19
9
  end
20
10
 
21
11
  context 'Berater::Inhibitor' do
22
- let(:limiter) { Berater.new(:key, :inhibited) }
12
+ let(:limiter) { Berater::Inhibitor.new }
23
13
 
24
14
  it { expect(limiter).to be_overloaded }
25
- it { expect(limiter).to be_inhibited }
26
-
27
15
  it { expect { limiter }.to be_overloaded }
28
- it { expect { limiter }.to be_inhibited }
29
-
30
16
  it { expect { limiter.limit }.to be_overloaded }
31
- it { expect { limiter.limit }.to be_inhibited }
32
17
  end
33
18
 
34
19
  context 'Berater::RateLimiter' do
35
- let(:limiter) { Berater.new(:key, :rate, 1, :second) }
20
+ let(:limiter) { Berater::RateLimiter.new(:key, 1, :second) }
36
21
 
37
22
  it { expect(limiter).not_to be_overloaded }
38
- it { expect(limiter).not_to be_inhibited }
39
- it { expect(limiter).not_to be_overrated }
40
- it { expect(limiter).not_to be_incapacitated }
41
-
42
23
  it { expect { limiter }.not_to be_overloaded }
43
- it { expect { limiter }.not_to be_inhibited }
44
- it { expect { limiter }.not_to be_overrated }
45
- it { expect { limiter }.not_to be_incapacitated }
46
-
47
24
  it { expect { limiter.limit }.not_to be_overloaded }
48
- it { expect { limiter.limit }.not_to be_inhibited }
49
- it { expect { limiter.limit }.not_to be_overrated }
50
- it { expect { limiter.limit }.not_to be_incapacitated }
51
25
 
52
26
  context 'once limit is used up' do
53
27
  before { limiter.limit }
54
28
 
55
- it 'should be_overrated' do
56
- expect(limiter).to be_overrated
29
+ it 'should be_overloaded' do
30
+ expect(limiter).to be_overloaded
57
31
  end
58
32
 
59
- it 'should be_overrated' do
60
- expect { limiter }.to be_overrated
33
+ it 'should be_overloaded' do
34
+ expect { limiter }.to be_overloaded
61
35
  end
62
36
 
63
- it 'should be_overrated' do
64
- expect { limiter.limit }.to be_overrated
37
+ it 'should be_overloaded' do
38
+ expect { limiter.limit }.to be_overloaded
65
39
  end
66
40
  end
67
41
  end
68
42
 
69
43
  context 'Berater::ConcurrencyLimiter' do
70
- let(:limiter) { Berater.new(:key, :concurrency, 1) }
44
+ let(:limiter) { Berater::ConcurrencyLimiter.new(:key, 1) }
71
45
 
72
46
  it { expect(limiter).not_to be_overloaded }
73
- it { expect(limiter).not_to be_inhibited }
74
- it { expect(limiter).not_to be_overrated }
75
- it { expect(limiter).not_to be_incapacitated }
76
-
77
47
  it { expect { limiter }.not_to be_overloaded }
78
- it { expect { limiter }.not_to be_inhibited }
79
- it { expect { limiter }.not_to be_overrated }
80
- it { expect { limiter }.not_to be_incapacitated }
81
-
82
48
  it { expect { limiter.limit }.not_to be_overloaded }
83
- it { expect { limiter.limit }.not_to be_inhibited }
84
- it { expect { limiter.limit }.not_to be_overrated }
85
- it { expect { limiter.limit }.not_to be_incapacitated }
86
49
 
87
50
  context 'when lock is released' do
88
- it 'should be_incapacitated' do
51
+ it 'should be_overloaded' do
89
52
  3.times do
90
- expect(limiter).not_to be_incapacitated
53
+ expect(limiter).not_to be_overloaded
91
54
  end
92
55
  end
93
56
 
94
- it 'should be_incapacitated' do
57
+ it 'should be_overloaded' do
95
58
  3.times do
96
- expect { limiter }.not_to be_incapacitated
59
+ expect { limiter }.not_to be_overloaded
97
60
  end
98
61
  end
99
62
 
100
- it 'should be_incapacitated' do
63
+ it 'should be_overloaded' do
101
64
  3.times do
102
- expect { limiter.limit {} }.not_to be_incapacitated
65
+ expect { limiter.limit {} }.not_to be_overloaded
103
66
  end
104
67
  end
105
68
  end
106
69
 
107
70
  context 'when lock is *not* released' do
108
- it 'should be_incapacitated' do
109
- expect { limiter.limit }.not_to be_incapacitated
110
- expect { limiter.limit }.to be_incapacitated
71
+ it 'should be_overloaded' do
72
+ expect { limiter.limit }.not_to be_overloaded
73
+ expect { limiter.limit }.to be_overloaded
111
74
  end
112
75
 
113
- it 'should be_incapacitated' do
114
- expect { 3.times { limiter.limit } }.to be_incapacitated
76
+ it 'should be_overloaded' do
77
+ expect { 3.times { limiter.limit } }.to be_overloaded
115
78
  end
116
79
  end
117
80
  end
81
+
82
+ context 'when matchers fail' do
83
+ let(:unlimiter) { Berater::Unlimiter.new }
84
+ let(:inhibitor) { Berater::Inhibitor.new }
85
+
86
+ it 'catches false negatives' do
87
+ expect {
88
+ expect(unlimiter).to be_overloaded
89
+ }.to fail_including('expected to be overloaded')
90
+
91
+ expect {
92
+ expect { unlimiter }.to be_overloaded
93
+ }.to fail_including('expected to be overloaded')
94
+
95
+ expect {
96
+ expect { unlimiter.limit }.to be_overloaded
97
+ }.to fail_including("expected #{Berater::Overloaded} to be raised")
98
+
99
+ expect {
100
+ expect { 123 }.to be_overloaded
101
+ }.to fail_including("expected #{Berater::Overloaded} to be raised")
102
+ end
103
+
104
+ it 'catches false positives' do
105
+ expect {
106
+ expect(inhibitor).not_to be_overloaded
107
+ }.to fail_including('expected not to be overloaded')
108
+
109
+ expect {
110
+ expect { inhibitor }.not_to be_overloaded
111
+ }.to fail_including('expected not to be overloaded')
112
+
113
+ expect {
114
+ expect { inhibitor.limit }.not_to be_overloaded
115
+ }.to fail_including("did not expect #{Berater::Overloaded} to be raised")
116
+
117
+ expect {
118
+ expect { raise Berater::Overloaded }.not_to be_overloaded
119
+ }.to fail_including("did not expect #{Berater::Overloaded} to be raised")
120
+ end
121
+ end
118
122
  end