sidejob 3.0.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,452 @@
1
+ require 'spec_helper'
2
+
3
+ describe SideJob::Port do
4
+ before do
5
+ @job = SideJob.queue('testq', 'TestWorker', inports: {
6
+ port1: {},
7
+ memory: { mode: :memory },
8
+ default: { default: 'default' },
9
+ default_null: { default: nil },
10
+ default_false: { default: false},
11
+ memory_with_default: { mode: :memory, default: 'memory default' },
12
+ }, outports: {
13
+ out1: {},
14
+ })
15
+ @port1 = @job.input(:port1)
16
+ @out1 = @job.output(:out1)
17
+ @memory = @job.input(:memory)
18
+ @default = @job.input(:default)
19
+ @defaults = [
20
+ @default,
21
+ @job.input(:default_null),
22
+ @job.input(:default_false),
23
+ @job.input(:memory_with_default)
24
+ ]
25
+ end
26
+
27
+ describe '#initialize' do
28
+ it 'raises error if name is invalid' do
29
+ expect { SideJob::Port.new(@job, :in, 'PORT.1')}.to raise_error
30
+ end
31
+
32
+ it 'raises error if name is empty' do
33
+ expect { SideJob::Port.new(@job, :in, '')}.to raise_error
34
+ end
35
+ end
36
+
37
+ describe '#==, #eql?' do
38
+ it 'two ports with the same job, type, and name are eq' do
39
+ expect(SideJob::Port.new(@job, :in, :port1)).to eq(@port1)
40
+ expect(SideJob::Port.new(@job, :in, 'port1')).to eq(@port1)
41
+ expect(SideJob::Port.new(@job, :in, 'port1')).to eql(@port1)
42
+ end
43
+
44
+ it 'two ports with different names are not eq' do
45
+ expect(SideJob::Port.new(@job, :in, 'port2')).not_to eq(@port1)
46
+ expect(SideJob::Port.new(@job, :in, 'port2')).not_to eql(@port1)
47
+ end
48
+
49
+ it 'port names are case sensitive' do
50
+ expect(SideJob::Port.new(@job, :in, 'PORT1')).not_to eq(@port1)
51
+ expect(SideJob::Port.new(@job, :in, 'PORT1')).not_to eql(@port1)
52
+ end
53
+ end
54
+
55
+ describe '#to_s' do
56
+ it 'returns the redis key' do
57
+ expect(SideJob::Port.new(@job, :in, :port1).to_s).to eq "job:#{@job.id}:in:port1"
58
+ end
59
+ end
60
+
61
+ describe '#options' do
62
+ it 'returns port options' do
63
+ expect(@port1.options).to eq({mode: :queue})
64
+ expect(@memory.options).to eq({mode: :memory})
65
+ expect(@default.options).to eq({mode: :queue, default: 'default'})
66
+ end
67
+ end
68
+
69
+ describe '#options=' do
70
+ it 'can change mode to memory' do
71
+ expect(@port1.mode).to eq :queue
72
+ @port1.options = { mode: :memory }
73
+ expect(@port1.mode).to eq :memory
74
+ end
75
+
76
+ it 'can change mode to queue' do
77
+ expect(@memory.mode).to eq :memory
78
+ @memory.options = { mode: :queue }
79
+ expect(@memory.mode).to eq :queue
80
+ end
81
+
82
+ it 'can set default value' do
83
+ expect(@port1.default?).to be false
84
+ @port1.options = { default: [1,2] }
85
+ expect(@port1.default).to eq [1,2]
86
+ expect(@port1.read).to eq [1,2]
87
+ end
88
+
89
+ it 'can remove default value' do
90
+ expect(@default.default?).to be true
91
+ @default.options = { }
92
+ expect(@default.default?).to be false
93
+ end
94
+ end
95
+
96
+ describe '#mode' do
97
+ it 'returns nil for non-existent ports' do
98
+ expect(SideJob::Port.new(@job, :in, 'missing').mode).to be nil
99
+ end
100
+
101
+ it 'returns the port mode for queue ports' do
102
+ expect(@port1.mode).to eq :queue
103
+ expect(@default.mode).to eq :queue
104
+ end
105
+
106
+ it 'returns the port mode for memory ports' do
107
+ expect(@memory.mode).to eq :memory
108
+ expect(@job.input(:memory_with_default).mode).to eq :memory
109
+ end
110
+ end
111
+
112
+ describe '#size' do
113
+ it 'returns 0 when the port is empty' do
114
+ expect(@port1.size).to eq 0
115
+ end
116
+
117
+ it 'number of elements is increased on write' do
118
+ expect {
119
+ @port1.write 1
120
+ }.to change(@port1, :size).by(1)
121
+ end
122
+
123
+ it 'number of elements is decreased on read' do
124
+ @port1.write 1
125
+ expect {
126
+ @port1.read
127
+ }.to change(@port1, :size).by(-1)
128
+ end
129
+
130
+ it 'returns 0 if there is default value' do
131
+ @defaults.each {|port| expect(port.size).to eq 0 }
132
+ end
133
+ end
134
+
135
+ describe '#data?' do
136
+ it 'returns false when the port is empty' do
137
+ expect(@port1.data?).to be false
138
+ end
139
+
140
+ it 'returns true when the port has data' do
141
+ @port1.write 1
142
+ expect(@port1.data?).to be true
143
+ end
144
+
145
+ it 'works for memory ports' do
146
+ expect(@memory.data?).to be false
147
+ @memory.write 1
148
+ expect(@memory.data?).to be true
149
+ end
150
+
151
+ it 'works when there is a default value on the port' do
152
+ @defaults.each {|port| expect(port.data?).to be true }
153
+ end
154
+ end
155
+
156
+ describe '#default' do
157
+ it 'returns nil for no default' do
158
+ expect(@port1.default).to be nil
159
+ end
160
+
161
+ it 'returns default value' do
162
+ @port1.options = {default: [1,2]}
163
+ expect(@port1.default).to eq [1,2]
164
+ end
165
+
166
+ it 'can return null default value' do
167
+ @port1.options = {default: nil}
168
+ expect(@port1.default).to be nil
169
+ expect(@port1.default?).to be true
170
+ end
171
+ end
172
+
173
+ describe '#default?' do
174
+ it 'returns false for normal port' do
175
+ expect(@port1.default?).to be false
176
+ end
177
+
178
+ it 'returns false for memory port with no data written' do
179
+ expect(@memory.default?).to be false
180
+ end
181
+
182
+ it 'returns true for memory port after writing data' do
183
+ @memory.write 1
184
+ expect(@memory.default?).to be true
185
+ end
186
+
187
+ it 'returns true for port with default value' do
188
+ @defaults.each {|port| expect(port.default?).to be true }
189
+ end
190
+ end
191
+
192
+ describe '#write' do
193
+ it 'can write different types of data to a port' do
194
+ ['abc', 123, true, false, nil, {abc: 123}, [1, {foo: true}]].each {|x| @port1.write x}
195
+ data = SideJob.redis.lrange(@port1.redis_key, 0, -1)
196
+ expect(data).to eq(['"abc"', '123', 'true', 'false', 'null', '{"abc":123}', '[1,{"foo":true}]'])
197
+ end
198
+
199
+ it 'writing to a memory port should only set default value to latest value' do
200
+ @memory.write 'abc'
201
+ @memory.write [1, {foo: true}]
202
+ @memory.write 'latest'
203
+ expect(@memory.size).to eq 0
204
+ expect(@memory.default).to eq 'latest'
205
+ end
206
+
207
+ it 'logs writes' do
208
+ now = Time.now
209
+ allow(Time).to receive(:now) { now }
210
+ @job.group_port_logs do
211
+ @port1.write 'abc'
212
+ @port1.write 123
213
+ end
214
+ expect(SideJob.logs).to eq [{'timestamp' => SideJob.timestamp, 'job' => @job.id, 'read' => [],
215
+ 'write' => ['job' => @job.id, 'inport' => 'port1', 'data' => ['abc', 123]]}]
216
+ end
217
+
218
+ it 'raises error if port does not exist' do
219
+ expect { SideJob::Port.new(@job, :in, 'foo').write true }.to raise_error
220
+ end
221
+
222
+ it 'raises error if port mode is unknown ' do
223
+ SideJob.redis.hset "#{@job.redis_key}:inports:mode", 'foo', '???'
224
+ expect { SideJob::Port.new(@job, :in, 'foo').write true }.to raise_error
225
+ end
226
+
227
+ it 'runs the job if it is an input port' do
228
+ expect(@port1.job).to receive(:run)
229
+ @port1.write 3
230
+ end
231
+
232
+ it 'does not run the job if it is an output port' do
233
+ expect(@out1.job).not_to receive(:run)
234
+ @out1.write 3
235
+ end
236
+
237
+ it 'does not run the job if it is a memory port' do
238
+ expect(@memory.job).not_to receive(:run)
239
+ @memory.write 3
240
+ end
241
+ end
242
+
243
+ describe '#read' do
244
+ it 'can distinguish reading nil data and no data' do
245
+ expect { @port1.read }.to raise_error(EOFError)
246
+ @port1.write nil
247
+ expect(@port1.read).to be nil
248
+ end
249
+
250
+ it 'can read data from a queue port' do
251
+ expect { @port1.read }.to raise_error(EOFError)
252
+ ['abc', 123, true, false, nil, {}, ['data1', 1, {key: 'val'}]].each {|x| @port1.write x}
253
+ expect(@port1.size).to be(7)
254
+ expect(@port1.read).to eq('abc')
255
+ expect(@port1.read).to eq(123)
256
+ expect(@port1.read).to eq(true)
257
+ expect(@port1.read).to eq(false)
258
+ expect(@port1.read).to eq(nil)
259
+ expect(@port1.read).to eq({})
260
+ expect(@port1.read).to eq(['data1', 1, {'key' => 'val'}])
261
+ expect { @port1.read }.to raise_error(EOFError)
262
+ expect(@port1.size).to be(0)
263
+ end
264
+
265
+ it 'can read data from a memory port' do
266
+ expect { @memory.read }.to raise_error(EOFError)
267
+ 5.times {|i| @memory.write i }
268
+ 3.times { expect(@memory.read).to eq(4) }
269
+ end
270
+
271
+ it 'can use default value' do
272
+ @defaults.each do |port|
273
+ 3.times { expect(port.read).to eq port.default }
274
+ port.write 'mydata'
275
+ expect(port.read).to eq 'mydata'
276
+ 3.times { expect(port.read).to eq port.mode == :memory ? 'mydata' : port.default }
277
+ end
278
+ end
279
+
280
+ it 'can use null default value' do
281
+ port = @job.input(:default_null)
282
+ expect(port.read).to eq nil
283
+ end
284
+
285
+ it 'can use false default value' do
286
+ port = @job.input(:default_false)
287
+ expect(port.read).to be false
288
+ end
289
+
290
+ it 'logs reads' do
291
+ now = Time.now
292
+ allow(Time).to receive(:now) { now }
293
+ ['abc', 123].each {|x| @port1.write x}
294
+ SideJob.logs
295
+ @job.group_port_logs do
296
+ expect(@port1.read).to eq('abc')
297
+ expect(@port1.read).to eq(123)
298
+ end
299
+ expect(SideJob.logs).to eq [{'timestamp' => SideJob.timestamp, 'job' => @job.id,
300
+ 'read' => ['job' => @job.id, 'inport' => 'port1', 'data' => ['abc', 123]],
301
+ 'write' => []}]
302
+ end
303
+ end
304
+
305
+ describe '#connect_to' do
306
+ it 'does nothing on an empty port' do
307
+ expect(@out1.connect_to(@port1)).to eq []
308
+ expect(@port1.data?).to be false
309
+ end
310
+
311
+ it 'sends data to a port' do
312
+ @out1.write 1
313
+ @out1.write [2,3]
314
+ expect(@out1.connect_to(@port1)).to eq [1, [2,3]]
315
+ expect(@out1.data?).to be false
316
+ expect(@port1.read).to eq 1
317
+ expect(@port1.read).to eq [2,3]
318
+ expect(@port1.data?).to be false
319
+ end
320
+
321
+ it 'sends data to all destination ports' do
322
+ dest = [@port1, @memory, @default]
323
+ @out1.write 1
324
+ @out1.write [2,3]
325
+ @out1.connect_to dest
326
+ expect(@port1.read).to eq 1
327
+ expect(@port1.read).to eq [2,3]
328
+ expect(@port1.data?).to be false
329
+ expect(@memory.size).to eq 0
330
+ expect(@memory.read).to eq [2,3]
331
+ expect(@default.read).to eq 1
332
+ expect(@default.read).to eq [2,3]
333
+ expect(@default.read).to eq 'default'
334
+ end
335
+
336
+ it 'passes port default values to all destinations' do
337
+ dest = [@port1, @memory, @out1]
338
+ @default.write 1
339
+ @default.write [2,3]
340
+ @default.connect_to dest
341
+ expect(@port1.read).to eq 1
342
+ expect(@port1.read).to eq [2,3]
343
+ expect(@port1.default).to eq 'default'
344
+ expect(@memory.size).to eq 0
345
+ expect(@memory.default).to eq 'default'
346
+ expect(@out1.read).to eq 1
347
+ expect(@out1.read).to eq [2,3]
348
+ expect(@out1.default).to eq 'default'
349
+ end
350
+
351
+ it 'runs job for normal input port' do
352
+ expect(@port1.job).to receive(:run)
353
+ @out1.write true
354
+ @out1.connect_to @port1
355
+ end
356
+
357
+ it 'does not run job if no data sent' do
358
+ expect(@port1.job).not_to receive(:run)
359
+ @out1.connect_to @port1
360
+ end
361
+
362
+ it 'does not run job for memory port' do
363
+ expect(@memory.job).not_to receive(:run)
364
+ @out1.write true
365
+ @out1.connect_to @memory
366
+ end
367
+
368
+ it 'logs data' do
369
+ now = Time.now
370
+ allow(Time).to receive(:now) { now }
371
+ @out1.write 1
372
+ @out1.write [2,3]
373
+ SideJob.logs(clear: true)
374
+ @out1.connect_to(@port1)
375
+ expect(SideJob.logs).to eq([{'timestamp' => SideJob.timestamp,
376
+ 'read' => [{'job' => @job.id, 'outport' => 'out1', 'data' => [1,[2,3]]}],
377
+ 'write' => [{'job' => @job.id, 'inport' => 'port1', 'data' => [1,[2,3]]}] }])
378
+ end
379
+
380
+ it 'can specify additional metadata to add to log' do
381
+ now = Time.now
382
+ allow(Time).to receive(:now) { now }
383
+ job2 = SideJob.queue('testq', 'TestWorker')
384
+ @out1.write 1
385
+ @out1.write [2,3]
386
+ SideJob.logs(clear: true)
387
+ @out1.connect_to(@port1, user: 'test')
388
+ expect(SideJob.logs).to eq([{'timestamp' => SideJob.timestamp, 'user' => 'test',
389
+ 'read' => [{'job' => @job.id, 'outport' => 'out1', 'data' => [1,[2,3]]}],
390
+ 'write' => [{'job' => @job.id, 'inport' => 'port1', 'data' => [1,[2,3]]}] }])
391
+ end
392
+
393
+ it 'does not log if no data on port' do
394
+ job2 = SideJob.queue('testq', 'TestWorker')
395
+ @out1.connect_to(@port1)
396
+ expect(SideJob.logs).to eq([])
397
+ end
398
+ end
399
+
400
+ describe 'is Enumerable' do
401
+ it 'can iterate over port elements' do
402
+ 10.times {|i| @port1.write i}
403
+ num = 0
404
+ @port1.each_with_index do |elem, i|
405
+ expect(elem).to eq i
406
+ num += 1
407
+ end
408
+ expect(num).to eq 10
409
+ end
410
+
411
+ it '#entries returns all data as an array' do
412
+ expect(@port1.entries).to eq []
413
+ 10.times {|i| @port1.write i}
414
+ expect(@port1.entries).to eq Array(0..9)
415
+ expect(@port1.entries).to eq []
416
+ end
417
+
418
+ it 'default values are not returned' do
419
+ @defaults.each do |port|
420
+ expect(port.entries).to eq []
421
+ port.write 'mydata'
422
+ if port.mode == :memory
423
+ expect(port.entries).to eq []
424
+ else
425
+ expect(port.entries).to eq ['mydata']
426
+ end
427
+ end
428
+ end
429
+ end
430
+
431
+ describe '#redis_key' do
432
+ it 'returns key with valid name' do
433
+ expect(@port1.redis_key).to eq("#{@port1.job.redis_key}:in:port1")
434
+ end
435
+ end
436
+
437
+ describe '#hash' do
438
+ it 'uses hash of the redis key' do
439
+ expect(@port1.hash).to eq(@port1.redis_key.hash)
440
+ end
441
+
442
+ it 'can be used as keys in a hash' do
443
+ h = {}
444
+ h[@port1] = 1
445
+ port2 = SideJob::Port.new(@job, :in, 'port1')
446
+ expect(@port1.hash).to eq(port2.hash)
447
+ h[port2] = 3
448
+ expect(h.keys.length).to be(1)
449
+ expect(h[@port1]).to be(3)
450
+ end
451
+ end
452
+ end
@@ -0,0 +1,254 @@
1
+ require 'spec_helper'
2
+
3
+ describe SideJob::ServerMiddleware do
4
+ class TestWorkerShutdown
5
+ include SideJob::Worker
6
+ register
7
+ attr_accessor :shutdown_called
8
+ def shutdown
9
+ @shutdown_called = true
10
+ end
11
+ def perform
12
+ end
13
+ end
14
+
15
+ class TestWorkerShutdownError
16
+ include SideJob::Worker
17
+ register
18
+ def shutdown
19
+ raise 'shutdown error'
20
+ end
21
+ def perform
22
+ end
23
+ end
24
+
25
+ before do
26
+ @queue = 'testq'
27
+ @job = SideJob.queue(@queue, 'TestWorker')
28
+ end
29
+
30
+ def process(job)
31
+ chain = Sidekiq::Middleware::Chain.new
32
+ chain.add SideJob::ServerMiddleware
33
+ msg = Sidekiq::Queue.new(@queue).find_job(job.id)
34
+ worker = msg.klass.constantize.new
35
+ worker.jid = job.id
36
+ chain.invoke(worker, msg, @queue) { yield worker }
37
+ job.reload
38
+ worker
39
+ end
40
+
41
+ %w{running suspended completed failed terminated}.each do |status|
42
+ it "does not run if status is #{status}" do
43
+ @job.status = status
44
+ @run = false
45
+ process(@job) { @run = true}
46
+ expect(@run).to be false
47
+ expect(@job.status).to eq status
48
+ end
49
+ end
50
+
51
+ it 'does not run if job has been deleted' do
52
+ @job.status = 'terminated'
53
+ @job.delete
54
+ @run = false
55
+ process(@job) { @run = true}
56
+ expect(@run).to be false
57
+ end
58
+
59
+ describe 'handles a normal run' do
60
+ it 'sets status to running on start and completed on completion' do
61
+ process(@job) { |worker| @status = worker.status }
62
+ expect(@status).to eq 'running'
63
+ expect(@job.status).to eq 'completed'
64
+ end
65
+
66
+ it 'runs the parent job' do
67
+ @job.status = 'suspended'
68
+ child = SideJob.queue(@queue, 'TestWorker', parent: @job, name: 'child')
69
+ expect(@job.status).to eq 'suspended'
70
+ expect {
71
+ child.run_inline
72
+ }.to change {Sidekiq::Stats.new.enqueued}.by(1)
73
+ @job.reload
74
+ expect(@job.status).to eq 'queued'
75
+ end
76
+
77
+ it 'sets the ran_at time at the beginning of the run' do
78
+ now = Time.now
79
+ allow(Time).to receive(:now) { now }
80
+ process(@job) { |worker| @ran_at = worker.get(:ran_at) }
81
+ expect(@ran_at).to eq SideJob.timestamp
82
+ expect(@job.status).to eq 'completed'
83
+ end
84
+ end
85
+
86
+ describe 'prevents multiple threads running the same job' do
87
+ it 'sets the job lock to the current time' do
88
+ now = Time.now
89
+ allow(Time).to receive(:now) { now }
90
+ process(@job) { @lock = SideJob.redis.get("#{@job.redis_key}:lock").to_f }
91
+ expect(@lock).to eq(now.to_f)
92
+ expect(SideJob.redis.exists("#{@job.redis_key}:lock")).to be false
93
+ end
94
+
95
+ it 'sets the job lock to the current time and does not run if already locked' do
96
+ now = Time.now
97
+ allow(Time).to receive(:now) { now }
98
+ @run = false
99
+ SideJob.redis.set "#{@job.redis_key}:lock", (now-10).to_f
100
+ process(@job) { @run = true }
101
+ expect(@run).to be false
102
+ expect(SideJob.redis.get("#{@job.redis_key}:lock").to_f).to eq now.to_f
103
+ end
104
+
105
+ it 'does not do anything if the enqueued_at time is before the ran_at' do
106
+ process(@job) {|worker| worker.set ran_at: SideJob.timestamp}
107
+ @run = false
108
+ process(@job) {@run = true}
109
+ expect(@run).to be false
110
+ end
111
+
112
+ it 'does not restart the worker unless another worker was locked out during the run' do
113
+ expect {
114
+ process(@job) {}
115
+ }.to change {Sidekiq::Stats.new.enqueued}.by(0)
116
+ expect(@job.status).to eq 'completed'
117
+ end
118
+
119
+ it 'restarts the worker if another worker was locked out during the run' do
120
+ expect {
121
+ process(@job) { SideJob.redis.set "#{@job.redis_key}:lock", Time.now.to_f }
122
+ }.to change {Sidekiq::Stats.new.enqueued}.by(1)
123
+ expect(@job.status).to eq 'queued'
124
+ end
125
+ end
126
+
127
+ describe 'prevents job loops' do
128
+ it 'does not run if called too many times in a minute' do
129
+ now = Time.now
130
+ allow(Time).to receive(:now) { now }
131
+ key = "#{@job.redis_key}:rate:#{Time.now.to_i/60}"
132
+ SideJob.redis.set key, SideJob::ServerMiddleware::CONFIGURATION[:max_runs_per_minute]
133
+ @run = false
134
+ process(@job) { @run = true }
135
+ expect(@run).to be false
136
+ expect(@job.status).to eq 'terminated'
137
+ end
138
+
139
+ it 'does run if not called too many times in a minute' do
140
+ now = Time.now
141
+ allow(Time).to receive(:now) { now }
142
+ key = "#{@job.redis_key}:rate:#{Time.now.to_i/60}"
143
+ SideJob.redis.set key, SideJob::ServerMiddleware::CONFIGURATION[:max_runs_per_minute]-1
144
+ @run = false
145
+ process(@job) { @run = true }
146
+ expect(@run).to be true
147
+ expect(@job.status).to eq 'completed'
148
+ end
149
+
150
+ it 'does not run if job is too deep' do
151
+ (SideJob::ServerMiddleware::CONFIGURATION[:max_depth]+1).times do |i|
152
+ @job = SideJob.queue(@queue, 'TestWorker', parent: @job, name: 'child')
153
+ end
154
+ @run = false
155
+ process(@job) { @run = true }
156
+ expect(@run).to be false
157
+ expect(@job.status).to eq 'terminated'
158
+ end
159
+
160
+ it 'does run if job is not too deep' do
161
+ SideJob::ServerMiddleware::CONFIGURATION[:max_depth].times do |i|
162
+ @job = SideJob.queue(@queue, 'TestWorker', parent: @job, name: 'child')
163
+ end
164
+ @run = false
165
+ process(@job) { @run = true }
166
+ expect(@run).to be true
167
+ expect(@job.status).to eq 'completed'
168
+ end
169
+ end
170
+
171
+ describe 'handles worker exceptions' do
172
+ it 'sets status to failed on exception and logs error' do
173
+ now = Time.now
174
+ allow(Time).to receive(:now) { now }
175
+ process(@job) { raise 'oops' }
176
+ expect(@job.status).to eq 'failed'
177
+
178
+ log = SideJob.logs.select {|log| log['error'] }
179
+ expect(log.size).to eq(1)
180
+ expect(log[0]['job']).to eq @job.id
181
+ expect(log[0]['error']).to eq('oops')
182
+ # check that we trim down backtrace to remove sidekiq lines
183
+ expect(log[0]['backtrace']).to_not match(/sidekiq/)
184
+ end
185
+
186
+ it 'does not set status to failed if status is not running' do
187
+ process(@job) do |worker|
188
+ worker.run
189
+ raise 'oops'
190
+ end
191
+ expect(@job.status).to eq 'queued'
192
+ end
193
+
194
+ it 'runs the parent job' do
195
+ @job.status = 'suspended'
196
+ child = SideJob.queue(@queue, 'TestWorker', parent: @job, name: 'child')
197
+ expect {
198
+ process(child) { raise 'oops' }
199
+ }.to change {Sidekiq::Stats.new.enqueued}.by(1)
200
+ @job.reload
201
+ expect(@job.status).to eq 'queued'
202
+ end
203
+ end
204
+
205
+ describe 'handles worker suspend' do
206
+ it 'sets status to suspended' do
207
+ process(@job) { |worker| worker.suspend }
208
+ expect(@job.status).to eq 'suspended'
209
+ end
210
+
211
+ it 'does not set status to suspended if job was requeued' do
212
+ process(@job) do |worker|
213
+ worker.run
214
+ worker.suspend
215
+ end
216
+ expect(@job.status).to eq 'queued'
217
+ end
218
+ end
219
+
220
+ describe 'handles job termination' do
221
+ it 'sets status to terminated upon run' do
222
+ @job.status = 'terminating'
223
+ process(@job) { raise 'should not be called' }
224
+ expect(@job.status).to eq 'terminated'
225
+ errors = SideJob.logs.select {|log| log['type'] == 'error'}
226
+ expect(errors.size).to eq 0
227
+ end
228
+
229
+ it 'runs parent' do
230
+ child = SideJob.queue(@queue, 'TestWorker', parent: @job, name: 'child')
231
+ child.status = 'terminating'
232
+ process(child) { raise 'should not be called' }
233
+ expect(child.status).to eq 'terminated'
234
+ expect(@job.status).to eq 'queued'
235
+ end
236
+
237
+ it 'calls worker shutdown method' do
238
+ @job = SideJob.queue(@queue, 'TestWorkerShutdown')
239
+ @job.status = 'terminating'
240
+ worker = process(@job) { raise 'not reached' }
241
+ expect(worker.shutdown_called).to be true
242
+ end
243
+
244
+ it 'logs but ignores exceptions thrown during shutdown' do
245
+ @job = SideJob.queue(@queue, 'TestWorkerShutdownError')
246
+ @job.status = 'terminating'
247
+ worker = process(@job) { raise 'not reached' }
248
+ logs = SideJob.logs.select {|log| log['error']}
249
+ expect(logs.size).to eq 1
250
+ expect(logs[0]['job']).to eq @job.id
251
+ expect(logs[0]['error']).to eq 'shutdown error'
252
+ end
253
+ end
254
+ end