resque-loner 1.2.1 → 1.3.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.
@@ -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