semian 0.6.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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