resque_signal_from_child 1.25.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +15 -0
  2. data/HISTORY.md +455 -0
  3. data/LICENSE +20 -0
  4. data/README.markdown +866 -0
  5. data/Rakefile +70 -0
  6. data/bin/resque +81 -0
  7. data/bin/resque-web +27 -0
  8. data/lib/resque.rb +487 -0
  9. data/lib/resque/errors.rb +13 -0
  10. data/lib/resque/failure.rb +113 -0
  11. data/lib/resque/failure/airbrake.rb +33 -0
  12. data/lib/resque/failure/base.rb +73 -0
  13. data/lib/resque/failure/multiple.rb +59 -0
  14. data/lib/resque/failure/redis.rb +108 -0
  15. data/lib/resque/failure/redis_multi_queue.rb +89 -0
  16. data/lib/resque/helpers.rb +101 -0
  17. data/lib/resque/job.rb +346 -0
  18. data/lib/resque/log_formatters/quiet_formatter.rb +7 -0
  19. data/lib/resque/log_formatters/verbose_formatter.rb +7 -0
  20. data/lib/resque/log_formatters/very_verbose_formatter.rb +8 -0
  21. data/lib/resque/logging.rb +18 -0
  22. data/lib/resque/plugin.rb +66 -0
  23. data/lib/resque/server.rb +271 -0
  24. data/lib/resque/server/helpers.rb +52 -0
  25. data/lib/resque/server/public/favicon.ico +0 -0
  26. data/lib/resque/server/public/idle.png +0 -0
  27. data/lib/resque/server/public/jquery-1.3.2.min.js +19 -0
  28. data/lib/resque/server/public/jquery.relatize_date.js +95 -0
  29. data/lib/resque/server/public/poll.png +0 -0
  30. data/lib/resque/server/public/ranger.js +78 -0
  31. data/lib/resque/server/public/reset.css +44 -0
  32. data/lib/resque/server/public/style.css +91 -0
  33. data/lib/resque/server/public/working.png +0 -0
  34. data/lib/resque/server/test_helper.rb +19 -0
  35. data/lib/resque/server/views/error.erb +1 -0
  36. data/lib/resque/server/views/failed.erb +29 -0
  37. data/lib/resque/server/views/failed_job.erb +50 -0
  38. data/lib/resque/server/views/failed_queues_overview.erb +24 -0
  39. data/lib/resque/server/views/key_sets.erb +19 -0
  40. data/lib/resque/server/views/key_string.erb +11 -0
  41. data/lib/resque/server/views/layout.erb +44 -0
  42. data/lib/resque/server/views/next_more.erb +22 -0
  43. data/lib/resque/server/views/overview.erb +4 -0
  44. data/lib/resque/server/views/queues.erb +58 -0
  45. data/lib/resque/server/views/stats.erb +62 -0
  46. data/lib/resque/server/views/workers.erb +109 -0
  47. data/lib/resque/server/views/working.erb +72 -0
  48. data/lib/resque/stat.rb +57 -0
  49. data/lib/resque/tasks.rb +83 -0
  50. data/lib/resque/vendor/utf8_util.rb +26 -0
  51. data/lib/resque/vendor/utf8_util/utf8_util_18.rb +91 -0
  52. data/lib/resque/vendor/utf8_util/utf8_util_19.rb +5 -0
  53. data/lib/resque/version.rb +3 -0
  54. data/lib/resque/worker.rb +772 -0
  55. data/lib/tasks/redis.rake +161 -0
  56. data/lib/tasks/resque.rake +2 -0
  57. data/test/airbrake_test.rb +27 -0
  58. data/test/dump.rdb +0 -0
  59. data/test/failure_base_test.rb +15 -0
  60. data/test/job_hooks_test.rb +464 -0
  61. data/test/job_plugins_test.rb +230 -0
  62. data/test/logging_test.rb +24 -0
  63. data/test/plugin_test.rb +116 -0
  64. data/test/redis-test-cluster.conf +115 -0
  65. data/test/redis-test.conf +115 -0
  66. data/test/resque-web_test.rb +59 -0
  67. data/test/resque_failure_redis_test.rb +19 -0
  68. data/test/resque_hook_test.rb +190 -0
  69. data/test/resque_test.rb +278 -0
  70. data/test/test_helper.rb +198 -0
  71. data/test/worker_test.rb +1015 -0
  72. metadata +196 -0
@@ -0,0 +1,198 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'minitest/autorun'
4
+ require 'redis/namespace'
5
+
6
+ require 'mocha/setup'
7
+
8
+ $dir = File.dirname(File.expand_path(__FILE__))
9
+ $LOAD_PATH.unshift $dir + '/../lib'
10
+ require 'resque'
11
+ $TESTING = true
12
+ $TEST_PID=Process.pid
13
+
14
+ begin
15
+ require 'leftright'
16
+ rescue LoadError
17
+ end
18
+
19
+
20
+ #
21
+ # make sure we can run redis
22
+ #
23
+
24
+ if !system("which redis-server")
25
+ puts '', "** can't find `redis-server` in your path"
26
+ puts "** try running `sudo rake install`"
27
+ abort ''
28
+ end
29
+
30
+
31
+ #
32
+ # start our own redis when the tests start,
33
+ # kill it when they end
34
+ #
35
+
36
+ MiniTest::Unit.after_tests do
37
+ if Process.pid == $TEST_PID
38
+ processes = `ps -A -o pid,command | grep [r]edis-test`.split("\n")
39
+ pids = processes.map { |process| process.split(" ")[0] }
40
+ puts "Killing test redis server..."
41
+ pids.each { |pid| Process.kill("TERM", pid.to_i) }
42
+ system("rm -f #{$dir}/dump.rdb #{$dir}/dump-cluster.rdb")
43
+ end
44
+ end
45
+
46
+ if ENV.key? 'RESQUE_DISTRIBUTED'
47
+ require 'redis/distributed'
48
+ puts "Starting redis for testing at localhost:9736 and localhost:9737..."
49
+ `redis-server #{$dir}/redis-test.conf`
50
+ `redis-server #{$dir}/redis-test-cluster.conf`
51
+ r = Redis::Distributed.new(['redis://localhost:9736', 'redis://localhost:9737'])
52
+ Resque.redis = Redis::Namespace.new :resque, :redis => r
53
+ else
54
+ puts "Starting redis for testing at localhost:9736..."
55
+ `redis-server #{$dir}/redis-test.conf`
56
+ Resque.redis = 'localhost:9736'
57
+ end
58
+
59
+
60
+ ##
61
+ # test/spec/mini 3
62
+ # http://gist.github.com/25455
63
+ # chris@ozmm.org
64
+ #
65
+ def context(*args, &block)
66
+ return super unless (name = args.first) && block
67
+ require 'test/unit'
68
+ klass = Class.new(defined?(ActiveSupport::TestCase) ? ActiveSupport::TestCase : Test::Unit::TestCase) do
69
+ def self.test(name, &block)
70
+ define_method("test_#{name.gsub(/\W/,'_')}", &block) if block
71
+ end
72
+ def self.xtest(*args) end
73
+ def self.setup(&block) define_method(:setup, &block) end
74
+ def self.teardown(&block) define_method(:teardown, &block) end
75
+ end
76
+ (class << klass; self end).send(:define_method, :name) { name.gsub(/\W/,'_') }
77
+ klass.class_eval &block
78
+ # XXX: In 1.8.x, not all tests will run unless anonymous classes are kept in scope.
79
+ ($test_classes ||= []) << klass
80
+ end
81
+
82
+ ##
83
+ # Helper to perform job classes
84
+ #
85
+ module PerformJob
86
+ def perform_job(klass, *args)
87
+ resque_job = Resque::Job.new(:testqueue, 'class' => klass, 'args' => args)
88
+ resque_job.perform
89
+ end
90
+ end
91
+
92
+ #
93
+ # fixture classes
94
+ #
95
+
96
+ class SomeJob
97
+ def self.perform(repo_id, path)
98
+ end
99
+ end
100
+
101
+ class SomeIvarJob < SomeJob
102
+ @queue = :ivar
103
+ end
104
+
105
+ class SomeMethodJob < SomeJob
106
+ def self.queue
107
+ :method
108
+ end
109
+ end
110
+
111
+ class BadJob
112
+ def self.perform
113
+ raise "Bad job!"
114
+ end
115
+ end
116
+
117
+ class GoodJob
118
+ def self.perform(name)
119
+ "Good job, #{name}"
120
+ end
121
+ end
122
+
123
+ class AtExitJob
124
+ def self.perform(filename)
125
+ at_exit do
126
+ File.open(filename, "w") {|file| file.puts "at_exit"}
127
+ end
128
+ "at_exit job"
129
+ end
130
+ end
131
+
132
+ class BadJobWithSyntaxError
133
+ def self.perform
134
+ raise SyntaxError, "Extra Bad job!"
135
+ end
136
+ end
137
+
138
+ class BadFailureBackend < Resque::Failure::Base
139
+ def save
140
+ raise Exception.new("Failure backend error")
141
+ end
142
+ end
143
+
144
+ def with_failure_backend(failure_backend, &block)
145
+ previous_backend = Resque::Failure.backend
146
+ Resque::Failure.backend = failure_backend
147
+ yield block
148
+ ensure
149
+ Resque::Failure.backend = previous_backend
150
+ end
151
+
152
+ require 'time'
153
+
154
+ class Time
155
+ # Thanks, Timecop
156
+ class << self
157
+ attr_accessor :fake_time
158
+
159
+ alias_method :now_without_mock_time, :now
160
+
161
+ def now
162
+ fake_time || now_without_mock_time
163
+ end
164
+ end
165
+
166
+ self.fake_time = nil
167
+ end
168
+
169
+ # From minitest/unit
170
+ def capture_io
171
+ require 'stringio'
172
+
173
+ orig_stdout, orig_stderr = $stdout, $stderr
174
+ captured_stdout, captured_stderr = StringIO.new, StringIO.new
175
+ $stdout, $stderr = captured_stdout, captured_stderr
176
+
177
+ yield
178
+
179
+ return captured_stdout.string, captured_stderr.string
180
+ ensure
181
+ $stdout = orig_stdout
182
+ $stderr = orig_stderr
183
+ end
184
+
185
+ # Log to log/test.log
186
+ def reset_logger
187
+ $test_logger ||= MonoLogger.new(File.open(File.expand_path("../../log/test.log", __FILE__), "w"))
188
+ Resque.logger = $test_logger
189
+ end
190
+
191
+ reset_logger
192
+
193
+ def suppress_warnings
194
+ old_verbose, $VERBOSE = $VERBOSE, nil
195
+ yield
196
+ ensure
197
+ $VERBOSE = old_verbose
198
+ end
@@ -0,0 +1,1015 @@
1
+ require 'test_helper'
2
+ require 'tmpdir'
3
+
4
+ context "Resque::Worker" do
5
+ setup do
6
+ Resque.redis.flushall
7
+
8
+ Resque.before_first_fork = nil
9
+ Resque.before_fork = nil
10
+ Resque.after_fork = nil
11
+
12
+ @worker = Resque::Worker.new(:jobs)
13
+ Resque::Job.create(:jobs, SomeJob, 20, '/tmp')
14
+ end
15
+
16
+ test "can fail jobs" do
17
+ Resque::Job.create(:jobs, BadJob)
18
+ @worker.work(0)
19
+ assert_equal 1, Resque::Failure.count
20
+ end
21
+
22
+ test "failed jobs report exception and message" do
23
+ Resque::Job.create(:jobs, BadJobWithSyntaxError)
24
+ @worker.work(0)
25
+ assert_equal('SyntaxError', Resque::Failure.all['exception'])
26
+ assert_equal('Extra Bad job!', Resque::Failure.all['error'])
27
+ end
28
+
29
+ test "does not allow exceptions from failure backend to escape" do
30
+ job = Resque::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, Resque::Failure.count
40
+ else
41
+ # ensure we actually fork
42
+ $TESTING = false
43
+ Resque.redis.client.reconnect
44
+ worker = Resque::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
+ Resque.redis.client.reconnect
64
+ Resque::Job.create(:at_exit_jobs, AtExitJob, tmpfile)
65
+ worker = Resque::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
+ Resque.redis.client.reconnect
95
+ Resque::Job.create(:not_failing_job, RaiseExceptionOnFailure)
96
+ worker = Resque::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
+ Resque.redis.client.reconnect
118
+ Resque::Job.create(:at_exit_jobs, AtExitJob, tmpfile)
119
+ worker = Resque::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 = Resque::Job.new(:jobs, { 'class' => 'NotAValidJobClass', 'args' => '' })
130
+ @worker.perform job
131
+ assert_equal 1, Resque::Failure.count, 'failure not reported'
132
+ end
133
+
134
+ test "register 'run_at' time on UTC timezone in ISO8601 format" do
135
+ job = Resque::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 = Resque::Job.new(:jobs, {'class' => 'GoodJob', 'args' => "blah"})
143
+ @worker.working_on(job)
144
+ @worker.unregister_worker
145
+ assert_equal 1, Resque::Failure.count
146
+ assert_equal('Resque::DirtyExit', Resque::Failure.all['exception'])
147
+ end
148
+
149
+ test "fails uncompleted jobs with worker exception on exit" do
150
+ job = Resque::Job.new(:jobs, {'class' => 'GoodJob', 'args' => "blah"})
151
+ @worker.working_on(job)
152
+ @worker.unregister_worker(StandardError.new)
153
+ assert_equal 1, Resque::Failure.count
154
+ assert_equal('StandardError', Resque::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 = Resque::Job.new(:jobs, {'class' => 'SimpleJobWithFailureHandling', 'args' => ""})
169
+ @worker.working_on(job)
170
+ @worker.unregister_worker
171
+ assert_equal 1, Resque::Failure.count
172
+ assert(SimpleJobWithFailureHandling.exception.kind_of?(Resque::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 = Resque::Job.new(:jobs, {'class' => 'SimpleFailingJob', 'args' => ""})
193
+ @worker.perform(job)
194
+ assert_equal 1, Resque::Failure.count
195
+ assert_equal 1, SimpleFailingJob.exception_count
196
+ end
197
+
198
+ test "can peek at failed jobs" do
199
+ 10.times { Resque::Job.create(:jobs, BadJob) }
200
+ @worker.work(0)
201
+ assert_equal 10, Resque::Failure.count
202
+
203
+ assert_equal 10, Resque::Failure.all(0, 20).size
204
+ end
205
+
206
+ test "can clear failed jobs" do
207
+ Resque::Job.create(:jobs, BadJob)
208
+ @worker.work(0)
209
+ assert_equal 1, Resque::Failure.count
210
+ Resque::Failure.clear
211
+ assert_equal 0, Resque::Failure.count
212
+ end
213
+
214
+ test "catches exceptional jobs" do
215
+ Resque::Job.create(:jobs, BadJob)
216
+ Resque::Job.create(:jobs, BadJob)
217
+ @worker.process
218
+ @worker.process
219
+ @worker.process
220
+ assert_equal 2, Resque::Failure.count
221
+ end
222
+
223
+ test "strips whitespace from queue names" do
224
+ queues = "critical, high, low".split(',')
225
+ worker = Resque::Worker.new(*queues)
226
+ assert_equal %w( critical high low ), worker.queues
227
+ end
228
+
229
+ test "can work on multiple queues" do
230
+ Resque::Job.create(:high, GoodJob)
231
+ Resque::Job.create(:critical, GoodJob)
232
+
233
+ worker = Resque::Worker.new(:critical, :high)
234
+
235
+ worker.process
236
+ assert_equal 1, Resque.size(:high)
237
+ assert_equal 0, Resque.size(:critical)
238
+
239
+ worker.process
240
+ assert_equal 0, Resque.size(:high)
241
+ end
242
+
243
+ test "can work on all queues" do
244
+ Resque::Job.create(:high, GoodJob)
245
+ Resque::Job.create(:critical, GoodJob)
246
+ Resque::Job.create(:blahblah, GoodJob)
247
+
248
+ worker = Resque::Worker.new("*")
249
+
250
+ worker.work(0)
251
+ assert_equal 0, Resque.size(:high)
252
+ assert_equal 0, Resque.size(:critical)
253
+ assert_equal 0, Resque.size(:blahblah)
254
+ end
255
+
256
+ test "can work with wildcard at the end of the list" do
257
+ Resque::Job.create(:high, GoodJob)
258
+ Resque::Job.create(:critical, GoodJob)
259
+ Resque::Job.create(:blahblah, GoodJob)
260
+ Resque::Job.create(:beer, GoodJob)
261
+
262
+ worker = Resque::Worker.new(:critical, :high, "*")
263
+
264
+ worker.work(0)
265
+ assert_equal 0, Resque.size(:high)
266
+ assert_equal 0, Resque.size(:critical)
267
+ assert_equal 0, Resque.size(:blahblah)
268
+ assert_equal 0, Resque.size(:beer)
269
+ end
270
+
271
+ test "can work with wildcard at the middle of the list" do
272
+ Resque::Job.create(:high, GoodJob)
273
+ Resque::Job.create(:critical, GoodJob)
274
+ Resque::Job.create(:blahblah, GoodJob)
275
+ Resque::Job.create(:beer, GoodJob)
276
+
277
+ worker = Resque::Worker.new(:critical, "*", :high)
278
+
279
+ worker.work(0)
280
+ assert_equal 0, Resque.size(:high)
281
+ assert_equal 0, Resque.size(:critical)
282
+ assert_equal 0, Resque.size(:blahblah)
283
+ assert_equal 0, Resque.size(:beer)
284
+ end
285
+
286
+ test "processes * queues in alphabetical order" do
287
+ Resque::Job.create(:high, GoodJob)
288
+ Resque::Job.create(:critical, GoodJob)
289
+ Resque::Job.create(:blahblah, GoodJob)
290
+
291
+ worker = Resque::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
+ Resque::Job.create(:critical, GoodJob)
303
+ Resque::Job.create(:test_one, GoodJob)
304
+ Resque::Job.create(:test_two, GoodJob)
305
+
306
+ worker = Resque::Worker.new("test_*")
307
+
308
+ worker.work(0)
309
+ assert_equal 1, Resque.size(:critical)
310
+ assert_equal 0, Resque.size(:test_one)
311
+ assert_equal 0, Resque.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 Resque::NoQueueError do
320
+ Resque::Worker.new
321
+ end
322
+ end
323
+
324
+ test "fails if a job class has no `perform` method" do
325
+ worker = Resque::Worker.new(:perform_less)
326
+ Resque::Job.create(:perform_less, Object)
327
+
328
+ assert_equal 0, Resque::Failure.count
329
+ worker.work(0)
330
+ assert_equal 1, Resque::Failure.count
331
+ end
332
+
333
+ test "inserts itself into the 'workers' list on startup" do
334
+ @worker.work(0) do
335
+ assert_equal @worker, Resque.workers[0]
336
+ end
337
+ end
338
+
339
+ test "removes itself from the 'workers' list on shutdown" do
340
+ @worker.work(0) do
341
+ assert_equal @worker, Resque.workers[0]
342
+ end
343
+
344
+ assert_equal [], Resque.workers
345
+ end
346
+
347
+ test "removes worker with stringified id" do
348
+ @worker.work(0) do
349
+ worker_id = Resque.workers[0].to_s
350
+ Resque.remove_worker(worker_id)
351
+ assert_equal [], Resque.workers
352
+ end
353
+ end
354
+
355
+ test "records what it is working on" do
356
+ @worker.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.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.work(0) do
382
+ assert_equal [@worker], Resque.working
383
+ end
384
+ end
385
+
386
+ test "keeps track of how many jobs it has processed" do
387
+ Resque::Job.create(:jobs, BadJob)
388
+ Resque::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
+ Resque::Job.create(:jobs, BadJob)
399
+ Resque::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.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.work(0) do
423
+ assert Resque::Worker.exists?(@worker)
424
+ assert !Resque::Worker.exists?('blah-blah')
425
+ end
426
+ end
427
+
428
+ test "sets $0 while working" do
429
+ @worker.work(0) do
430
+ ver = Resque::Version
431
+ assert_equal "resque-#{ver}: Processing jobs since #{Time.now.to_i}", $0
432
+ end
433
+ end
434
+
435
+ test "can be found" do
436
+ @worker.work(0) do
437
+ found = Resque::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.work(0) do
446
+ found = Resque::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 = Resque::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 = Resque::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 = Resque::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 = Resque::Worker.new(:high)
470
+ workerB.instance_variable_set(:@to_s, "#{`hostname`.chomp}-foo:4:high")
471
+ workerB.register_worker
472
+
473
+ assert_equal 4, Resque.workers.size
474
+
475
+ # then we prune them
476
+ @worker.work(0)
477
+
478
+ worker_strings = Resque::Worker.all.map(&:to_s)
479
+
480
+ assert_equal 3, Resque.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, Resque.info[:processed]
499
+ end
500
+
501
+ test "Will call a before_first_fork hook only once" do
502
+ Resque.redis.flushall
503
+ $BEFORE_FORK_CALLED = 0
504
+ Resque.before_first_fork = Proc.new { $BEFORE_FORK_CALLED += 1 }
505
+ workerA = Resque::Worker.new(:jobs)
506
+ Resque::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
+ Resque.redis.flushall
520
+ $BEFORE_FORK_CALLED = false
521
+ Resque.before_fork = Proc.new { $BEFORE_FORK_CALLED = true }
522
+ workerA = Resque::Worker.new(:jobs)
523
+
524
+ assert !$BEFORE_FORK_CALLED
525
+ Resque::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
+ Resque.redis.flushall
532
+ $BEFORE_FORK_CALLED = false
533
+ Resque.before_fork = Proc.new { $BEFORE_FORK_CALLED = true }
534
+ workerA = Resque::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
+ Resque::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
+ Resque.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
+ Resque.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
+ Resque.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 = Resque::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
+ Resque.redis.flushall
641
+ $AFTER_FORK_CALLED = false
642
+ Resque.after_fork = Proc.new { $AFTER_FORK_CALLED = true }
643
+ workerA = Resque::Worker.new(:jobs)
644
+
645
+ assert !$AFTER_FORK_CALLED
646
+ Resque::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
+ Resque.redis.flushall
653
+ $AFTER_FORK_CALLED = false
654
+ Resque.after_fork = Proc.new { $AFTER_FORK_CALLED = true }
655
+ workerA = Resque::Worker.new(:jobs)
656
+ workerA.cant_fork = true
657
+
658
+ assert !$AFTER_FORK_CALLED
659
+ Resque::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
+ Resque::Failure.create(:exception => Exception.new, :worker => Resque::Worker.new(queue), :queue => queue, :payload => {'class' => 'GoodJob'})
671
+ Resque::Failure.create(:exception => Exception.new, :worker => Resque::Worker.new(queue), :queue => 'some_job', :payload => {'class' => 'SomeJob'})
672
+ Resque::Failure.requeue_queue(queue)
673
+ assert Resque::Failure.all(0).has_key?('retried_at')
674
+ assert !Resque::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
+ Resque::Failure.create(:exception => Exception.new, :worker => Resque::Worker.new(queue), :queue => queue, :payload => {'class' => 'GoodJob'})
681
+ Resque::Failure.create(:exception => Exception.new, :worker => Resque::Worker.new(queue2), :queue => queue2, :payload => {'class' => 'SomeJob'})
682
+ Resque::Failure.create(:exception => Exception.new, :worker => Resque::Worker.new(queue), :queue => queue, :payload => {'class' => 'GoodJob'})
683
+ Resque::Failure.remove_queue(queue)
684
+ assert_equal queue2, Resque::Failure.all(0)['queue']
685
+ assert_equal 1, Resque::Failure.count
686
+ end
687
+
688
+ test "reconnects to redis after fork" do
689
+ original_connection = Resque.redis.client.connection.instance_variable_get("@sock")
690
+ @worker.work(0)
691
+ assert_not_equal original_connection, Resque.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 Resque::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
+ Resque.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 Resque::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 Resque::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
+ Resque.logger = DummyLogger.new
768
+ begin
769
+ @worker.work(0)
770
+ messages = Resque.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 Resque::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
+ Resque.redis.client.reconnect # get its own connection
798
+ Resque.redis.rpush( 'sigterm-test:start', Process.pid )
799
+ sleep run_time
800
+ Resque.redis.rpush( 'sigterm-test:result', 'Finished Normally' )
801
+ rescue Resque::TermException => e
802
+ Resque.redis.rpush( 'sigterm-test:result', %Q(Caught SignalException: #{e.inspect}))
803
+ sleep rescue_time unless rescue_time.nil?
804
+ ensure
805
+ puts 'fuuuu'
806
+ Resque.redis.rpush( 'sigterm-test:final', 'exiting.' )
807
+ end
808
+ end
809
+
810
+ Resque.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
+ Resque.redis.client.reconnect
817
+
818
+ worker = Resque::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 = Resque.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 = Resque.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 = Resque.redis.keys('sigterm-test:*') || []
845
+ Resque.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, Resque::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
+ Resque.redis.client.reconnect # get its own connection
864
+ Resque.redis.rpush( 'sigterm-test:start', Process.pid )
865
+ sleep run_time
866
+ Resque.redis.rpush( 'sigterm-test:result', 'Finished Normally' )
867
+ rescue @@exception => e
868
+ Resque.redis.rpush( 'sigterm-test:result', %Q(Caught SignalException: #{e.inspect}))
869
+ sleep rescue_time unless rescue_time.nil?
870
+ ensure
871
+ Resque.redis.rpush( 'sigterm-test:final', 'exiting.' )
872
+ end
873
+ end
874
+
875
+ Resque.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
+ Resque.redis.client.reconnect
882
+
883
+ worker = Resque::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 = Resque.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 = Resque.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 = Resque.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 = Resque.redis.keys('sigterm-test:*') || []
918
+ Resque.redis.del(*remaining_keys) unless remaining_keys.empty?
919
+ end
920
+ end
921
+ end
922
+ end
923
+
924
+ test "displays warning when not using term_child" do
925
+ begin
926
+ $TESTING = false
927
+ stdout, stderr = capture_io { @worker.work(0) }
928
+
929
+ assert stderr.match(/^WARNING:/)
930
+ ensure
931
+ $TESTING = true
932
+ end
933
+ end
934
+
935
+ test "it does not display warning when using term_child" do
936
+ @worker.term_child = "1"
937
+ stdout, stderr = capture_io { @worker.work(0) }
938
+
939
+ assert !stderr.match(/^WARNING:/)
940
+ end
941
+
942
+ class SuicidalJob
943
+ @queue = :jobs
944
+
945
+ def self.perform
946
+ Process.kill('KILL', Process.pid)
947
+ end
948
+
949
+ def self.on_failure_store_exception(exc, *args)
950
+ @@failure_exception = exc
951
+ end
952
+ end
953
+
954
+ test "will notify failure hooks when a job is killed by a signal" do
955
+ begin
956
+ $TESTING = false
957
+ Resque.enqueue(SuicidalJob)
958
+ suppress_warnings do
959
+ @worker.work(0)
960
+ end
961
+ assert_equal Resque::DirtyExit, SuicidalJob.send(:class_variable_get, :@@failure_exception).class
962
+ ensure
963
+ $TESTING = true
964
+ end
965
+ end
966
+ end
967
+
968
+ test "displays warning when using verbose" do
969
+ begin
970
+ $TESTING = false
971
+ stdout, stderr = capture_io { @worker.verbose }
972
+ ensure
973
+ $TESTING = true
974
+ end
975
+ $warned_logger_severity_deprecation = false
976
+
977
+ assert stderr.match(/WARNING:/)
978
+ end
979
+
980
+ test "displays warning when using verbose=" do
981
+ begin
982
+ $TESTING = false
983
+ stdout, stderr = capture_io { @worker.verbose = true }
984
+ ensure
985
+ $TESTING = true
986
+ end
987
+ $warned_logger_severity_deprecation = false
988
+
989
+ assert stderr.match(/WARNING:/)
990
+ end
991
+
992
+ test "displays warning when using very_verbose" do
993
+ begin
994
+ $TESTING = false
995
+ stdout, stderr = capture_io { @worker.very_verbose }
996
+ ensure
997
+ $TESTING = true
998
+ end
999
+ $warned_logger_severity_deprecation = false
1000
+
1001
+ assert stderr.match(/WARNING:/)
1002
+ end
1003
+
1004
+ test "displays warning when using very_verbose=" do
1005
+ begin
1006
+ $TESTING = false
1007
+ stdout, stderr = capture_io { @worker.very_verbose = true }
1008
+ ensure
1009
+ $TESTING = true
1010
+ end
1011
+ $warned_logger_severity_deprecation = false
1012
+
1013
+ assert stderr.match(/WARNING:/)
1014
+ end
1015
+ end