resque-loner 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,37 +1,37 @@
1
1
  require 'rubygems'
2
- require 'bundler'
3
- Bundler.setup(:default, :test)
4
- Bundler.require(:default, :test)
2
+ require 'bundler/setup'
5
3
 
6
- dir = File.dirname(File.expand_path(__FILE__))
7
- $LOAD_PATH.unshift dir + '/../lib'
4
+ $dir = File.dirname(File.expand_path(__FILE__))
5
+ $LOAD_PATH.unshift $dir + '/../lib'
8
6
  $TESTING = true
9
7
  require 'test/unit'
10
8
 
9
+ require 'redis/namespace'
10
+ require 'resque'
11
+ require 'resque-loner'
12
+
11
13
  begin
12
14
  require 'leftright'
13
15
  rescue LoadError
14
16
  end
15
17
 
16
-
17
18
  #
18
19
  # make sure we can run redis
19
20
  #
20
21
 
21
- if !system("which redis-server")
22
+ if !system('which redis-server')
22
23
  puts '', "** can't find `redis-server` in your path"
23
- puts "** try running `sudo rake install`"
24
+ puts '** try running `sudo rake install`'
24
25
  abort ''
25
26
  end
26
27
 
27
-
28
28
  #
29
29
  # start our own redis when the tests start,
30
30
  # kill it when they end
31
31
  #
32
32
 
33
33
  at_exit do
34
- next if $!
34
+ next if $ERROR_INFO
35
35
 
36
36
  if defined?(MiniTest)
37
37
  exit_code = MiniTest::Unit.new.run(ARGV)
@@ -39,17 +39,26 @@ at_exit do
39
39
  exit_code = Test::Unit::AutoRunner.run
40
40
  end
41
41
 
42
- pid = `ps -A -o pid,command | grep [r]edis-test`.split(" ")[0]
43
- puts "Killing test redis server..."
44
- `rm -f #{dir}/dump.rdb`
45
- Process.kill("KILL", pid.to_i)
42
+ processes = `ps -A -o pid,command | grep [r]edis-test`.split("\n")
43
+ pids = processes.map { |process| process.split(' ')[0] }
44
+ puts 'Killing test redis server...'
45
+ pids.each { |pid| Process.kill('TERM', pid.to_i) }
46
+ system("rm -f #{$dir}/dump.rdb #{$dir}/dump-cluster.rdb")
46
47
  exit exit_code
47
48
  end
48
49
 
49
- puts "Starting redis for testing at localhost:9736..."
50
- `redis-server #{dir}/redis-test.conf`
51
- Resque.redis = 'localhost:9736'
52
-
50
+ if ENV.key? 'RESQUE_DISTRIBUTED'
51
+ require 'redis/distributed'
52
+ puts 'Starting redis for testing at localhost:9736 and localhost:9737...'
53
+ `redis-server #{$dir}/redis-test.conf`
54
+ `redis-server #{$dir}/redis-test-cluster.conf`
55
+ r = Redis::Distributed.new(['redis://localhost:9736', 'redis://localhost:9737'])
56
+ Resque.redis = Redis::Namespace.new :resque, redis: r
57
+ else
58
+ puts 'Starting redis for testing at localhost:9736...'
59
+ `redis-server #{$dir}/redis-test.conf`
60
+ Resque.redis = 'localhost:9736'
61
+ end
53
62
 
54
63
  ##
55
64
  # test/spec/mini 3
@@ -61,13 +70,17 @@ def context(*args, &block)
61
70
  require 'test/unit'
62
71
  klass = Class.new(defined?(ActiveSupport::TestCase) ? ActiveSupport::TestCase : Test::Unit::TestCase) do
63
72
  def self.test(name, &block)
64
- define_method("test_#{name.gsub(/\W/,'_')}", &block) if block
73
+ define_method("test_#{name.gsub(/\W/, '_')}", &block) if block
65
74
  end
66
75
  def self.xtest(*args) end
67
- def self.setup(&block) define_method(:setup, &block) end
68
- def self.teardown(&block) define_method(:teardown, &block) end
76
+ def self.setup(&block)
77
+ define_method(:setup, &block)
78
+ end
79
+ def self.teardown(&block)
80
+ define_method(:teardown, &block)
81
+ end
69
82
  end
70
- (class << klass; self end).send(:define_method, :name) { name.gsub(/\W/,'_') }
83
+ (class << klass; self end).send(:define_method, :name) { name.gsub(/\W/, '_') }
71
84
  klass.class_eval &block
72
85
  # XXX: In 1.8.x, not all tests will run unless anonymous classes are kept in scope.
73
86
  ($test_classes ||= []) << klass
@@ -104,7 +117,7 @@ end
104
117
 
105
118
  class BadJob
106
119
  def self.perform
107
- raise "Bad job!"
120
+ fail 'Bad job!'
108
121
  end
109
122
  end
110
123
 
@@ -116,13 +129,13 @@ end
116
129
 
117
130
  class BadJobWithSyntaxError
118
131
  def self.perform
119
- raise SyntaxError, "Extra Bad job!"
132
+ fail SyntaxError, 'Extra Bad job!'
120
133
  end
121
134
  end
122
135
 
123
136
  class BadFailureBackend < Resque::Failure::Base
124
137
  def save
125
- raise Exception.new("Failure backend error")
138
+ fail Exception.new('Failure backend error')
126
139
  end
127
140
  end
128
141
 
@@ -134,15 +147,31 @@ ensure
134
147
  Resque::Failure.backend = previous_backend
135
148
  end
136
149
 
150
+ require 'time'
151
+
137
152
  class Time
138
153
  # Thanks, Timecop
139
154
  class << self
155
+ attr_accessor :fake_time
156
+
140
157
  alias_method :now_without_mock_time, :now
141
158
 
142
- def now_with_mock_time
143
- $fake_time || now_without_mock_time
159
+ def now
160
+ fake_time || now_without_mock_time
144
161
  end
145
-
146
- alias_method :now, :now_with_mock_time
147
162
  end
163
+
164
+ self.fake_time = nil
165
+ end
166
+
167
+ def capture_stderr
168
+ # The output stream must be an IO-like object. In this case we capture it in
169
+ # an in-memory IO object so we can return the string value. You can assign any
170
+ # IO object here.
171
+ previous_stderr, $stderr = $stderr, StringIO.new
172
+ yield
173
+ $stderr.string
174
+ ensure
175
+ # Restore the previous value of stderr (typically equal to STDERR).
176
+ $stderr = previous_stderr
148
177
  end
@@ -1,6 +1,6 @@
1
1
  require 'test_helper'
2
2
 
3
- context "Resque::Worker" do
3
+ context 'Resque::Worker' do
4
4
  setup do
5
5
  Resque.redis.flushall
6
6
 
@@ -12,34 +12,75 @@ context "Resque::Worker" do
12
12
  Resque::Job.create(:jobs, SomeJob, 20, '/tmp')
13
13
  end
14
14
 
15
- test "can fail jobs" do
15
+ test 'can fail jobs' do
16
16
  Resque::Job.create(:jobs, BadJob)
17
17
  @worker.work(0)
18
18
  assert_equal 1, Resque::Failure.count
19
19
  end
20
20
 
21
- test "failed jobs report exception and message" do
21
+ test 'failed jobs report exception and message' do
22
22
  Resque::Job.create(:jobs, BadJobWithSyntaxError)
23
23
  @worker.work(0)
24
24
  assert_equal('SyntaxError', Resque::Failure.all['exception'])
25
25
  assert_equal('Extra Bad job!', Resque::Failure.all['error'])
26
26
  end
27
27
 
28
- test "does not allow exceptions from failure backend to escape" do
28
+ test 'does not allow exceptions from failure backend to escape' do
29
29
  job = Resque::Job.new(:jobs, {})
30
30
  with_failure_backend BadFailureBackend do
31
31
  @worker.perform job
32
32
  end
33
33
  end
34
34
 
35
- test "fails uncompleted jobs on exit" do
36
- job = Resque::Job.new(:jobs, [GoodJob, "blah"])
35
+ test 'fails uncompleted jobs on exit' do
36
+ job = Resque::Job.new(:jobs, 'class' => 'GoodJob', 'args' => 'blah')
37
37
  @worker.working_on(job)
38
38
  @worker.unregister_worker
39
39
  assert_equal 1, Resque::Failure.count
40
40
  end
41
41
 
42
- test "can peek at failed jobs" do
42
+ class ::SimpleJobWithFailureHandling
43
+ def self.on_failure_record_failure(exception, *job_args)
44
+ @@exception = exception
45
+ end
46
+
47
+ def self.exception
48
+ @@exception
49
+ end
50
+ end
51
+
52
+ test 'fails uncompleted jobs on exit, and calls failure hook' do
53
+ job = Resque::Job.new(:jobs, 'class' => 'SimpleJobWithFailureHandling', 'args' => '')
54
+ @worker.working_on(job)
55
+ @worker.unregister_worker
56
+ assert_equal 1, Resque::Failure.count
57
+ assert(SimpleJobWithFailureHandling.exception.kind_of?(Resque::DirtyExit))
58
+ end
59
+
60
+ class ::SimpleFailingJob
61
+ @@exception_count = 0
62
+
63
+ def self.on_failure_record_failure(exception, *job_args)
64
+ @@exception_count += 1
65
+ end
66
+
67
+ def self.exception_count
68
+ @@exception_count
69
+ end
70
+
71
+ def self.perform
72
+ fail Exception.new
73
+ end
74
+ end
75
+
76
+ test 'only calls failure hook once on exception' do
77
+ job = Resque::Job.new(:jobs, 'class' => 'SimpleFailingJob', 'args' => '')
78
+ @worker.perform(job)
79
+ assert_equal 1, Resque::Failure.count
80
+ assert_equal 1, SimpleFailingJob.exception_count
81
+ end
82
+
83
+ test 'can peek at failed jobs' do
43
84
  10.times { Resque::Job.create(:jobs, BadJob) }
44
85
  @worker.work(0)
45
86
  assert_equal 10, Resque::Failure.count
@@ -47,7 +88,7 @@ context "Resque::Worker" do
47
88
  assert_equal 10, Resque::Failure.all(0, 20).size
48
89
  end
49
90
 
50
- test "can clear failed jobs" do
91
+ test 'can clear failed jobs' do
51
92
  Resque::Job.create(:jobs, BadJob)
52
93
  @worker.work(0)
53
94
  assert_equal 1, Resque::Failure.count
@@ -55,7 +96,7 @@ context "Resque::Worker" do
55
96
  assert_equal 0, Resque::Failure.count
56
97
  end
57
98
 
58
- test "catches exceptional jobs" do
99
+ test 'catches exceptional jobs' do
59
100
  Resque::Job.create(:jobs, BadJob)
60
101
  Resque::Job.create(:jobs, BadJob)
61
102
  @worker.process
@@ -64,13 +105,13 @@ context "Resque::Worker" do
64
105
  assert_equal 2, Resque::Failure.count
65
106
  end
66
107
 
67
- test "strips whitespace from queue names" do
68
- queues = "critical, high, low".split(',')
108
+ test 'strips whitespace from queue names' do
109
+ queues = 'critical, high, low'.split(',')
69
110
  worker = Resque::Worker.new(*queues)
70
111
  assert_equal %w( critical high low ), worker.queues
71
112
  end
72
113
 
73
- test "can work on multiple queues" do
114
+ test 'can work on multiple queues' do
74
115
  Resque::Job.create(:high, GoodJob)
75
116
  Resque::Job.create(:critical, GoodJob)
76
117
 
@@ -84,25 +125,55 @@ context "Resque::Worker" do
84
125
  assert_equal 0, Resque.size(:high)
85
126
  end
86
127
 
87
- test "can work on all queues" do
128
+ test 'can work on all queues' do
129
+ Resque::Job.create(:high, GoodJob)
130
+ Resque::Job.create(:critical, GoodJob)
131
+ Resque::Job.create(:blahblah, GoodJob)
132
+
133
+ worker = Resque::Worker.new('*')
134
+
135
+ worker.work(0)
136
+ assert_equal 0, Resque.size(:high)
137
+ assert_equal 0, Resque.size(:critical)
138
+ assert_equal 0, Resque.size(:blahblah)
139
+ end
140
+
141
+ test 'can work with wildcard at the end of the list' do
88
142
  Resque::Job.create(:high, GoodJob)
89
143
  Resque::Job.create(:critical, GoodJob)
90
144
  Resque::Job.create(:blahblah, GoodJob)
145
+ Resque::Job.create(:beer, GoodJob)
91
146
 
92
- worker = Resque::Worker.new("*")
147
+ worker = Resque::Worker.new(:critical, :high, '*')
93
148
 
94
149
  worker.work(0)
95
150
  assert_equal 0, Resque.size(:high)
96
151
  assert_equal 0, Resque.size(:critical)
97
152
  assert_equal 0, Resque.size(:blahblah)
153
+ assert_equal 0, Resque.size(:beer)
98
154
  end
99
155
 
100
- test "processes * queues in alphabetical order" do
156
+ test 'can work with wildcard at the middle of the list' do
101
157
  Resque::Job.create(:high, GoodJob)
102
158
  Resque::Job.create(:critical, GoodJob)
103
159
  Resque::Job.create(:blahblah, GoodJob)
160
+ Resque::Job.create(:beer, GoodJob)
161
+
162
+ worker = Resque::Worker.new(:critical, '*', :high)
104
163
 
105
- worker = Resque::Worker.new("*")
164
+ worker.work(0)
165
+ assert_equal 0, Resque.size(:high)
166
+ assert_equal 0, Resque.size(:critical)
167
+ assert_equal 0, Resque.size(:blahblah)
168
+ assert_equal 0, Resque.size(:beer)
169
+ end
170
+
171
+ test 'processes * queues in alphabetical order' do
172
+ Resque::Job.create(:high, GoodJob)
173
+ Resque::Job.create(:critical, GoodJob)
174
+ Resque::Job.create(:blahblah, GoodJob)
175
+
176
+ worker = Resque::Worker.new('*')
106
177
  processed_queues = []
107
178
 
108
179
  worker.work(0) do |job|
@@ -112,17 +183,17 @@ context "Resque::Worker" do
112
183
  assert_equal %w( jobs high critical blahblah ).sort, processed_queues
113
184
  end
114
185
 
115
- test "has a unique id" do
116
- assert_equal "#{`hostname`.chomp}:#{$$}:jobs", @worker.to_s
186
+ test 'has a unique id' do
187
+ assert_equal "#{`hostname`.chomp}:#{$PROCESS_ID}:jobs", @worker.to_s
117
188
  end
118
189
 
119
- test "complains if no queues are given" do
190
+ test 'complains if no queues are given' do
120
191
  assert_raise Resque::NoQueueError do
121
192
  Resque::Worker.new
122
193
  end
123
194
  end
124
195
 
125
- test "fails if a job class has no `perform` method" do
196
+ test 'fails if a job class has no `perform` method' do
126
197
  worker = Resque::Worker.new(:perform_less)
127
198
  Resque::Job.create(:perform_less, Object)
128
199
 
@@ -145,7 +216,7 @@ context "Resque::Worker" do
145
216
  assert_equal [], Resque.workers
146
217
  end
147
218
 
148
- test "removes worker with stringified id" do
219
+ test 'removes worker with stringified id' do
149
220
  @worker.work(0) do
150
221
  worker_id = Resque.workers[0].to_s
151
222
  Resque.remove_worker(worker_id)
@@ -153,38 +224,38 @@ context "Resque::Worker" do
153
224
  end
154
225
  end
155
226
 
156
- test "records what it is working on" do
227
+ test 'records what it is working on' do
157
228
  @worker.work(0) do
158
229
  task = @worker.job
159
- assert_equal({"args"=>[20, "/tmp"], "class"=>"SomeJob"}, task['payload'])
230
+ assert_equal({ 'args' => [20, '/tmp'], 'class' => 'SomeJob' }, task['payload'])
160
231
  assert task['run_at']
161
232
  assert_equal 'jobs', task['queue']
162
233
  end
163
234
  end
164
235
 
165
- test "clears its status when not working on anything" do
236
+ test 'clears its status when not working on anything' do
166
237
  @worker.work(0)
167
238
  assert_equal Hash.new, @worker.job
168
239
  end
169
240
 
170
- test "knows when it is working" do
241
+ test 'knows when it is working' do
171
242
  @worker.work(0) do
172
243
  assert @worker.working?
173
244
  end
174
245
  end
175
246
 
176
- test "knows when it is idle" do
247
+ test 'knows when it is idle' do
177
248
  @worker.work(0)
178
249
  assert @worker.idle?
179
250
  end
180
251
 
181
- test "knows who is working" do
252
+ test 'knows who is working' do
182
253
  @worker.work(0) do
183
254
  assert_equal [@worker], Resque.working
184
255
  end
185
256
  end
186
257
 
187
- test "keeps track of how many jobs it has processed" do
258
+ test 'keeps track of how many jobs it has processed' do
188
259
  Resque::Job.create(:jobs, BadJob)
189
260
  Resque::Job.create(:jobs, BadJob)
190
261
 
@@ -195,7 +266,7 @@ context "Resque::Worker" do
195
266
  assert_equal 3, @worker.processed
196
267
  end
197
268
 
198
- test "keeps track of how many failures it has seen" do
269
+ test 'keeps track of how many failures it has seen' do
199
270
  Resque::Job.create(:jobs, BadJob)
200
271
  Resque::Job.create(:jobs, BadJob)
201
272
 
@@ -206,34 +277,34 @@ context "Resque::Worker" do
206
277
  assert_equal 2, @worker.failed
207
278
  end
208
279
 
209
- test "stats are erased when the worker goes away" do
280
+ test 'stats are erased when the worker goes away' do
210
281
  @worker.work(0)
211
282
  assert_equal 0, @worker.processed
212
283
  assert_equal 0, @worker.failed
213
284
  end
214
285
 
215
- test "knows when it started" do
286
+ test 'knows when it started' do
216
287
  time = Time.now
217
288
  @worker.work(0) do
218
- assert_equal time.to_s, @worker.started.to_s
289
+ assert Time.parse(@worker.started) - time < 0.1
219
290
  end
220
291
  end
221
292
 
222
- test "knows whether it exists or not" do
293
+ test 'knows whether it exists or not' do
223
294
  @worker.work(0) do
224
295
  assert Resque::Worker.exists?(@worker)
225
296
  assert !Resque::Worker.exists?('blah-blah')
226
297
  end
227
298
  end
228
299
 
229
- test "sets $0 while working" do
300
+ test 'sets $0 while working' do
230
301
  @worker.work(0) do
231
302
  ver = Resque::Version
232
- assert_equal "resque-#{ver}: Processing jobs since #{Time.now.to_i}", $0
303
+ assert_equal "resque-#{ver}: Processing jobs since #{Time.now.to_i}", $PROGRAM_NAME
233
304
  end
234
305
  end
235
306
 
236
- test "can be found" do
307
+ test 'can be found' do
237
308
  @worker.work(0) do
238
309
  found = Resque::Worker.find(@worker.to_s)
239
310
  assert_equal @worker.to_s, found.to_s
@@ -249,7 +320,7 @@ context "Resque::Worker" do
249
320
  end
250
321
  end
251
322
 
252
- test "cleans up dead worker info on start (crash recovery)" do
323
+ test 'cleans up dead worker info on start (crash recovery)' do
253
324
  # first we fake out two dead workers
254
325
  workerA = Resque::Worker.new(:jobs)
255
326
  workerA.instance_variable_set(:@to_s, "#{`hostname`.chomp}:1:jobs")
@@ -267,15 +338,20 @@ context "Resque::Worker" do
267
338
  end
268
339
  end
269
340
 
270
- test "Processed jobs count" do
341
+ test 'worker_pids returns pids' do
342
+ known_workers = @worker.worker_pids
343
+ assert !known_workers.empty?
344
+ end
345
+
346
+ test 'Processed jobs count' do
271
347
  @worker.work(0)
272
348
  assert_equal 1, Resque.info[:processed]
273
349
  end
274
350
 
275
- test "Will call a before_first_fork hook only once" do
351
+ test 'Will call a before_first_fork hook only once' do
276
352
  Resque.redis.flushall
277
353
  $BEFORE_FORK_CALLED = 0
278
- Resque.before_first_fork = Proc.new { $BEFORE_FORK_CALLED += 1 }
354
+ Resque.before_first_fork = proc { $BEFORE_FORK_CALLED += 1 }
279
355
  workerA = Resque::Worker.new(:jobs)
280
356
  Resque::Job.create(:jobs, SomeJob, 20, '/tmp')
281
357
 
@@ -289,10 +365,10 @@ context "Resque::Worker" do
289
365
  # assert_equal 1, $BEFORE_FORK_CALLED
290
366
  end
291
367
 
292
- test "Will call a before_fork hook before forking" do
368
+ test 'Will call a before_fork hook before forking' do
293
369
  Resque.redis.flushall
294
370
  $BEFORE_FORK_CALLED = false
295
- Resque.before_fork = Proc.new { $BEFORE_FORK_CALLED = true }
371
+ Resque.before_fork = proc { $BEFORE_FORK_CALLED = true }
296
372
  workerA = Resque::Worker.new(:jobs)
297
373
 
298
374
  assert !$BEFORE_FORK_CALLED
@@ -301,23 +377,29 @@ context "Resque::Worker" do
301
377
  assert $BEFORE_FORK_CALLED
302
378
  end
303
379
 
304
- test "very verbose works in the afternoon" do
305
- require 'time'
306
- $last_puts = ""
307
- $fake_time = Time.parse("15:44:33 2011-03-02")
308
- singleton = class << @worker; self end
309
- singleton.send :define_method, :puts, lambda { |thing| $last_puts = thing }
380
+ test 'very verbose works in the afternoon' do
381
+ begin
382
+ require 'time'
383
+ last_puts = ''
384
+ Time.fake_time = Time.parse('15:44:33 2011-03-02')
310
385
 
311
- @worker.very_verbose = true
312
- @worker.log("some log text")
386
+ @worker.extend(Module.new do
387
+ define_method(:puts) { |thing| last_puts = thing }
388
+ end)
313
389
 
314
- assert_match /\*\* \[15:44:33 2011-03-02\] \d+: some log text/, $last_puts
390
+ @worker.very_verbose = true
391
+ @worker.log('some log text')
392
+
393
+ assert_match /\*\* \[15:44:33 2011-03-02\] \d+: some log text/, last_puts
394
+ ensure
395
+ Time.fake_time = nil
396
+ end
315
397
  end
316
398
 
317
- test "Will call an after_fork hook after forking" do
399
+ test 'Will call an after_fork hook after forking' do
318
400
  Resque.redis.flushall
319
401
  $AFTER_FORK_CALLED = false
320
- Resque.after_fork = Proc.new { $AFTER_FORK_CALLED = true }
402
+ Resque.after_fork = proc { $AFTER_FORK_CALLED = true }
321
403
  workerA = Resque::Worker.new(:jobs)
322
404
 
323
405
  assert !$AFTER_FORK_CALLED
@@ -326,7 +408,181 @@ context "Resque::Worker" do
326
408
  assert $AFTER_FORK_CALLED
327
409
  end
328
410
 
329
- test "returns PID of running process" do
330
- assert_equal @worker.to_s.split(":")[1].to_i, @worker.pid
411
+ test 'returns PID of running process' do
412
+ assert_equal @worker.to_s.split(':')[1].to_i, @worker.pid
413
+ end
414
+
415
+ test 'requeue failed queue' do
416
+ queue = 'good_job'
417
+ Resque::Failure.create(exception: Exception.new, worker: Resque::Worker.new(queue), queue: queue, payload: { 'class' => 'GoodJob' })
418
+ Resque::Failure.create(exception: Exception.new, worker: Resque::Worker.new(queue), queue: 'some_job', payload: { 'class' => 'SomeJob' })
419
+ Resque::Failure.requeue_queue(queue)
420
+ assert Resque::Failure.all(0).key?('retried_at')
421
+ assert !Resque::Failure.all(1).key?('retried_at')
422
+ end
423
+
424
+ test 'remove failed queue' do
425
+ queue = 'good_job'
426
+ queue2 = 'some_job'
427
+ Resque::Failure.create(exception: Exception.new, worker: Resque::Worker.new(queue), queue: queue, payload: { 'class' => 'GoodJob' })
428
+ Resque::Failure.create(exception: Exception.new, worker: Resque::Worker.new(queue2), queue: queue2, payload: { 'class' => 'SomeJob' })
429
+ Resque::Failure.create(exception: Exception.new, worker: Resque::Worker.new(queue), queue: queue, payload: { 'class' => 'GoodJob' })
430
+ Resque::Failure.remove_queue(queue)
431
+ assert_equal queue2, Resque::Failure.all(0)['queue']
432
+ assert_equal 1, Resque::Failure.count
433
+ end
434
+
435
+ test 'reconnects to redis after fork' do
436
+ original_connection = Resque.redis.client.connection.instance_variable_get('@sock')
437
+ @worker.work(0)
438
+ assert_not_equal original_connection, Resque.redis.client.connection.instance_variable_get('@sock')
439
+ end
440
+
441
+ if !defined?(RUBY_ENGINE) || defined?(RUBY_ENGINE) && RUBY_ENGINE != 'jruby'
442
+ test 'old signal handling is the default' do
443
+ rescue_time = nil
444
+
445
+ begin
446
+ class LongRunningJob
447
+ @queue = :long_running_job
448
+
449
+ def self.perform(run_time, rescue_time = nil)
450
+ Resque.redis.client.reconnect # get its own connection
451
+ Resque.redis.rpush('sigterm-test:start', Process.pid)
452
+ sleep run_time
453
+ Resque.redis.rpush('sigterm-test:result', 'Finished Normally')
454
+ rescue Resque::TermException => e
455
+ Resque.redis.rpush('sigterm-test:result', %Q(Caught SignalException: #{e.inspect}))
456
+ sleep rescue_time unless rescue_time.nil?
457
+ ensure
458
+ puts 'fuuuu'
459
+ Resque.redis.rpush('sigterm-test:final', 'exiting.')
460
+ end
461
+ end
462
+
463
+ Resque.enqueue(LongRunningJob, 5, rescue_time)
464
+
465
+ worker_pid = Kernel.fork do
466
+ # ensure we actually fork
467
+ $TESTING = false
468
+ # reconnect since we just forked
469
+ Resque.redis.client.reconnect
470
+
471
+ worker = Resque::Worker.new(:long_running_job)
472
+
473
+ worker.work(0)
474
+ exit!
475
+ end
476
+
477
+ # ensure the worker is started
478
+ start_status = Resque.redis.blpop('sigterm-test:start', 5)
479
+ assert_not_nil start_status
480
+ child_pid = start_status[1].to_i
481
+ assert_operator child_pid, :>, 0
482
+
483
+ # send signal to abort the worker
484
+ Process.kill('TERM', worker_pid)
485
+ Process.waitpid(worker_pid)
486
+
487
+ # wait to see how it all came down
488
+ result = Resque.redis.blpop('sigterm-test:result', 5)
489
+ assert_nil result
490
+
491
+ # ensure that the child pid is no longer running
492
+ child_still_running = !(`ps -p #{child_pid.to_s} -o pid=`).empty?
493
+ assert !child_still_running
494
+ ensure
495
+ remaining_keys = Resque.redis.keys('sigterm-test:*') || []
496
+ Resque.redis.del(*remaining_keys) unless remaining_keys.empty?
497
+ end
498
+ end
499
+ end
500
+
501
+ if !defined?(RUBY_ENGINE) || defined?(RUBY_ENGINE) && RUBY_ENGINE != 'jruby'
502
+ [SignalException, Resque::TermException].each do |exception|
503
+ {
504
+ 'cleanup occurs in allotted time' => nil,
505
+ 'cleanup takes too long' => 2
506
+ }.each do |scenario, rescue_time|
507
+ test "SIGTERM when #{scenario} while catching #{exception}" do
508
+ begin
509
+ eval("class LongRunningJob; @@exception = #{exception}; end")
510
+ class LongRunningJob
511
+ @queue = :long_running_job
512
+
513
+ def self.perform(run_time, rescue_time = nil)
514
+ Resque.redis.client.reconnect # get its own connection
515
+ Resque.redis.rpush('sigterm-test:start', Process.pid)
516
+ sleep run_time
517
+ Resque.redis.rpush('sigterm-test:result', 'Finished Normally')
518
+ rescue @@exception => e
519
+ Resque.redis.rpush('sigterm-test:result', %Q(Caught SignalException: #{e.inspect}))
520
+ sleep rescue_time unless rescue_time.nil?
521
+ ensure
522
+ Resque.redis.rpush('sigterm-test:final', 'exiting.')
523
+ end
524
+ end
525
+
526
+ Resque.enqueue(LongRunningJob, 5, rescue_time)
527
+
528
+ worker_pid = Kernel.fork do
529
+ # ensure we actually fork
530
+ $TESTING = false
531
+ # reconnect since we just forked
532
+ Resque.redis.client.reconnect
533
+
534
+ worker = Resque::Worker.new(:long_running_job)
535
+ worker.term_timeout = 1
536
+ worker.term_child = 1
537
+
538
+ worker.work(0)
539
+ exit!
540
+ end
541
+
542
+ # ensure the worker is started
543
+ start_status = Resque.redis.blpop('sigterm-test:start', 5)
544
+ assert_not_nil start_status
545
+ child_pid = start_status[1].to_i
546
+ assert_operator child_pid, :>, 0
547
+
548
+ # send signal to abort the worker
549
+ Process.kill('TERM', worker_pid)
550
+ Process.waitpid(worker_pid)
551
+
552
+ # wait to see how it all came down
553
+ result = Resque.redis.blpop('sigterm-test:result', 5)
554
+ assert_not_nil result
555
+ assert !result[1].start_with?('Finished Normally'), 'Job Finished normally. Sleep not long enough?'
556
+ assert result[1].start_with? 'Caught SignalException', 'Signal exception not raised in child.'
557
+
558
+ # ensure that the child pid is no longer running
559
+ child_still_running = !(`ps -p #{child_pid.to_s} -o pid=`).empty?
560
+ assert !child_still_running
561
+
562
+ # see if post-cleanup occurred. This should happen IFF the rescue_time is less than the term_timeout
563
+ post_cleanup_occurred = Resque.redis.lpop('sigterm-test:final')
564
+ assert post_cleanup_occurred, 'post cleanup did not occur. SIGKILL sent too early?' if rescue_time.nil?
565
+ assert !post_cleanup_occurred, 'post cleanup occurred. SIGKILL sent too late?' unless rescue_time.nil?
566
+
567
+ ensure
568
+ remaining_keys = Resque.redis.keys('sigterm-test:*') || []
569
+ Resque.redis.del(*remaining_keys) unless remaining_keys.empty?
570
+ end
571
+ end
572
+ end
573
+ end
574
+
575
+ test 'displays warning when not using term_child' do
576
+ stderr = capture_stderr { @worker.work(0) }
577
+
578
+ assert stderr.match(/^WARNING:/)
579
+ end
580
+
581
+ test 'it does not display warning when using term_child' do
582
+ @worker.term_child = '1'
583
+ stderr = capture_stderr { @worker.work(0) }
584
+
585
+ assert !stderr.match(/^WARNING:/)
586
+ end
331
587
  end
332
588
  end