berater 0.0.1 → 0.1.4

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,202 @@
1
+ describe Berater::ConcurrencyLimiter do
2
+ before { Berater.mode = :concurrency }
3
+
4
+ describe '.new' do
5
+ let(:limiter) { described_class.new(1) }
6
+
7
+ it 'initializes' do
8
+ expect(limiter.capacity).to be 1
9
+ end
10
+
11
+ it 'has default values' do
12
+ expect(limiter.key).to eq described_class.to_s
13
+ expect(limiter.redis).to be Berater.redis
14
+ end
15
+ end
16
+
17
+ describe '#capacity' do
18
+ def expect_capacity(capacity)
19
+ limiter = described_class.new(capacity)
20
+ expect(limiter.capacity).to eq capacity
21
+ end
22
+
23
+ it { expect_capacity(0) }
24
+ it { expect_capacity(1) }
25
+ it { expect_capacity(10_000) }
26
+
27
+ context 'with erroneous values' do
28
+ def expect_bad_capacity(capacity)
29
+ expect do
30
+ described_class.new(capacity)
31
+ end.to raise_error ArgumentError
32
+ end
33
+
34
+ it { expect_bad_capacity(0.5) }
35
+ it { expect_bad_capacity(-1) }
36
+ it { expect_bad_capacity('1') }
37
+ it { expect_bad_capacity(:one) }
38
+ end
39
+ end
40
+
41
+ describe '#timeout' do
42
+ def expect_timeout(timeout)
43
+ limiter = described_class.new(1, timeout: timeout)
44
+ expect(limiter.timeout).to eq timeout
45
+ end
46
+
47
+ it { expect_timeout(0) }
48
+ it { expect_timeout(1) }
49
+ it { expect_timeout(10_000) }
50
+
51
+ context 'with erroneous values' do
52
+ def expect_bad_timeout(timeout)
53
+ expect do
54
+ described_class.new(1, timeout: timeout)
55
+ end.to raise_error ArgumentError
56
+ end
57
+
58
+ it { expect_bad_timeout(0.5) }
59
+ it { expect_bad_timeout(-1) }
60
+ it { expect_bad_timeout('1') }
61
+ it { expect_bad_timeout(:one) }
62
+ end
63
+ end
64
+
65
+ describe '#limit' do
66
+ let(:limiter) { described_class.new(2, timeout: 1) }
67
+
68
+ it 'works' do
69
+ expect {|b| limiter.limit(&b) }.to yield_control
70
+ end
71
+
72
+ it 'works many times if workers release locks' do
73
+ 30.times do
74
+ expect {|b| limiter.limit(&b) }.to yield_control
75
+ end
76
+
77
+ 30.times do
78
+ lock = limiter.limit
79
+ lock.release
80
+ end
81
+ end
82
+
83
+ it 'limits excessive calls' do
84
+ expect(limiter.limit).to be_a Berater::ConcurrencyLimiter::Lock
85
+ expect(limiter.limit).to be_a Berater::ConcurrencyLimiter::Lock
86
+
87
+ expect(limiter).to be_incapacitated
88
+ end
89
+
90
+ it 'times out locks' do
91
+ expect(limiter.limit).to be_a Berater::ConcurrencyLimiter::Lock
92
+ expect(limiter.limit).to be_a Berater::ConcurrencyLimiter::Lock
93
+ expect(limiter).to be_incapacitated
94
+
95
+ Timecop.travel(1)
96
+
97
+ expect(limiter.limit).to be_a Berater::ConcurrencyLimiter::Lock
98
+ expect(limiter.limit).to be_a Berater::ConcurrencyLimiter::Lock
99
+ expect(limiter).to be_incapacitated
100
+ end
101
+
102
+ context 'with capacity 0' do
103
+ let(:limiter) { described_class.new(0) }
104
+
105
+ it 'always fails' do
106
+ expect(limiter).to be_incapacitated
107
+ end
108
+ end
109
+ end
110
+
111
+ context 'with same key, different limiters' do
112
+ let(:limiter_one) { described_class.new(1) }
113
+ let(:limiter_two) { described_class.new(1) }
114
+
115
+ it { expect(limiter_one.key).to eq limiter_two.key }
116
+
117
+ it 'works as expected' do
118
+ expect(limiter_one.limit).to be_a Berater::ConcurrencyLimiter::Lock
119
+
120
+ expect(limiter_one).to be_incapacitated
121
+ expect(limiter_two).to be_incapacitated
122
+ end
123
+ end
124
+
125
+ context 'with different keys, same limiter' do
126
+ let(:limiter) { described_class.new(1) }
127
+
128
+ it 'works as expected' do
129
+ one_lock = limiter.limit(key: :one)
130
+ expect(one_lock).to be_a Berater::ConcurrencyLimiter::Lock
131
+
132
+ expect { limiter.limit(key: :one) {} }.to be_incapacitated
133
+ expect { limiter.limit(key: :two) {} }.not_to be_incapacitated
134
+
135
+ two_lock = limiter.limit(key: :two)
136
+ expect(two_lock).to be_a Berater::ConcurrencyLimiter::Lock
137
+
138
+ expect { limiter.limit(key: :one) {} }.to be_incapacitated
139
+ expect { limiter.limit(key: :two) {} }.to be_incapacitated
140
+
141
+ one_lock.release
142
+ expect { limiter.limit(key: :one) {} }.not_to be_incapacitated
143
+ expect { limiter.limit(key: :two) {} }.to be_incapacitated
144
+
145
+ two_lock.release
146
+ expect { limiter.limit(key: :one) {} }.not_to be_incapacitated
147
+ expect { limiter.limit(key: :two) {} }.not_to be_incapacitated
148
+ end
149
+ end
150
+
151
+ context 'with same key, different capacities' do
152
+ let(:limiter_one) { described_class.new(1) }
153
+ let(:limiter_two) { described_class.new(2) }
154
+
155
+ it { expect(limiter_one.capacity).not_to eq limiter_two.capacity }
156
+
157
+ it 'works as expected' do
158
+ one_lock = limiter_one.limit
159
+ expect(one_lock).to be_a Berater::ConcurrencyLimiter::Lock
160
+
161
+ expect(limiter_one).to be_incapacitated
162
+ expect(limiter_two).not_to be_incapacitated
163
+
164
+ two_lock = limiter_two.limit
165
+ expect(two_lock).to be_a Berater::ConcurrencyLimiter::Lock
166
+
167
+ expect(limiter_one).to be_incapacitated
168
+ expect(limiter_two).to be_incapacitated
169
+
170
+ one_lock.release
171
+ expect(limiter_one).to be_incapacitated
172
+ expect(limiter_two).not_to be_incapacitated
173
+
174
+ two_lock.release
175
+ expect(limiter_one).not_to be_incapacitated
176
+ expect(limiter_two).not_to be_incapacitated
177
+ end
178
+ end
179
+
180
+ context 'with different keys, different limiters' do
181
+ let(:limiter_one) { described_class.new(1, key: :one) }
182
+ let(:limiter_two) { described_class.new(1, key: :two) }
183
+
184
+ it 'works as expected' do
185
+ expect(limiter_one).not_to be_incapacitated
186
+ expect(limiter_two).not_to be_incapacitated
187
+
188
+ one_lock = limiter_one.limit
189
+ expect(one_lock).to be_a Berater::ConcurrencyLimiter::Lock
190
+
191
+ expect(limiter_one).to be_incapacitated
192
+ expect(limiter_two).not_to be_incapacitated
193
+
194
+ two_lock = limiter_two.limit
195
+ expect(two_lock).to be_a Berater::ConcurrencyLimiter::Lock
196
+
197
+ expect(limiter_one).to be_incapacitated
198
+ expect(limiter_two).to be_incapacitated
199
+ end
200
+ end
201
+
202
+ end
@@ -0,0 +1,92 @@
1
+ describe Berater::ConcurrencyLimiter::Lock do
2
+ before { Berater.mode = :concurrency }
3
+
4
+ let(:limiter) { Berater.new(:concurrency, 3) }
5
+
6
+ describe '#contention' do
7
+ it 'tracks contention' do
8
+ lock_1 = limiter.limit
9
+ expect(lock_1.contention).to eq 1
10
+
11
+ lock_2 = limiter.limit
12
+ expect(lock_2.contention).to eq 2
13
+
14
+ limiter.limit do |lock_3|
15
+ expect(lock_3.contention).to eq 3
16
+ end
17
+ end
18
+
19
+ it 'works in block mode' do
20
+ lock_1 = limiter.limit
21
+
22
+ limiter.limit do |lock_2|
23
+ expect(lock_1.contention).to eq 1
24
+ expect(lock_2.contention).to eq 2
25
+ end
26
+ end
27
+ end
28
+
29
+ describe '#release' do
30
+ it 'can not be released twice' do
31
+ lock = limiter.limit
32
+ expect(lock.release).to be true
33
+ expect { lock.release }.to raise_error(RuntimeError, /already/)
34
+ end
35
+
36
+ it 'does not work in block mode' do
37
+ expect do
38
+ limiter.limit do |lock|
39
+ lock.release
40
+ end
41
+ end.to raise_error(RuntimeError, /already/)
42
+ end
43
+ end
44
+
45
+ describe '#released?' do
46
+ it 'works' do
47
+ lock = limiter.limit
48
+ expect(lock.released?).to be false
49
+
50
+ lock.release
51
+ expect(lock.released?).to be true
52
+ end
53
+
54
+ it 'works in block mode' do
55
+ limiter.limit do |lock|
56
+ expect(lock.released?).to be false
57
+ end
58
+ end
59
+ end
60
+
61
+ describe '#expired?' do
62
+ let!(:lock) { limiter.limit }
63
+
64
+ context 'when timeout is not set' do
65
+ it { expect(limiter.timeout).to eq 0 }
66
+
67
+ it 'never expires' do
68
+ expect(lock.expired?).to be false
69
+
70
+ Timecop.travel(1_000)
71
+
72
+ expect(lock.expired?).to be false
73
+ end
74
+ end
75
+
76
+ context 'when timeout is set and exceeded' do
77
+ before { Timecop.travel(1) }
78
+
79
+ let(:limiter) { Berater.new(:concurrency, 3, timeout: 1) }
80
+
81
+ it 'expires' do
82
+ expect(lock.expired?).to be true
83
+ end
84
+
85
+ it 'fails to release' do
86
+ expect(lock.released?).to be false
87
+ expect { lock.release }.to raise_error(RuntimeError, /expired/)
88
+ end
89
+ end
90
+ end
91
+
92
+ end
@@ -0,0 +1,37 @@
1
+ describe Berater::Inhibitor do
2
+ before { Berater.mode = :inhibited }
3
+
4
+ describe '.new' do
5
+ it 'initializes without any arguments or options' do
6
+ expect(described_class.new).to be_a described_class
7
+ end
8
+
9
+ it 'initializes with any arguments and options' do
10
+ expect(described_class.new(:abc, x: 123)).to be_a described_class
11
+ end
12
+
13
+ it 'has default values' do
14
+ expect(described_class.new.key).to eq described_class.to_s
15
+ expect(described_class.new.redis).to be Berater.redis
16
+ end
17
+ end
18
+
19
+ describe '.limit' do
20
+ it 'always limits' do
21
+ expect { described_class.limit }.to be_inhibited
22
+ end
23
+
24
+ it 'works with any arguments or options' do
25
+ expect { described_class.limit(:abc, x: 123) }.to be_inhibited
26
+ end
27
+ end
28
+
29
+ describe '#limit' do
30
+ let(:limiter) { described_class.new }
31
+
32
+ it 'always limits' do
33
+ expect { described_class.limit }.to be_inhibited
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,118 @@
1
+ describe 'be_overloaded' do
2
+ context 'Berater::Unlimiter' do
3
+ let(:limiter) { Berater.new(:unlimited) }
4
+
5
+ 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
+ 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
+ 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
+ end
20
+
21
+ context 'Berater::Inhibitor' do
22
+ let(:limiter) { Berater.new(:inhibited) }
23
+
24
+ it { expect(limiter).to be_overloaded }
25
+ it { expect(limiter).to be_inhibited }
26
+
27
+ it { expect { limiter }.to be_overloaded }
28
+ it { expect { limiter }.to be_inhibited }
29
+
30
+ it { expect { limiter.limit }.to be_overloaded }
31
+ it { expect { limiter.limit }.to be_inhibited }
32
+ end
33
+
34
+ context 'Berater::RateLimiter' do
35
+ let(:limiter) { Berater.new(:rate, 1, :second) }
36
+
37
+ 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
+ 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
+ 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
+
52
+ context 'once limit is used up' do
53
+ before { limiter.limit }
54
+
55
+ it 'should be_overrated' do
56
+ expect(limiter).to be_overrated
57
+ end
58
+
59
+ it 'should be_overrated' do
60
+ expect { limiter }.to be_overrated
61
+ end
62
+
63
+ it 'should be_overrated' do
64
+ expect { limiter.limit }.to be_overrated
65
+ end
66
+ end
67
+ end
68
+
69
+ context 'Berater::ConcurrencyLimiter' do
70
+ let(:limiter) { Berater.new(:concurrency, 1) }
71
+
72
+ 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
+ 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
+ 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
+
87
+ context 'when lock is released' do
88
+ it 'should be_incapacitated' do
89
+ 3.times do
90
+ expect(limiter).not_to be_incapacitated
91
+ end
92
+ end
93
+
94
+ it 'should be_incapacitated' do
95
+ 3.times do
96
+ expect { limiter }.not_to be_incapacitated
97
+ end
98
+ end
99
+
100
+ it 'should be_incapacitated' do
101
+ 3.times do
102
+ expect { limiter.limit {} }.not_to be_incapacitated
103
+ end
104
+ end
105
+ end
106
+
107
+ 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
111
+ end
112
+
113
+ it 'should be_incapacitated' do
114
+ expect { 3.times { limiter.limit } }.to be_incapacitated
115
+ end
116
+ end
117
+ end
118
+ end