semian 0.6.0 → 0.6.1

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.
@@ -1,237 +0,0 @@
1
- require 'test_helper'
2
-
3
- class TestRedis < Minitest::Test
4
- ERROR_TIMEOUT = 5
5
- ERROR_THRESHOLD = 1
6
- SEMIAN_OPTIONS = {
7
- name: :testing,
8
- tickets: 1,
9
- timeout: 0,
10
- error_threshold: ERROR_THRESHOLD,
11
- success_threshold: 2,
12
- error_timeout: ERROR_TIMEOUT,
13
- }
14
-
15
- attr_writer :threads
16
- def setup
17
- @proxy = Toxiproxy[:semian_test_redis]
18
- Semian.destroy(:redis_testing)
19
- end
20
-
21
- def test_semian_identifier
22
- assert_equal :redis_foo, new_redis(semian: {name: 'foo'}).client.semian_identifier
23
- assert_equal :'redis_127.0.0.1:16379/1', new_redis(semian: {name: nil}).client.semian_identifier
24
- assert_equal :'redis_example.com:42/1', new_redis(host: 'example.com', port: 42, semian: {name: nil}).client.semian_identifier
25
- end
26
-
27
- def test_client_alias
28
- redis = connect_to_redis!
29
- assert_equal redis.client.semian_resource, redis.semian_resource
30
- assert_equal redis.client.semian_identifier, redis.semian_identifier
31
- end
32
-
33
- def test_semian_can_be_disabled
34
- resource = Redis.new(semian: false).client.semian_resource
35
- assert_instance_of Semian::UnprotectedResource, resource
36
- end
37
-
38
- def test_semian_resource_in_pipeline
39
- redis = connect_to_redis!
40
- redis.pipelined do
41
- assert_instance_of Semian::ProtectedResource, redis.semian_resource
42
- end
43
- end
44
-
45
- def test_connection_errors_open_the_circuit
46
- client = connect_to_redis!
47
-
48
- @proxy.downstream(:latency, latency: 600).apply do
49
- ERROR_THRESHOLD.times do
50
- assert_raises ::Redis::TimeoutError do
51
- client.get('foo')
52
- end
53
- end
54
-
55
- assert_raises ::Redis::CircuitOpenError do
56
- client.get('foo')
57
- end
58
- end
59
- end
60
-
61
- def test_command_errors_does_not_open_the_circuit
62
- client = connect_to_redis!
63
- client.hset('my_hash', 'foo', 'bar')
64
- (ERROR_THRESHOLD * 2).times do
65
- assert_raises Redis::CommandError do
66
- client.get('my_hash')
67
- end
68
- end
69
- end
70
-
71
- def test_connect_instrumentation
72
- notified = false
73
- subscriber = Semian.subscribe do |event, resource, scope, adapter|
74
- notified = true
75
- assert_equal :success, event
76
- assert_equal Semian[:redis_testing], resource
77
- assert_equal :connection, scope
78
- assert_equal :redis, adapter
79
- end
80
-
81
- connect_to_redis!
82
-
83
- assert notified, 'No notifications has been emitted'
84
- ensure
85
- Semian.unsubscribe(subscriber)
86
- end
87
-
88
- def test_resource_acquisition_for_connect
89
- connect_to_redis!
90
-
91
- Semian[:redis_testing].acquire do
92
- error = assert_raises Redis::ResourceBusyError do
93
- connect_to_redis!
94
- end
95
- assert_equal :redis_testing, error.semian_identifier
96
- end
97
- end
98
-
99
- def test_redis_connection_errors_are_tagged_with_the_resource_identifier
100
- @proxy.downstream(:latency, latency: 600).apply do
101
- error = assert_raises ::Redis::TimeoutError do
102
- connect_to_redis!
103
- end
104
- assert_equal :redis_testing, error.semian_identifier
105
- end
106
- end
107
-
108
- def test_other_redis_errors_are_not_tagged_with_the_resource_identifier
109
- client = connect_to_redis!
110
- client.set('foo', 'bar')
111
- error = assert_raises ::Redis::CommandError do
112
- client.hget('foo', 'bar')
113
- end
114
- refute error.respond_to?(:semian_identifier)
115
- end
116
-
117
- def test_resource_timeout_on_connect
118
- @proxy.downstream(:latency, latency: 500).apply do
119
- background { connect_to_redis! }
120
-
121
- assert_raises Redis::ResourceBusyError do
122
- connect_to_redis!
123
- end
124
- end
125
- end
126
-
127
- def test_circuit_breaker_on_connect
128
- @proxy.downstream(:latency, latency: 500).apply do
129
- background { connect_to_redis! }
130
-
131
- ERROR_THRESHOLD.times do
132
- assert_raises Redis::ResourceBusyError do
133
- connect_to_redis!
134
- end
135
- end
136
- end
137
-
138
- yield_to_background
139
-
140
- assert_raises Redis::CircuitOpenError do
141
- connect_to_redis!
142
- end
143
-
144
- Timecop.travel(ERROR_TIMEOUT + 1) do
145
- connect_to_redis!
146
- end
147
- end
148
-
149
- def test_query_instrumentation
150
- client = connect_to_redis!
151
-
152
- notified = false
153
- subscriber = Semian.subscribe do |event, resource, scope, adapter|
154
- notified = true
155
- assert_equal :success, event
156
- assert_equal Semian[:redis_testing], resource
157
- assert_equal :query, scope
158
- assert_equal :redis, adapter
159
- end
160
-
161
- client.get('foo')
162
-
163
- assert notified, 'No notifications has been emitted'
164
- ensure
165
- Semian.unsubscribe(subscriber)
166
- end
167
-
168
- def test_resource_acquisition_for_query
169
- client = connect_to_redis!
170
-
171
- Semian[:redis_testing].acquire do
172
- assert_raises Redis::ResourceBusyError do
173
- client.get('foo')
174
- end
175
- end
176
- end
177
-
178
- def test_resource_timeout_on_query
179
- client = connect_to_redis!
180
- client2 = connect_to_redis!
181
-
182
- @proxy.downstream(:latency, latency: 500).apply do
183
- background { client2.get('foo') }
184
-
185
- assert_raises Redis::ResourceBusyError do
186
- client.get('foo')
187
- end
188
- end
189
- end
190
-
191
- def test_circuit_breaker_on_query
192
- client = connect_to_redis!
193
- client2 = connect_to_redis!
194
-
195
- client.set('foo', 2)
196
-
197
- @proxy.downstream(:latency, latency: 1000).apply do
198
- background { client2.get('foo') }
199
-
200
- ERROR_THRESHOLD.times do
201
- assert_raises Redis::ResourceBusyError do
202
- client.get('foo')
203
- end
204
- end
205
- end
206
-
207
- yield_to_background
208
-
209
- assert_raises Redis::CircuitOpenError do
210
- client.get('foo')
211
- end
212
-
213
- Timecop.travel(ERROR_TIMEOUT + 1) do
214
- assert_equal '2', client.get('foo')
215
- end
216
- end
217
-
218
- private
219
-
220
- def new_redis(options = {})
221
- semian_options = SEMIAN_OPTIONS.merge(options.delete(:semian) || {})
222
- Redis.new({
223
- host: '127.0.0.1',
224
- port: 16_379,
225
- reconnect_attempts: 0,
226
- db: 1,
227
- timeout: 0.5,
228
- semian: semian_options,
229
- }.merge(options))
230
- end
231
-
232
- def connect_to_redis!(semian_options = {})
233
- redis = new_redis(semian: semian_options)
234
- redis.client.connect
235
- redis
236
- end
237
- end
@@ -1,322 +0,0 @@
1
- require 'test_helper'
2
-
3
- class TestResource < Minitest::Test
4
- def setup
5
- Semian.destroy(:testing)
6
- rescue
7
- nil
8
- end
9
-
10
- def teardown
11
- destroy_resources
12
- end
13
-
14
- def test_initialize_invalid_args
15
- assert_raises TypeError do
16
- create_resource 123, tickets: 2
17
- end
18
- assert_raises ArgumentError do
19
- create_resource :testing, tickets: -1
20
- end
21
- assert_raises ArgumentError do
22
- create_resource :testing, tickets: 1_000_000
23
- end
24
- assert_raises TypeError do
25
- create_resource :testing, tickets: 2, permissions: 'test'
26
- end
27
- end
28
-
29
- def test_initialize_with_float
30
- resource = create_resource :testing, tickets: 1.0
31
- assert resource
32
- assert_equal 1, resource.tickets
33
- end
34
-
35
- def test_max_tickets
36
- assert Semian::MAX_TICKETS > 0
37
- end
38
-
39
- def test_register
40
- create_resource :testing, tickets: 2
41
- end
42
-
43
- def test_register_with_no_tickets_raises
44
- assert_raises Semian::SyscallError do
45
- create_resource :testing, tickets: 0
46
- end
47
- end
48
-
49
- def test_acquire
50
- acquired = false
51
- resource = create_resource :testing, tickets: 1
52
- resource.acquire { acquired = true }
53
- assert acquired
54
- end
55
-
56
- def test_acquire_return_val
57
- resource = create_resource :testing, tickets: 1
58
- val = resource.acquire { 1234 }
59
- assert_equal 1234, val
60
- end
61
-
62
- def test_acquire_timeout
63
- resource = create_resource :testing, tickets: 1, timeout: 0.05
64
-
65
- acquired = false
66
- m = Monitor.new
67
- cond = m.new_cond
68
-
69
- t = Thread.start do
70
- m.synchronize do
71
- cond.wait_until { acquired }
72
- assert_raises Semian::TimeoutError do
73
- resource.acquire { refute true }
74
- end
75
- end
76
- end
77
-
78
- resource.acquire do
79
- acquired = true
80
- m.synchronize { cond.signal }
81
- sleep 0.2
82
- end
83
-
84
- t.join
85
-
86
- assert acquired
87
- end
88
-
89
- def test_acquire_timeout_override
90
- resource = create_resource :testing, tickets: 1, timeout: 0.01
91
-
92
- acquired = false
93
- thread_acquired = false
94
- m = Monitor.new
95
- cond = m.new_cond
96
-
97
- t = Thread.start do
98
- m.synchronize do
99
- cond.wait_until { acquired }
100
- resource.acquire(timeout: 1) { thread_acquired = true }
101
- end
102
- end
103
-
104
- resource.acquire do
105
- acquired = true
106
- m.synchronize { cond.signal }
107
- sleep 0.2
108
- end
109
-
110
- t.join
111
-
112
- assert acquired
113
- assert thread_acquired
114
- end
115
-
116
- def test_acquire_with_fork
117
- resource = create_resource :testing, tickets: 2, timeout: 0.5
118
-
119
- resource.acquire do
120
- fork do
121
- resource.acquire do
122
- assert_raises Semian::TimeoutError do
123
- resource.acquire {}
124
- end
125
- end
126
- end
127
-
128
- Process.wait
129
- end
130
- end
131
-
132
- def test_acquire_releases_on_kill
133
- resource = create_resource :testing, tickets: 1, timeout: 0.1
134
- acquired = false
135
-
136
- # Ghetto process synchronization
137
- file = Tempfile.new('semian')
138
- path = file.path
139
- file.close!
140
-
141
- pid = fork do
142
- resource.acquire do
143
- FileUtils.touch(path)
144
- sleep 1000
145
- end
146
- end
147
-
148
- sleep 0.1 until File.exist?(path)
149
- assert_raises Semian::TimeoutError do
150
- resource.acquire {}
151
- end
152
-
153
- Process.kill("KILL", pid)
154
- resource.acquire { acquired = true }
155
- assert acquired
156
-
157
- Process.wait
158
- ensure
159
- FileUtils.rm_f(path) if path
160
- end
161
-
162
- def test_count
163
- resource = create_resource :testing, tickets: 2
164
- acquired = false
165
-
166
- resource.acquire do
167
- acquired = true
168
- assert_equal 1, resource.count
169
- assert_equal 2, resource.tickets
170
- end
171
-
172
- assert acquired
173
- end
174
-
175
- def test_sem_undo
176
- resource = create_resource :testing, tickets: 1
177
-
178
- # Ensure we don't hit ERANGE errors caused by lack of SEM_UNDO on semop* calls
179
- # by doing an acquire > SEMVMX (32767) times:
180
- #
181
- # See: http://lxr.free-electrons.com/source/ipc/sem.c?v=3.8#L419
182
- (1 << 16).times do # do an acquire 64k times
183
- resource.acquire do
184
- 1
185
- end
186
- end
187
- end
188
-
189
- def test_destroy
190
- resource = create_resource :testing, tickets: 1
191
- resource.destroy
192
- assert_raises Semian::SyscallError do
193
- resource.acquire {}
194
- end
195
- end
196
-
197
- def test_permissions
198
- resource = create_resource :testing, permissions: 0600, tickets: 1
199
- semid = resource.semid
200
- `ipcs -s `.lines.each do |line|
201
- if /\s#{semid}\s/.match(line)
202
- assert_equal '600', line.split[3]
203
- end
204
- end
205
-
206
- resource = create_resource :testing, permissions: 0660, tickets: 1
207
- semid = resource.semid
208
- `ipcs -s `.lines.each do |line|
209
- if /\s#{semid}\s/.match(line)
210
- assert_equal '660', line.split[3]
211
- end
212
- end
213
- end
214
-
215
- def test_resize_tickets_increase
216
- resource = create_resource :testing, tickets: 1
217
-
218
- acquired = false
219
- m = Monitor.new
220
- cond = m.new_cond
221
-
222
- t = Thread.start do
223
- m.synchronize do
224
- cond.wait_until { acquired }
225
-
226
- resource = create_resource :testing, tickets: 5
227
- assert_equal 4, resource.count
228
- end
229
- end
230
-
231
- assert_equal 1, resource.count
232
-
233
- resource.acquire do
234
- acquired = true
235
- m.synchronize { cond.signal }
236
- sleep 0.2
237
- end
238
-
239
- t.join
240
-
241
- assert_equal 5, resource.count
242
- end
243
-
244
- def test_resize_tickets_decrease
245
- resource = create_resource :testing, tickets: 5
246
-
247
- acquired = false
248
- m = Monitor.new
249
- cond = m.new_cond
250
-
251
- t = Thread.start do
252
- m.synchronize do
253
- cond.wait_until { acquired }
254
-
255
- resource = create_resource :testing, tickets: 1
256
- assert_equal 0, resource.count
257
- end
258
- end
259
-
260
- assert_equal 5, resource.count
261
-
262
- resource.acquire do
263
- acquired = true
264
- m.synchronize { cond.signal }
265
- sleep 0.2
266
- end
267
-
268
- t.join
269
-
270
- assert_equal 1, resource.count
271
- end
272
-
273
- def test_multiple_register_with_fork
274
- f = Tempfile.new('semian_test')
275
-
276
- begin
277
- f.flock(File::LOCK_EX)
278
-
279
- children = []
280
- 5.times do
281
- children << fork do
282
- acquired = false
283
-
284
- f.flock(File::LOCK_SH)
285
- create_resource(:testing, tickets: 5).acquire do |resource|
286
- assert resource.count < 5
287
- acquired = true
288
- end
289
- assert acquired
290
- end
291
- end
292
- children.compact!
293
-
294
- f.flock(File::LOCK_UN)
295
-
296
- children.delete(Process.wait) while children.any?
297
-
298
- assert_equal 5, create_resource(:testing, tickets: 0).count
299
- ensure
300
- f.close!
301
- end
302
- end
303
-
304
- def create_resource(*args)
305
- @resources ||= []
306
- resource = Semian::Resource.new(*args)
307
- @resources << resource
308
- resource
309
- end
310
-
311
- def destroy_resources
312
- return unless @resources
313
- @resources.each do |resource|
314
- begin
315
- resource.destroy
316
- rescue
317
- nil
318
- end
319
- end
320
- @resources = []
321
- end
322
- end