resque_sqs 1.25.2

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.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/HISTORY.md +467 -0
  3. data/LICENSE +20 -0
  4. data/README.markdown +866 -0
  5. data/Rakefile +70 -0
  6. data/bin/resque-sqs +81 -0
  7. data/bin/resque-sqs-web +27 -0
  8. data/lib/resque_sqs/errors.rb +13 -0
  9. data/lib/resque_sqs/failure/airbrake.rb +33 -0
  10. data/lib/resque_sqs/failure/base.rb +73 -0
  11. data/lib/resque_sqs/failure/multiple.rb +59 -0
  12. data/lib/resque_sqs/failure/redis.rb +108 -0
  13. data/lib/resque_sqs/failure/redis_multi_queue.rb +89 -0
  14. data/lib/resque_sqs/failure.rb +113 -0
  15. data/lib/resque_sqs/helpers.rb +107 -0
  16. data/lib/resque_sqs/job.rb +346 -0
  17. data/lib/resque_sqs/log_formatters/quiet_formatter.rb +7 -0
  18. data/lib/resque_sqs/log_formatters/verbose_formatter.rb +7 -0
  19. data/lib/resque_sqs/log_formatters/very_verbose_formatter.rb +8 -0
  20. data/lib/resque_sqs/logging.rb +18 -0
  21. data/lib/resque_sqs/plugin.rb +66 -0
  22. data/lib/resque_sqs/server/helpers.rb +52 -0
  23. data/lib/resque_sqs/server/public/favicon.ico +0 -0
  24. data/lib/resque_sqs/server/public/idle.png +0 -0
  25. data/lib/resque_sqs/server/public/jquery-1.3.2.min.js +19 -0
  26. data/lib/resque_sqs/server/public/jquery.relatize_date.js +95 -0
  27. data/lib/resque_sqs/server/public/poll.png +0 -0
  28. data/lib/resque_sqs/server/public/ranger.js +78 -0
  29. data/lib/resque_sqs/server/public/reset.css +44 -0
  30. data/lib/resque_sqs/server/public/style.css +91 -0
  31. data/lib/resque_sqs/server/public/working.png +0 -0
  32. data/lib/resque_sqs/server/test_helper.rb +19 -0
  33. data/lib/resque_sqs/server/views/error.erb +1 -0
  34. data/lib/resque_sqs/server/views/failed.erb +29 -0
  35. data/lib/resque_sqs/server/views/failed_job.erb +50 -0
  36. data/lib/resque_sqs/server/views/failed_queues_overview.erb +24 -0
  37. data/lib/resque_sqs/server/views/key_sets.erb +19 -0
  38. data/lib/resque_sqs/server/views/key_string.erb +11 -0
  39. data/lib/resque_sqs/server/views/layout.erb +44 -0
  40. data/lib/resque_sqs/server/views/next_more.erb +22 -0
  41. data/lib/resque_sqs/server/views/overview.erb +4 -0
  42. data/lib/resque_sqs/server/views/queues.erb +58 -0
  43. data/lib/resque_sqs/server/views/stats.erb +62 -0
  44. data/lib/resque_sqs/server/views/workers.erb +109 -0
  45. data/lib/resque_sqs/server/views/working.erb +72 -0
  46. data/lib/resque_sqs/server.rb +271 -0
  47. data/lib/resque_sqs/stat.rb +57 -0
  48. data/lib/resque_sqs/tasks.rb +83 -0
  49. data/lib/resque_sqs/vendor/utf8_util/utf8_util_18.rb +91 -0
  50. data/lib/resque_sqs/vendor/utf8_util/utf8_util_19.rb +5 -0
  51. data/lib/resque_sqs/vendor/utf8_util.rb +20 -0
  52. data/lib/resque_sqs/version.rb +3 -0
  53. data/lib/resque_sqs/worker.rb +779 -0
  54. data/lib/resque_sqs.rb +479 -0
  55. data/lib/tasks/redis_sqs.rake +161 -0
  56. data/lib/tasks/resque_sqs.rake +2 -0
  57. data/test/airbrake_test.rb +27 -0
  58. data/test/failure_base_test.rb +15 -0
  59. data/test/job_hooks_test.rb +465 -0
  60. data/test/job_plugins_test.rb +230 -0
  61. data/test/logging_test.rb +24 -0
  62. data/test/plugin_test.rb +116 -0
  63. data/test/redis-test-cluster.conf +115 -0
  64. data/test/redis-test.conf +115 -0
  65. data/test/resque-web_test.rb +59 -0
  66. data/test/resque_failure_redis_test.rb +19 -0
  67. data/test/resque_hook_test.rb +165 -0
  68. data/test/resque_test.rb +278 -0
  69. data/test/stdout +42 -0
  70. data/test/test_helper.rb +228 -0
  71. data/test/worker_test.rb +1080 -0
  72. metadata +202 -0
@@ -0,0 +1,1080 @@
1
+ require 'test_helper'
2
+ require 'tmpdir'
3
+
4
+ context "ResqueSqs::Worker" do
5
+ setup do
6
+ ResqueSqs.redis.flushall
7
+
8
+ ResqueSqs.before_first_fork = nil
9
+ ResqueSqs.before_fork = nil
10
+ ResqueSqs.after_fork = nil
11
+
12
+ @worker = ResqueSqs::Worker.new(:jobs)
13
+ ResqueSqs::Job.create(:jobs, SomeJob, 20, '/tmp')
14
+ end
15
+
16
+ test "can fail jobs" do
17
+ ResqueSqs::Job.create(:jobs, BadJob)
18
+ @worker.work(0)
19
+ assert_equal 1, ResqueSqs::Failure.count
20
+ end
21
+
22
+ test "failed jobs report exception and message" do
23
+ ResqueSqs::Job.create(:jobs, BadJobWithSyntaxError)
24
+ @worker.work(0)
25
+ assert_equal('SyntaxError', ResqueSqs::Failure.all['exception'])
26
+ assert_equal('Extra Bad job!', ResqueSqs::Failure.all['error'])
27
+ end
28
+
29
+ test "does not allow exceptions from failure backend to escape" do
30
+ job = ResqueSqs::Job.new(:jobs, {})
31
+ with_failure_backend BadFailureBackend do
32
+ @worker.perform job
33
+ end
34
+ end
35
+
36
+ test "does not raise exception for completed jobs" do
37
+ if worker_pid = Kernel.fork
38
+ Process.waitpid(worker_pid)
39
+ assert_equal 0, ResqueSqs::Failure.count
40
+ else
41
+ # ensure we actually fork
42
+ $TESTING = false
43
+ ResqueSqs.redis.client.reconnect
44
+ worker = ResqueSqs::Worker.new(:jobs)
45
+ suppress_warnings do
46
+ worker.work(0)
47
+ end
48
+ exit
49
+ end
50
+ end
51
+
52
+ test "executes at_exit hooks when configured with run_at_exit_hooks" do
53
+ tmpfile = File.join(Dir.tmpdir, "resque_at_exit_test_file")
54
+ FileUtils.rm_f tmpfile
55
+
56
+ if worker_pid = Kernel.fork
57
+ Process.waitpid(worker_pid)
58
+ assert File.exist?(tmpfile), "The file '#{tmpfile}' does not exist"
59
+ assert_equal "at_exit", File.open(tmpfile).read.strip
60
+ else
61
+ # ensure we actually fork
62
+ $TESTING = false
63
+ ResqueSqs.redis.client.reconnect
64
+ ResqueSqs::Job.create(:at_exit_jobs, AtExitJob, tmpfile)
65
+ worker = ResqueSqs::Worker.new(:at_exit_jobs)
66
+ worker.run_at_exit_hooks = true
67
+ suppress_warnings do
68
+ worker.work(0)
69
+ end
70
+ exit
71
+ end
72
+
73
+ end
74
+
75
+ class ::RaiseExceptionOnFailure
76
+
77
+ def self.on_failure_trhow_exception(exception,*args)
78
+ $TESTING = true
79
+ raise "The worker threw an exception"
80
+ end
81
+
82
+ def self.perform
83
+ ""
84
+ end
85
+ end
86
+
87
+ test "should not treat SystemExit as an exception in the child with run_at_exit_hooks == true" do
88
+
89
+ if worker_pid = Kernel.fork
90
+ Process.waitpid(worker_pid)
91
+ else
92
+ # ensure we actually fork
93
+ $TESTING = false
94
+ ResqueSqs.redis.client.reconnect
95
+ ResqueSqs::Job.create(:not_failing_job, RaiseExceptionOnFailure)
96
+ worker = ResqueSqs::Worker.new(:not_failing_job)
97
+ worker.run_at_exit_hooks = true
98
+ suppress_warnings do
99
+ worker.work(0)
100
+ end
101
+ exit
102
+ end
103
+
104
+ end
105
+
106
+
107
+ test "does not execute at_exit hooks by default" do
108
+ tmpfile = File.join(Dir.tmpdir, "resque_at_exit_test_file")
109
+ FileUtils.rm_f tmpfile
110
+
111
+ if worker_pid = Kernel.fork
112
+ Process.waitpid(worker_pid)
113
+ assert !File.exist?(tmpfile), "The file '#{tmpfile}' exists, at_exit hooks were run"
114
+ else
115
+ # ensure we actually fork
116
+ $TESTING = false
117
+ ResqueSqs.redis.client.reconnect
118
+ ResqueSqs::Job.create(:at_exit_jobs, AtExitJob, tmpfile)
119
+ worker = ResqueSqs::Worker.new(:at_exit_jobs)
120
+ suppress_warnings do
121
+ worker.work(0)
122
+ end
123
+ exit
124
+ end
125
+
126
+ end
127
+
128
+ test "does report failure for jobs with invalid payload" do
129
+ job = ResqueSqs::Job.new(:jobs, { 'class' => 'NotAValidJobClass', 'args' => '' })
130
+ @worker.perform job
131
+ assert_equal 1, ResqueSqs::Failure.count, 'failure not reported'
132
+ end
133
+
134
+ test "register 'run_at' time on UTC timezone in ISO8601 format" do
135
+ job = ResqueSqs::Job.new(:jobs, {'class' => 'GoodJob', 'args' => "blah"})
136
+ now = Time.now.utc.iso8601
137
+ @worker.working_on(job)
138
+ assert_equal now, @worker.processing['run_at']
139
+ end
140
+
141
+ test "fails uncompleted jobs with DirtyExit by default on exit" do
142
+ job = ResqueSqs::Job.new(:jobs, {'class' => 'GoodJob', 'args' => "blah"})
143
+ @worker.working_on(job)
144
+ @worker.unregister_worker
145
+ assert_equal 1, ResqueSqs::Failure.count
146
+ assert_equal('ResqueSqs::DirtyExit', ResqueSqs::Failure.all['exception'])
147
+ end
148
+
149
+ test "fails uncompleted jobs with worker exception on exit" do
150
+ job = ResqueSqs::Job.new(:jobs, {'class' => 'GoodJob', 'args' => "blah"})
151
+ @worker.working_on(job)
152
+ @worker.unregister_worker(StandardError.new)
153
+ assert_equal 1, ResqueSqs::Failure.count
154
+ assert_equal('StandardError', ResqueSqs::Failure.all['exception'])
155
+ end
156
+
157
+ class ::SimpleJobWithFailureHandling
158
+ def self.on_failure_record_failure(exception, *job_args)
159
+ @@exception = exception
160
+ end
161
+
162
+ def self.exception
163
+ @@exception
164
+ end
165
+ end
166
+
167
+ test "fails uncompleted jobs on exit, and calls failure hook" do
168
+ job = ResqueSqs::Job.new(:jobs, {'class' => 'SimpleJobWithFailureHandling', 'args' => ""})
169
+ @worker.working_on(job)
170
+ @worker.unregister_worker
171
+ assert_equal 1, ResqueSqs::Failure.count
172
+ assert(SimpleJobWithFailureHandling.exception.kind_of?(ResqueSqs::DirtyExit))
173
+ end
174
+
175
+ class ::SimpleFailingJob
176
+ @@exception_count = 0
177
+
178
+ def self.on_failure_record_failure(exception, *job_args)
179
+ @@exception_count += 1
180
+ end
181
+
182
+ def self.exception_count
183
+ @@exception_count
184
+ end
185
+
186
+ def self.perform
187
+ raise Exception.new
188
+ end
189
+ end
190
+
191
+ test "only calls failure hook once on exception" do
192
+ job = ResqueSqs::Job.new(:jobs, {'class' => 'SimpleFailingJob', 'args' => ""})
193
+ @worker.perform(job)
194
+ assert_equal 1, ResqueSqs::Failure.count
195
+ assert_equal 1, SimpleFailingJob.exception_count
196
+ end
197
+
198
+ test "can peek at failed jobs" do
199
+ 10.times { ResqueSqs::Job.create(:jobs, BadJob) }
200
+ @worker.work(0)
201
+ assert_equal 10, ResqueSqs::Failure.count
202
+
203
+ assert_equal 10, ResqueSqs::Failure.all(0, 20).size
204
+ end
205
+
206
+ test "can clear failed jobs" do
207
+ ResqueSqs::Job.create(:jobs, BadJob)
208
+ @worker.work(0)
209
+ assert_equal 1, ResqueSqs::Failure.count
210
+ ResqueSqs::Failure.clear
211
+ assert_equal 0, ResqueSqs::Failure.count
212
+ end
213
+
214
+ test "catches exceptional jobs" do
215
+ ResqueSqs::Job.create(:jobs, BadJob)
216
+ ResqueSqs::Job.create(:jobs, BadJob)
217
+ @worker.process
218
+ @worker.process
219
+ @worker.process
220
+ assert_equal 2, ResqueSqs::Failure.count
221
+ end
222
+
223
+ test "strips whitespace from queue names" do
224
+ queues = "critical, high, low".split(',')
225
+ worker = ResqueSqs::Worker.new(*queues)
226
+ assert_equal %w( critical high low ), worker.queues
227
+ end
228
+
229
+ test "can work on multiple queues" do
230
+ ResqueSqs::Job.create(:high, GoodJob)
231
+ ResqueSqs::Job.create(:critical, GoodJob)
232
+
233
+ worker = ResqueSqs::Worker.new(:critical, :high)
234
+
235
+ worker.process
236
+ assert_equal 1, ResqueSqs.size(:high)
237
+ assert_equal 0, ResqueSqs.size(:critical)
238
+
239
+ worker.process
240
+ assert_equal 0, ResqueSqs.size(:high)
241
+ end
242
+
243
+ test "can work on all queues" do
244
+ ResqueSqs::Job.create(:high, GoodJob)
245
+ ResqueSqs::Job.create(:critical, GoodJob)
246
+ ResqueSqs::Job.create(:blahblah, GoodJob)
247
+
248
+ worker = ResqueSqs::Worker.new("*")
249
+
250
+ worker.work(0)
251
+ assert_equal 0, ResqueSqs.size(:high)
252
+ assert_equal 0, ResqueSqs.size(:critical)
253
+ assert_equal 0, ResqueSqs.size(:blahblah)
254
+ end
255
+
256
+ test "can work with wildcard at the end of the list" do
257
+ ResqueSqs::Job.create(:high, GoodJob)
258
+ ResqueSqs::Job.create(:critical, GoodJob)
259
+ ResqueSqs::Job.create(:blahblah, GoodJob)
260
+ ResqueSqs::Job.create(:beer, GoodJob)
261
+
262
+ worker = ResqueSqs::Worker.new(:critical, :high, "*")
263
+
264
+ worker.work(0)
265
+ assert_equal 0, ResqueSqs.size(:high)
266
+ assert_equal 0, ResqueSqs.size(:critical)
267
+ assert_equal 0, ResqueSqs.size(:blahblah)
268
+ assert_equal 0, ResqueSqs.size(:beer)
269
+ end
270
+
271
+ test "can work with wildcard at the middle of the list" do
272
+ ResqueSqs::Job.create(:high, GoodJob)
273
+ ResqueSqs::Job.create(:critical, GoodJob)
274
+ ResqueSqs::Job.create(:blahblah, GoodJob)
275
+ ResqueSqs::Job.create(:beer, GoodJob)
276
+
277
+ worker = ResqueSqs::Worker.new(:critical, "*", :high)
278
+
279
+ worker.work(0)
280
+ assert_equal 0, ResqueSqs.size(:high)
281
+ assert_equal 0, ResqueSqs.size(:critical)
282
+ assert_equal 0, ResqueSqs.size(:blahblah)
283
+ assert_equal 0, ResqueSqs.size(:beer)
284
+ end
285
+
286
+ test "processes * queues in alphabetical order" do
287
+ ResqueSqs::Job.create(:high, GoodJob)
288
+ ResqueSqs::Job.create(:critical, GoodJob)
289
+ ResqueSqs::Job.create(:blahblah, GoodJob)
290
+
291
+ worker = ResqueSqs::Worker.new("*")
292
+ processed_queues = []
293
+
294
+ worker.work(0) do |job|
295
+ processed_queues << job.queue
296
+ end
297
+
298
+ assert_equal %w( jobs high critical blahblah ).sort, processed_queues
299
+ end
300
+
301
+ test "works with globs" do
302
+ ResqueSqs::Job.create(:critical, GoodJob)
303
+ ResqueSqs::Job.create(:test_one, GoodJob)
304
+ ResqueSqs::Job.create(:test_two, GoodJob)
305
+
306
+ worker = ResqueSqs::Worker.new("test_*")
307
+
308
+ worker.work(0)
309
+ assert_equal 1, ResqueSqs.size(:critical)
310
+ assert_equal 0, ResqueSqs.size(:test_one)
311
+ assert_equal 0, ResqueSqs.size(:test_two)
312
+ end
313
+
314
+ test "has a unique id" do
315
+ assert_equal "#{`hostname`.chomp}:#{$$}:jobs", @worker.to_s
316
+ end
317
+
318
+ test "complains if no queues are given" do
319
+ assert_raise ResqueSqs::NoQueueError do
320
+ ResqueSqs::Worker.new
321
+ end
322
+ end
323
+
324
+ test "fails if a job class has no `perform` method" do
325
+ worker = ResqueSqs::Worker.new(:perform_less)
326
+ ResqueSqs::Job.create(:perform_less, Object)
327
+
328
+ assert_equal 0, ResqueSqs::Failure.count
329
+ worker.work(0)
330
+ assert_equal 1, ResqueSqs::Failure.count
331
+ end
332
+
333
+ test "inserts itself into the 'workers' list on startup" do
334
+ @worker.extend(AssertInWorkBlock).work(0) do
335
+ assert_equal @worker, ResqueSqs.workers[0]
336
+ end
337
+ end
338
+
339
+ test "removes itself from the 'workers' list on shutdown" do
340
+ @worker.extend(AssertInWorkBlock).work(0) do
341
+ assert_equal @worker, ResqueSqs.workers[0]
342
+ end
343
+
344
+ assert_equal [], ResqueSqs.workers
345
+ end
346
+
347
+ test "removes worker with stringified id" do
348
+ @worker.extend(AssertInWorkBlock).work(0) do
349
+ worker_id = ResqueSqs.workers[0].to_s
350
+ ResqueSqs.remove_worker(worker_id)
351
+ assert_equal [], ResqueSqs.workers
352
+ end
353
+ end
354
+
355
+ test "records what it is working on" do
356
+ @worker.extend(AssertInWorkBlock).work(0) do
357
+ task = @worker.job
358
+ assert_equal({"args"=>[20, "/tmp"], "class"=>"SomeJob"}, task['payload'])
359
+ assert task['run_at']
360
+ assert_equal 'jobs', task['queue']
361
+ end
362
+ end
363
+
364
+ test "clears its status when not working on anything" do
365
+ @worker.work(0)
366
+ assert_equal Hash.new, @worker.job
367
+ end
368
+
369
+ test "knows when it is working" do
370
+ @worker.extend(AssertInWorkBlock).work(0) do
371
+ assert @worker.working?
372
+ end
373
+ end
374
+
375
+ test "knows when it is idle" do
376
+ @worker.work(0)
377
+ assert @worker.idle?
378
+ end
379
+
380
+ test "knows who is working" do
381
+ @worker.extend(AssertInWorkBlock).work(0) do
382
+ assert_equal [@worker], ResqueSqs.working
383
+ end
384
+ end
385
+
386
+ test "keeps track of how many jobs it has processed" do
387
+ ResqueSqs::Job.create(:jobs, BadJob)
388
+ ResqueSqs::Job.create(:jobs, BadJob)
389
+
390
+ 3.times do
391
+ job = @worker.reserve
392
+ @worker.process job
393
+ end
394
+ assert_equal 3, @worker.processed
395
+ end
396
+
397
+ test "keeps track of how many failures it has seen" do
398
+ ResqueSqs::Job.create(:jobs, BadJob)
399
+ ResqueSqs::Job.create(:jobs, BadJob)
400
+
401
+ 3.times do
402
+ job = @worker.reserve
403
+ @worker.process job
404
+ end
405
+ assert_equal 2, @worker.failed
406
+ end
407
+
408
+ test "stats are erased when the worker goes away" do
409
+ @worker.work(0)
410
+ assert_equal 0, @worker.processed
411
+ assert_equal 0, @worker.failed
412
+ end
413
+
414
+ test "knows when it started" do
415
+ time = Time.now
416
+ @worker.extend(AssertInWorkBlock).work(0) do
417
+ assert Time.parse(@worker.started) - time < 0.1
418
+ end
419
+ end
420
+
421
+ test "knows whether it exists or not" do
422
+ @worker.extend(AssertInWorkBlock).work(0) do
423
+ assert ResqueSqs::Worker.exists?(@worker)
424
+ assert !ResqueSqs::Worker.exists?('blah-blah')
425
+ end
426
+ end
427
+
428
+ test "sets $0 while working" do
429
+ @worker.extend(AssertInWorkBlock).work(0) do
430
+ ver = ResqueSqs::Version
431
+ assert_equal "resque-#{ver}: Processing jobs since #{Time.now.to_i} [SomeJob]", $0
432
+ end
433
+ end
434
+
435
+ test "can be found" do
436
+ @worker.extend(AssertInWorkBlock).work(0) do
437
+ found = ResqueSqs::Worker.find(@worker.to_s)
438
+ assert_equal @worker.to_s, found.to_s
439
+ assert found.working?
440
+ assert_equal @worker.job, found.job
441
+ end
442
+ end
443
+
444
+ test "doesn't find fakes" do
445
+ @worker.extend(AssertInWorkBlock).work(0) do
446
+ found = ResqueSqs::Worker.find('blah-blah')
447
+ assert_equal nil, found
448
+ end
449
+ end
450
+
451
+ test "cleans up dead worker info on start (crash recovery)" do
452
+ # first we fake out several dead workers
453
+ # 1: matches queue and hostname; gets pruned.
454
+ workerA = ResqueSqs::Worker.new(:jobs)
455
+ workerA.instance_variable_set(:@to_s, "#{`hostname`.chomp}:1:jobs")
456
+ workerA.register_worker
457
+
458
+ # 2. matches queue but not hostname; no prune.
459
+ workerB = ResqueSqs::Worker.new(:jobs)
460
+ workerB.instance_variable_set(:@to_s, "#{`hostname`.chomp}-foo:2:jobs")
461
+ workerB.register_worker
462
+
463
+ # 3. matches hostname but not queue; no prune.
464
+ workerB = ResqueSqs::Worker.new(:high)
465
+ workerB.instance_variable_set(:@to_s, "#{`hostname`.chomp}:3:high")
466
+ workerB.register_worker
467
+
468
+ # 4. matches neither hostname nor queue; no prune.
469
+ workerB = ResqueSqs::Worker.new(:high)
470
+ workerB.instance_variable_set(:@to_s, "#{`hostname`.chomp}-foo:4:high")
471
+ workerB.register_worker
472
+
473
+ assert_equal 4, ResqueSqs.workers.size
474
+
475
+ # then we prune them
476
+ @worker.work(0)
477
+
478
+ worker_strings = ResqueSqs::Worker.all.map(&:to_s)
479
+
480
+ assert_equal 3, ResqueSqs.workers.size
481
+
482
+ # pruned
483
+ assert !worker_strings.include?("#{`hostname`.chomp}:1:jobs")
484
+
485
+ # not pruned
486
+ assert worker_strings.include?("#{`hostname`.chomp}-foo:2:jobs")
487
+ assert worker_strings.include?("#{`hostname`.chomp}:3:high")
488
+ assert worker_strings.include?("#{`hostname`.chomp}-foo:4:high")
489
+ end
490
+
491
+ test "worker_pids returns pids" do
492
+ known_workers = @worker.worker_pids
493
+ assert !known_workers.empty?
494
+ end
495
+
496
+ test "Processed jobs count" do
497
+ @worker.work(0)
498
+ assert_equal 1, ResqueSqs.info[:processed]
499
+ end
500
+
501
+ test "Will call a before_first_fork hook only once" do
502
+ ResqueSqs.redis.flushall
503
+ $BEFORE_FORK_CALLED = 0
504
+ ResqueSqs.before_first_fork = Proc.new { $BEFORE_FORK_CALLED += 1 }
505
+ workerA = ResqueSqs::Worker.new(:jobs)
506
+ ResqueSqs::Job.create(:jobs, SomeJob, 20, '/tmp')
507
+
508
+ assert_equal 0, $BEFORE_FORK_CALLED
509
+
510
+ workerA.work(0)
511
+ assert_equal 1, $BEFORE_FORK_CALLED
512
+
513
+ # TODO: Verify it's only run once. Not easy.
514
+ # workerA.work(0)
515
+ # assert_equal 1, $BEFORE_FORK_CALLED
516
+ end
517
+
518
+ test "Will call a before_fork hook before forking" do
519
+ ResqueSqs.redis.flushall
520
+ $BEFORE_FORK_CALLED = false
521
+ ResqueSqs.before_fork = Proc.new { $BEFORE_FORK_CALLED = true }
522
+ workerA = ResqueSqs::Worker.new(:jobs)
523
+
524
+ assert !$BEFORE_FORK_CALLED
525
+ ResqueSqs::Job.create(:jobs, SomeJob, 20, '/tmp')
526
+ workerA.work(0)
527
+ assert $BEFORE_FORK_CALLED
528
+ end
529
+
530
+ test "Will not call a before_fork hook when the worker can't fork" do
531
+ ResqueSqs.redis.flushall
532
+ $BEFORE_FORK_CALLED = false
533
+ ResqueSqs.before_fork = Proc.new { $BEFORE_FORK_CALLED = true }
534
+ workerA = ResqueSqs::Worker.new(:jobs)
535
+ workerA.cant_fork = true
536
+
537
+ assert !$BEFORE_FORK_CALLED, "before_fork should not have been called before job runs"
538
+ ResqueSqs::Job.create(:jobs, SomeJob, 20, '/tmp')
539
+ workerA.work(0)
540
+ assert !$BEFORE_FORK_CALLED, "before_fork should not have been called after job runs"
541
+ end
542
+
543
+ test "setting verbose to true" do
544
+ @worker.verbose = true
545
+
546
+ assert @worker.verbose
547
+ assert !@worker.very_verbose
548
+ end
549
+
550
+ test "setting verbose to false" do
551
+ @worker.verbose = false
552
+
553
+ assert !@worker.verbose
554
+ assert !@worker.very_verbose
555
+ end
556
+
557
+ test "setting very_verbose to true" do
558
+ @worker.very_verbose = true
559
+
560
+ assert !@worker.verbose
561
+ assert @worker.very_verbose
562
+ end
563
+
564
+ test "setting setting verbose to true and then very_verbose to false" do
565
+ @worker.very_verbose = true
566
+ @worker.verbose = true
567
+ @worker.very_verbose = false
568
+
569
+ assert @worker.verbose
570
+ assert !@worker.very_verbose
571
+ end
572
+
573
+ test "verbose prints out logs" do
574
+ messages = StringIO.new
575
+ ResqueSqs.logger = Logger.new(messages)
576
+ @worker.verbose = true
577
+
578
+ begin
579
+ @worker.log("omghi mom")
580
+ ensure
581
+ reset_logger
582
+ end
583
+
584
+ assert_equal "*** omghi mom\n", messages.string
585
+ end
586
+
587
+ test "unsetting verbose works" do
588
+ messages = StringIO.new
589
+ ResqueSqs.logger = Logger.new(messages)
590
+ @worker.verbose = true
591
+ @worker.verbose = false
592
+
593
+ begin
594
+ @worker.log("omghi mom")
595
+ ensure
596
+ reset_logger
597
+ end
598
+
599
+ assert_equal "", messages.string
600
+ end
601
+
602
+ test "very verbose works in the afternoon" do
603
+ messages = StringIO.new
604
+ ResqueSqs.logger = Logger.new(messages)
605
+
606
+ begin
607
+ require 'time'
608
+ last_puts = ""
609
+ Time.fake_time = Time.parse("15:44:33 2011-03-02")
610
+
611
+ @worker.very_verbose = true
612
+ @worker.log("some log text")
613
+
614
+ assert_match /\*\* \[15:44:33 2011-03-02\] \d+: some log text/, messages.string
615
+ ensure
616
+ Time.fake_time = nil
617
+ reset_logger
618
+ end
619
+ end
620
+
621
+ test "won't fork if ENV['FORK_PER_JOB'] is false" do
622
+ begin
623
+ $TESTING = false
624
+ workerA = ResqueSqs::Worker.new(:jobs)
625
+
626
+ if workerA.will_fork?
627
+ begin
628
+ ENV["FORK_PER_JOB"] = 'false'
629
+ assert !workerA.will_fork?
630
+ ensure
631
+ ENV["FORK_PER_JOB"] = 'true'
632
+ end
633
+ end
634
+ ensure
635
+ $TESTING = true
636
+ end
637
+ end
638
+
639
+ test "Will call an after_fork hook if we're forking" do
640
+ ResqueSqs.redis.flushall
641
+ $AFTER_FORK_CALLED = false
642
+ ResqueSqs.after_fork = Proc.new { $AFTER_FORK_CALLED = true }
643
+ workerA = ResqueSqs::Worker.new(:jobs)
644
+
645
+ assert !$AFTER_FORK_CALLED
646
+ ResqueSqs::Job.create(:jobs, SomeJob, 20, '/tmp')
647
+ workerA.work(0)
648
+ assert $AFTER_FORK_CALLED == workerA.will_fork?
649
+ end
650
+
651
+ test "Will not call an after_fork hook when the worker can't fork" do
652
+ ResqueSqs.redis.flushall
653
+ $AFTER_FORK_CALLED = false
654
+ ResqueSqs.after_fork = Proc.new { $AFTER_FORK_CALLED = true }
655
+ workerA = ResqueSqs::Worker.new(:jobs)
656
+ workerA.cant_fork = true
657
+
658
+ assert !$AFTER_FORK_CALLED
659
+ ResqueSqs::Job.create(:jobs, SomeJob, 20, '/tmp')
660
+ workerA.work(0)
661
+ assert !$AFTER_FORK_CALLED
662
+ end
663
+
664
+ test "returns PID of running process" do
665
+ assert_equal @worker.to_s.split(":")[1].to_i, @worker.pid
666
+ end
667
+
668
+ test "requeue failed queue" do
669
+ queue = 'good_job'
670
+ ResqueSqs::Failure.create(:exception => Exception.new, :worker => ResqueSqs::Worker.new(queue), :queue => queue, :payload => {'class' => 'GoodJob'})
671
+ ResqueSqs::Failure.create(:exception => Exception.new, :worker => ResqueSqs::Worker.new(queue), :queue => 'some_job', :payload => {'class' => 'SomeJob'})
672
+ ResqueSqs::Failure.requeue_queue(queue)
673
+ assert ResqueSqs::Failure.all(0).has_key?('retried_at')
674
+ assert !ResqueSqs::Failure.all(1).has_key?('retried_at')
675
+ end
676
+
677
+ test "remove failed queue" do
678
+ queue = 'good_job'
679
+ queue2 = 'some_job'
680
+ ResqueSqs::Failure.create(:exception => Exception.new, :worker => ResqueSqs::Worker.new(queue), :queue => queue, :payload => {'class' => 'GoodJob'})
681
+ ResqueSqs::Failure.create(:exception => Exception.new, :worker => ResqueSqs::Worker.new(queue2), :queue => queue2, :payload => {'class' => 'SomeJob'})
682
+ ResqueSqs::Failure.create(:exception => Exception.new, :worker => ResqueSqs::Worker.new(queue), :queue => queue, :payload => {'class' => 'GoodJob'})
683
+ ResqueSqs::Failure.remove_queue(queue)
684
+ assert_equal queue2, ResqueSqs::Failure.all(0)['queue']
685
+ assert_equal 1, ResqueSqs::Failure.count
686
+ end
687
+
688
+ test "reconnects to redis after fork" do
689
+ original_connection = ResqueSqs.redis.client.connection.instance_variable_get("@sock")
690
+ @worker.work(0)
691
+ assert_not_equal original_connection, ResqueSqs.redis.client.connection.instance_variable_get("@sock")
692
+ end
693
+
694
+ test "tries to reconnect three times before giving up and the failure does not unregister the parent" do
695
+ begin
696
+ class Redis::Client
697
+ alias_method :original_reconnect, :reconnect
698
+
699
+ def reconnect
700
+ raise Redis::BaseConnectionError
701
+ end
702
+ end
703
+
704
+ class ResqueSqs::Worker
705
+ alias_method :original_sleep, :sleep
706
+
707
+ def sleep(duration = nil)
708
+ # noop
709
+ end
710
+ end
711
+
712
+ stdout, stderr = capture_io do
713
+ ResqueSqs.logger = Logger.new($stdout)
714
+ @worker.work(0)
715
+ end
716
+
717
+ assert_equal 3, stdout.scan(/retrying/).count
718
+ assert_equal 1, stdout.scan(/quitting/).count
719
+ assert_equal 0, stdout.scan(/Failed to start worker/).count
720
+ assert_equal 1, stdout.scan(/Redis::BaseConnectionError: Redis::BaseConnectionError/).count
721
+
722
+ ensure
723
+ class Redis::Client
724
+ alias_method :reconnect, :original_reconnect
725
+ end
726
+
727
+ class ResqueSqs::Worker
728
+ alias_method :sleep, :original_sleep
729
+ end
730
+ end
731
+ end
732
+
733
+ test "will call before_pause before it is paused" do
734
+ before_pause_called = false
735
+ captured_worker = nil
736
+ begin
737
+ class Redis::Client
738
+ alias_method :original_reconnect, :reconnect
739
+
740
+ def reconnect
741
+ raise Redis::BaseConnectionError
742
+ end
743
+ end
744
+
745
+ class ResqueSqs::Worker
746
+ alias_method :original_sleep, :sleep
747
+
748
+ def sleep(duration = nil)
749
+ # noop
750
+ end
751
+ end
752
+
753
+ class DummyLogger
754
+ attr_reader :messages
755
+
756
+ def initialize
757
+ @messages = []
758
+ end
759
+
760
+ def info(message); @messages << message; end
761
+ alias_method :debug, :info
762
+ alias_method :warn, :info
763
+ alias_method :error, :info
764
+ alias_method :fatal, :info
765
+ end
766
+
767
+ ResqueSqs.logger = DummyLogger.new
768
+ begin
769
+ @worker.work(0)
770
+ messages = ResqueSqs.logger.messages
771
+ ensure
772
+ reset_logger
773
+ end
774
+
775
+ assert_equal 3, messages.grep(/retrying/).count
776
+ assert_equal 1, messages.grep(/quitting/).count
777
+ ensure
778
+ class Redis::Client
779
+ alias_method :reconnect, :original_reconnect
780
+ end
781
+
782
+ class ResqueSqs::Worker
783
+ alias_method :sleep, :original_sleep
784
+ end
785
+ end
786
+ end
787
+
788
+ if !defined?(RUBY_ENGINE) || defined?(RUBY_ENGINE) && RUBY_ENGINE != "jruby"
789
+ test "old signal handling is the default" do
790
+ rescue_time = nil
791
+
792
+ begin
793
+ class LongRunningJob
794
+ @queue = :long_running_job
795
+
796
+ def self.perform( run_time, rescue_time=nil )
797
+ ResqueSqs.redis.client.reconnect # get its own connection
798
+ ResqueSqs.redis.rpush( 'sigterm-test:start', Process.pid )
799
+ sleep run_time
800
+ ResqueSqs.redis.rpush( 'sigterm-test:result', 'Finished Normally' )
801
+ rescue ResqueSqs::TermException => e
802
+ ResqueSqs.redis.rpush( 'sigterm-test:result', %Q(Caught SignalException: #{e.inspect}))
803
+ sleep rescue_time unless rescue_time.nil?
804
+ ensure
805
+ puts 'fuuuu'
806
+ ResqueSqs.redis.rpush( 'sigterm-test:final', 'exiting.' )
807
+ end
808
+ end
809
+
810
+ ResqueSqs.enqueue( LongRunningJob, 5, rescue_time )
811
+
812
+ worker_pid = Kernel.fork do
813
+ # ensure we actually fork
814
+ $TESTING = false
815
+ # reconnect since we just forked
816
+ ResqueSqs.redis.client.reconnect
817
+
818
+ worker = ResqueSqs::Worker.new(:long_running_job)
819
+
820
+ suppress_warnings do
821
+ worker.work(0)
822
+ end
823
+ exit!
824
+ end
825
+
826
+ # ensure the worker is started
827
+ start_status = ResqueSqs.redis.blpop( 'sigterm-test:start', 5 )
828
+ assert_not_nil start_status
829
+ child_pid = start_status[1].to_i
830
+ assert_operator child_pid, :>, 0
831
+
832
+ # send signal to abort the worker
833
+ Process.kill('TERM', worker_pid)
834
+ Process.waitpid(worker_pid)
835
+
836
+ # wait to see how it all came down
837
+ result = ResqueSqs.redis.blpop( 'sigterm-test:result', 5 )
838
+ assert_nil result
839
+
840
+ # ensure that the child pid is no longer running
841
+ child_not_running = `ps -p #{child_pid.to_s} -o pid=`.empty?
842
+ assert child_not_running
843
+ ensure
844
+ remaining_keys = ResqueSqs.redis.keys('sigterm-test:*') || []
845
+ ResqueSqs.redis.del(*remaining_keys) unless remaining_keys.empty?
846
+ end
847
+ end
848
+ end
849
+
850
+ if !defined?(RUBY_ENGINE) || defined?(RUBY_ENGINE) && RUBY_ENGINE != "jruby"
851
+ [SignalException, ResqueSqs::TermException].each do |exception|
852
+ {
853
+ 'cleanup occurs in allotted time' => nil,
854
+ 'cleanup takes too long' => 2
855
+ }.each do |scenario,rescue_time|
856
+ test "SIGTERM when #{scenario} while catching #{exception}" do
857
+ begin
858
+ eval("class LongRunningJob; @@exception = #{exception}; end")
859
+ class LongRunningJob
860
+ @queue = :long_running_job
861
+
862
+ def self.perform( run_time, rescue_time=nil )
863
+ ResqueSqs.redis.client.reconnect # get its own connection
864
+ ResqueSqs.redis.rpush( 'sigterm-test:start', Process.pid )
865
+ sleep run_time
866
+ ResqueSqs.redis.rpush( 'sigterm-test:result', 'Finished Normally' )
867
+ rescue @@exception => e
868
+ ResqueSqs.redis.rpush( 'sigterm-test:result', %Q(Caught SignalException: #{e.inspect}))
869
+ sleep rescue_time unless rescue_time.nil?
870
+ ensure
871
+ ResqueSqs.redis.rpush( 'sigterm-test:final', 'exiting.' )
872
+ end
873
+ end
874
+
875
+ ResqueSqs.enqueue( LongRunningJob, 5, rescue_time )
876
+
877
+ worker_pid = Kernel.fork do
878
+ # ensure we actually fork
879
+ $TESTING = false
880
+ # reconnect since we just forked
881
+ ResqueSqs.redis.client.reconnect
882
+
883
+ worker = ResqueSqs::Worker.new(:long_running_job)
884
+ worker.term_timeout = 1
885
+ worker.term_child = 1
886
+
887
+ worker.work(0)
888
+ exit!
889
+ end
890
+
891
+ # ensure the worker is started
892
+ start_status = ResqueSqs.redis.blpop( 'sigterm-test:start', 5 )
893
+ assert_not_nil start_status
894
+ child_pid = start_status[1].to_i
895
+ assert_operator child_pid, :>, 0
896
+
897
+ # send signal to abort the worker
898
+ Process.kill('TERM', worker_pid)
899
+ Process.waitpid(worker_pid)
900
+
901
+ # wait to see how it all came down
902
+ result = ResqueSqs.redis.blpop( 'sigterm-test:result', 5 )
903
+ assert_not_nil result
904
+ assert !result[1].start_with?('Finished Normally'), 'Job Finished normally. Sleep not long enough?'
905
+ assert result[1].start_with? 'Caught SignalException', 'Signal exception not raised in child.'
906
+
907
+ # ensure that the child pid is no longer running
908
+ child_still_running = !(`ps -p #{child_pid.to_s} -o pid=`).empty?
909
+ assert !child_still_running
910
+
911
+ # see if post-cleanup occurred. This should happen IFF the rescue_time is less than the term_timeout
912
+ post_cleanup_occurred = ResqueSqs.redis.lpop( 'sigterm-test:final' )
913
+ assert post_cleanup_occurred, 'post cleanup did not occur. SIGKILL sent too early?' if rescue_time.nil?
914
+ assert !post_cleanup_occurred, 'post cleanup occurred. SIGKILL sent too late?' unless rescue_time.nil?
915
+
916
+ ensure
917
+ remaining_keys = ResqueSqs.redis.keys('sigterm-test:*') || []
918
+ ResqueSqs.redis.del(*remaining_keys) unless remaining_keys.empty?
919
+ end
920
+ end
921
+ end
922
+ end
923
+
924
+ test "exits with ResqueSqs::TermException when using TERM_CHILD and not forking" do
925
+ begin
926
+ class LongRunningJob
927
+ @queue = :long_running_job
928
+
929
+ def self.perform(run_time)
930
+ ResqueSqs.redis.client.reconnect # get its own connection
931
+ ResqueSqs.redis.rpush('term-exception-test:start', Process.pid)
932
+ sleep run_time
933
+ ResqueSqs.redis.rpush('term-exception-test:result', 'Finished Normally')
934
+ rescue ResqueSqs::TermException => e
935
+ ResqueSqs.redis.rpush('term-exception-test:result', %Q(Caught TermException: #{e.inspect}))
936
+ ensure
937
+ ResqueSqs.redis.rpush('term-exception-test:final', 'exiting.')
938
+ end
939
+ end
940
+
941
+ ResqueSqs.enqueue(LongRunningJob, 5)
942
+
943
+ worker_pid = Kernel.fork do
944
+ # reconnect to redis
945
+ ResqueSqs.redis.client.reconnect
946
+
947
+ # ensure we don't fork (in worker)
948
+ $TESTING = false
949
+ ENV['FORK_PER_JOB'] = 'false'
950
+
951
+ worker = ResqueSqs::Worker.new(:long_running_job)
952
+ worker.term_timeout = 1
953
+ worker.term_child = 1
954
+
955
+ worker.work(0)
956
+ exit!
957
+ end
958
+
959
+ # ensure the worker is started
960
+ start_status = ResqueSqs.redis.blpop('term-exception-test:start', 5)
961
+ assert_not_nil start_status
962
+ child_pid = start_status[1].to_i
963
+ assert_operator child_pid, :>, 0
964
+
965
+ # send signal to abort the worker
966
+ Process.kill('TERM', worker_pid)
967
+ Process.waitpid(worker_pid)
968
+
969
+ # wait to see how it all came down
970
+ result = ResqueSqs.redis.blpop('term-exception-test:result', 5)
971
+ assert_not_nil result
972
+ assert !result[1].start_with?('Finished Normally'), 'Job finished normally. Sleep not long enough?'
973
+ assert result[1].start_with?('Caught TermException'), 'TermException not raised in child.'
974
+
975
+ # ensure that the child pid is no longer running
976
+ child_still_running = !(`ps -p #{child_pid.to_s} -o pid=`).empty?
977
+ assert !child_still_running
978
+
979
+ # see if post-cleanup occurred.
980
+ post_cleanup_occurred = ResqueSqs.redis.lpop( 'term-exception-test:final' )
981
+ assert post_cleanup_occurred, 'post cleanup did not occur. SIGKILL sent too early?'
982
+
983
+ ensure
984
+ remaining_keys = ResqueSqs.redis.keys('term-exception-test:*') || []
985
+ ResqueSqs.redis.del(*remaining_keys) unless remaining_keys.empty?
986
+ end
987
+ end
988
+
989
+ test "displays warning when not using term_child" do
990
+ begin
991
+ $TESTING = false
992
+ stdout, stderr = capture_io { @worker.work(0) }
993
+
994
+ assert stderr.match(/^WARNING:/)
995
+ ensure
996
+ $TESTING = true
997
+ end
998
+ end
999
+
1000
+ test "it does not display warning when using term_child" do
1001
+ @worker.term_child = "1"
1002
+ stdout, stderr = capture_io { @worker.work(0) }
1003
+
1004
+ assert !stderr.match(/^WARNING:/)
1005
+ end
1006
+
1007
+ class SuicidalJob
1008
+ @queue = :jobs
1009
+
1010
+ def self.perform
1011
+ Process.kill('KILL', Process.pid)
1012
+ end
1013
+
1014
+ def self.on_failure_store_exception(exc, *args)
1015
+ @@failure_exception = exc
1016
+ end
1017
+ end
1018
+
1019
+ test "will notify failure hooks when a job is killed by a signal" do
1020
+ begin
1021
+ $TESTING = false
1022
+ ResqueSqs.enqueue(SuicidalJob)
1023
+ suppress_warnings do
1024
+ @worker.work(0)
1025
+ end
1026
+ assert_equal ResqueSqs::DirtyExit, SuicidalJob.send(:class_variable_get, :@@failure_exception).class
1027
+ ensure
1028
+ $TESTING = true
1029
+ end
1030
+ end
1031
+ end
1032
+
1033
+ test "displays warning when using verbose" do
1034
+ begin
1035
+ $TESTING = false
1036
+ stdout, stderr = capture_io { @worker.verbose }
1037
+ ensure
1038
+ $TESTING = true
1039
+ end
1040
+ $warned_logger_severity_deprecation = false
1041
+
1042
+ assert stderr.match(/WARNING:/)
1043
+ end
1044
+
1045
+ test "displays warning when using verbose=" do
1046
+ begin
1047
+ $TESTING = false
1048
+ stdout, stderr = capture_io { @worker.verbose = true }
1049
+ ensure
1050
+ $TESTING = true
1051
+ end
1052
+ $warned_logger_severity_deprecation = false
1053
+
1054
+ assert stderr.match(/WARNING:/)
1055
+ end
1056
+
1057
+ test "displays warning when using very_verbose" do
1058
+ begin
1059
+ $TESTING = false
1060
+ stdout, stderr = capture_io { @worker.very_verbose }
1061
+ ensure
1062
+ $TESTING = true
1063
+ end
1064
+ $warned_logger_severity_deprecation = false
1065
+
1066
+ assert stderr.match(/WARNING:/)
1067
+ end
1068
+
1069
+ test "displays warning when using very_verbose=" do
1070
+ begin
1071
+ $TESTING = false
1072
+ stdout, stderr = capture_io { @worker.very_verbose = true }
1073
+ ensure
1074
+ $TESTING = true
1075
+ end
1076
+ $warned_logger_severity_deprecation = false
1077
+
1078
+ assert stderr.match(/WARNING:/)
1079
+ end
1080
+ end