resque-cedar 1.20.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.
Files changed (64) hide show
  1. data/HISTORY.md +354 -0
  2. data/LICENSE +20 -0
  3. data/README.markdown +908 -0
  4. data/Rakefile +70 -0
  5. data/bin/resque +81 -0
  6. data/bin/resque-web +27 -0
  7. data/lib/resque.rb +385 -0
  8. data/lib/resque/coder.rb +27 -0
  9. data/lib/resque/errors.rb +10 -0
  10. data/lib/resque/failure.rb +96 -0
  11. data/lib/resque/failure/airbrake.rb +17 -0
  12. data/lib/resque/failure/base.rb +64 -0
  13. data/lib/resque/failure/hoptoad.rb +33 -0
  14. data/lib/resque/failure/multiple.rb +54 -0
  15. data/lib/resque/failure/redis.rb +51 -0
  16. data/lib/resque/failure/thoughtbot.rb +33 -0
  17. data/lib/resque/helpers.rb +64 -0
  18. data/lib/resque/job.rb +223 -0
  19. data/lib/resque/multi_json_coder.rb +37 -0
  20. data/lib/resque/multi_queue.rb +73 -0
  21. data/lib/resque/plugin.rb +66 -0
  22. data/lib/resque/queue.rb +117 -0
  23. data/lib/resque/server.rb +248 -0
  24. data/lib/resque/server/public/favicon.ico +0 -0
  25. data/lib/resque/server/public/idle.png +0 -0
  26. data/lib/resque/server/public/jquery-1.3.2.min.js +19 -0
  27. data/lib/resque/server/public/jquery.relatize_date.js +95 -0
  28. data/lib/resque/server/public/poll.png +0 -0
  29. data/lib/resque/server/public/ranger.js +73 -0
  30. data/lib/resque/server/public/reset.css +44 -0
  31. data/lib/resque/server/public/style.css +86 -0
  32. data/lib/resque/server/public/working.png +0 -0
  33. data/lib/resque/server/test_helper.rb +19 -0
  34. data/lib/resque/server/views/error.erb +1 -0
  35. data/lib/resque/server/views/failed.erb +67 -0
  36. data/lib/resque/server/views/key_sets.erb +19 -0
  37. data/lib/resque/server/views/key_string.erb +11 -0
  38. data/lib/resque/server/views/layout.erb +44 -0
  39. data/lib/resque/server/views/next_more.erb +10 -0
  40. data/lib/resque/server/views/overview.erb +4 -0
  41. data/lib/resque/server/views/queues.erb +49 -0
  42. data/lib/resque/server/views/stats.erb +62 -0
  43. data/lib/resque/server/views/workers.erb +109 -0
  44. data/lib/resque/server/views/working.erb +72 -0
  45. data/lib/resque/stat.rb +53 -0
  46. data/lib/resque/tasks.rb +61 -0
  47. data/lib/resque/version.rb +3 -0
  48. data/lib/resque/worker.rb +557 -0
  49. data/lib/tasks/redis.rake +161 -0
  50. data/lib/tasks/resque.rake +2 -0
  51. data/test/airbrake_test.rb +26 -0
  52. data/test/hoptoad_test.rb +26 -0
  53. data/test/job_hooks_test.rb +423 -0
  54. data/test/job_plugins_test.rb +230 -0
  55. data/test/multi_queue_test.rb +95 -0
  56. data/test/plugin_test.rb +116 -0
  57. data/test/redis-test-cluster.conf +115 -0
  58. data/test/redis-test.conf +115 -0
  59. data/test/redis_queue_test.rb +133 -0
  60. data/test/resque-web_test.rb +59 -0
  61. data/test/resque_test.rb +284 -0
  62. data/test/test_helper.rb +135 -0
  63. data/test/worker_test.rb +443 -0
  64. metadata +188 -0
@@ -0,0 +1,135 @@
1
+ require 'rubygems'
2
+
3
+ dir = File.dirname(File.expand_path(__FILE__))
4
+ $LOAD_PATH.unshift dir + '/../lib'
5
+ $TESTING = true
6
+ require 'mocha'
7
+ require 'minitest/unit'
8
+ require 'minitest/spec'
9
+ require 'test/unit'
10
+
11
+ require 'redis/namespace'
12
+ require 'resque'
13
+
14
+ #
15
+ # make sure we can run redis
16
+ #
17
+
18
+ if !system("which redis-server")
19
+ puts '', "** can't find `redis-server` in your path"
20
+ puts "** try running `sudo rake install`"
21
+ abort ''
22
+ end
23
+
24
+
25
+ #
26
+ # start our own redis when the tests start,
27
+ # kill it when they end
28
+ #
29
+
30
+ at_exit do
31
+ next if $!
32
+
33
+ if defined?(MiniTest)
34
+ exit_code = MiniTest::Unit.new.run(ARGV)
35
+ else
36
+ exit_code = Test::Unit::AutoRunner.run
37
+ end
38
+
39
+ processes = `ps -A -o pid,command | grep [r]edis-test`.split("\n")
40
+ pids = processes.map { |process| process.split(" ")[0] }
41
+ puts "Killing test redis server..."
42
+ `rm -f #{dir}/dump.rdb #{dir}/dump-cluster.rdb`
43
+ pids.each { |pid| Process.kill("KILL", pid.to_i) }
44
+ exit exit_code
45
+ end
46
+
47
+ if ENV.key? 'RESQUE_DISTRIBUTED'
48
+ require 'redis/distributed'
49
+ puts "Starting redis for testing at localhost:9736 and localhost:9737..."
50
+ `redis-server #{dir}/redis-test.conf`
51
+ `redis-server #{dir}/redis-test-cluster.conf`
52
+ r = Redis::Distributed.new(['redis://localhost:9736', 'redis://localhost:9737'])
53
+ Resque.redis = Redis::Namespace.new :resque, :redis => r
54
+ else
55
+ puts "Starting redis for testing at localhost:9736..."
56
+ `redis-server #{dir}/redis-test.conf`
57
+ Resque.redis = 'localhost:9736'
58
+ end
59
+
60
+
61
+ ##
62
+ # Helper to perform job classes
63
+ #
64
+ module PerformJob
65
+ def perform_job(klass, *args)
66
+ resque_job = Resque::Job.new(:testqueue, 'class' => klass, 'args' => args)
67
+ resque_job.perform
68
+ end
69
+ end
70
+
71
+ #
72
+ # fixture classes
73
+ #
74
+
75
+ class SomeJob
76
+ def self.perform(repo_id, path)
77
+ end
78
+ end
79
+
80
+ class SomeIvarJob < SomeJob
81
+ @queue = :ivar
82
+ end
83
+
84
+ class SomeMethodJob < SomeJob
85
+ def self.queue
86
+ :method
87
+ end
88
+ end
89
+
90
+ class BadJob
91
+ def self.perform
92
+ raise "Bad job!"
93
+ end
94
+ end
95
+
96
+ class GoodJob
97
+ def self.perform(name)
98
+ "Good job, #{name}"
99
+ end
100
+ end
101
+
102
+ class BadJobWithSyntaxError
103
+ def self.perform
104
+ raise SyntaxError, "Extra Bad job!"
105
+ end
106
+ end
107
+
108
+ class BadFailureBackend < Resque::Failure::Base
109
+ def save
110
+ raise Exception.new("Failure backend error")
111
+ end
112
+ end
113
+
114
+ def with_failure_backend(failure_backend, &block)
115
+ previous_backend = Resque::Failure.backend
116
+ Resque::Failure.backend = failure_backend
117
+ yield block
118
+ ensure
119
+ Resque::Failure.backend = previous_backend
120
+ end
121
+
122
+ class Time
123
+ # Thanks, Timecop
124
+ class << self
125
+ attr_accessor :fake_time
126
+
127
+ alias_method :now_without_mock_time, :now
128
+
129
+ def now
130
+ fake_time || now_without_mock_time
131
+ end
132
+ end
133
+
134
+ self.fake_time = nil
135
+ end
@@ -0,0 +1,443 @@
1
+ require 'test_helper'
2
+
3
+ describe "Resque::Worker" do
4
+ include Test::Unit::Assertions
5
+
6
+ before do
7
+ Resque.redis = Resque.redis # reset state in Resque object
8
+ Resque.redis.flushall
9
+
10
+ Resque.before_first_fork = nil
11
+ Resque.before_fork = nil
12
+ Resque.after_fork = nil
13
+
14
+ @worker = Resque::Worker.new(:jobs)
15
+ Resque::Job.create(:jobs, SomeJob, 20, '/tmp')
16
+ end
17
+
18
+ it "can fail jobs" do
19
+ Resque::Job.create(:jobs, BadJob)
20
+ @worker.work(0)
21
+ assert_equal 1, Resque::Failure.count
22
+ end
23
+
24
+ it "failed jobs report exception and message" do
25
+ Resque::Job.create(:jobs, BadJobWithSyntaxError)
26
+ @worker.work(0)
27
+ assert_equal('SyntaxError', Resque::Failure.all['exception'])
28
+ assert_equal('Extra Bad job!', Resque::Failure.all['error'])
29
+ end
30
+
31
+ it "does not allow exceptions from failure backend to escape" do
32
+ job = Resque::Job.new(:jobs, {})
33
+ with_failure_backend BadFailureBackend do
34
+ @worker.perform job
35
+ end
36
+ end
37
+
38
+ it "fails uncompleted jobs on exit" do
39
+ job = Resque::Job.new(:jobs, {'class' => 'GoodJob', 'args' => "blah"})
40
+ @worker.working_on(job)
41
+ @worker.unregister_worker
42
+ assert_equal 1, Resque::Failure.count
43
+ end
44
+
45
+ class ::SimpleJobWithFailureHandling
46
+ def self.on_failure_record_failure(exception, *job_args)
47
+ @@exception = exception
48
+ end
49
+
50
+ def self.exception
51
+ @@exception
52
+ end
53
+ end
54
+
55
+ it "fails uncompleted jobs on exit, and calls failure hook" do
56
+ job = Resque::Job.new(:jobs, {'class' => 'SimpleJobWithFailureHandling', 'args' => ""})
57
+ @worker.working_on(job)
58
+ @worker.unregister_worker
59
+ assert_equal 1, Resque::Failure.count
60
+ assert(SimpleJobWithFailureHandling.exception.kind_of?(Resque::DirtyExit))
61
+ end
62
+
63
+ it "can peek at failed jobs" do
64
+ 10.times { Resque::Job.create(:jobs, BadJob) }
65
+ @worker.work(0)
66
+ assert_equal 10, Resque::Failure.count
67
+
68
+ assert_equal 10, Resque::Failure.all(0, 20).size
69
+ end
70
+
71
+ it "can clear failed jobs" do
72
+ Resque::Job.create(:jobs, BadJob)
73
+ @worker.work(0)
74
+ assert_equal 1, Resque::Failure.count
75
+ Resque::Failure.clear
76
+ assert_equal 0, Resque::Failure.count
77
+ end
78
+
79
+ it "catches exceptional jobs" do
80
+ Resque::Job.create(:jobs, BadJob)
81
+ Resque::Job.create(:jobs, BadJob)
82
+ @worker.process
83
+ @worker.process
84
+ @worker.process
85
+ assert_equal 2, Resque::Failure.count
86
+ end
87
+
88
+ it "strips whitespace from queue names" do
89
+ queues = "critical, high, low".split(',')
90
+ worker = Resque::Worker.new(*queues)
91
+ assert_equal %w( critical high low ), worker.queues
92
+ end
93
+
94
+ it "can work on multiple queues" do
95
+ Resque::Job.create(:high, GoodJob)
96
+ Resque::Job.create(:critical, GoodJob)
97
+
98
+ worker = Resque::Worker.new(:critical, :high)
99
+
100
+ worker.process
101
+ assert_equal 1, Resque.size(:high)
102
+ assert_equal 0, Resque.size(:critical)
103
+
104
+ worker.process
105
+ assert_equal 0, Resque.size(:high)
106
+ end
107
+
108
+ it "can work on all queues" do
109
+ Resque::Job.create(:high, GoodJob)
110
+ Resque::Job.create(:critical, GoodJob)
111
+ Resque::Job.create(:blahblah, GoodJob)
112
+
113
+ worker = Resque::Worker.new("*")
114
+
115
+ worker.work(0)
116
+ assert_equal 0, Resque.size(:high)
117
+ assert_equal 0, Resque.size(:critical)
118
+ assert_equal 0, Resque.size(:blahblah)
119
+ end
120
+
121
+ it "can work with wildcard at the end of the list" do
122
+ Resque::Job.create(:high, GoodJob)
123
+ Resque::Job.create(:critical, GoodJob)
124
+ Resque::Job.create(:blahblah, GoodJob)
125
+ Resque::Job.create(:beer, GoodJob)
126
+
127
+ worker = Resque::Worker.new(:critical, :high, "*")
128
+
129
+ worker.work(0)
130
+ assert_equal 0, Resque.size(:high)
131
+ assert_equal 0, Resque.size(:critical)
132
+ assert_equal 0, Resque.size(:blahblah)
133
+ assert_equal 0, Resque.size(:beer)
134
+ end
135
+
136
+ it "can work with wildcard at the middle of the list" do
137
+ Resque::Job.create(:high, GoodJob)
138
+ Resque::Job.create(:critical, GoodJob)
139
+ Resque::Job.create(:blahblah, GoodJob)
140
+ Resque::Job.create(:beer, GoodJob)
141
+
142
+ worker = Resque::Worker.new(:critical, "*", :high)
143
+
144
+ worker.work(0)
145
+ assert_equal 0, Resque.size(:high)
146
+ assert_equal 0, Resque.size(:critical)
147
+ assert_equal 0, Resque.size(:blahblah)
148
+ assert_equal 0, Resque.size(:beer)
149
+ end
150
+
151
+ it "processes * queues in alphabetical order" do
152
+ Resque::Job.create(:high, GoodJob)
153
+ Resque::Job.create(:critical, GoodJob)
154
+ Resque::Job.create(:blahblah, GoodJob)
155
+
156
+ worker = Resque::Worker.new("*")
157
+ processed_queues = []
158
+
159
+ worker.work(0) do |job|
160
+ processed_queues << job.queue
161
+ end
162
+
163
+ assert_equal %w( jobs high critical blahblah ).sort, processed_queues
164
+ end
165
+
166
+ it "can work with dynamically added queues when using wildcard" do
167
+ worker = Resque::Worker.new("*")
168
+
169
+ assert_equal ["jobs"], Resque.queues
170
+
171
+ Resque::Job.create(:high, GoodJob)
172
+ Resque::Job.create(:critical, GoodJob)
173
+ Resque::Job.create(:blahblah, GoodJob)
174
+
175
+ processed_queues = []
176
+
177
+ worker.work(0) do |job|
178
+ processed_queues << job.queue
179
+ end
180
+
181
+ assert_equal %w( jobs high critical blahblah ).sort, processed_queues
182
+ end
183
+
184
+ it "has a unique id" do
185
+ assert_equal "#{`hostname`.chomp}:#{$$}:jobs", @worker.to_s
186
+ end
187
+
188
+ it "complains if no queues are given" do
189
+ assert_raise Resque::NoQueueError do
190
+ Resque::Worker.new
191
+ end
192
+ end
193
+
194
+ it "fails if a job class has no `perform` method" do
195
+ worker = Resque::Worker.new(:perform_less)
196
+ Resque::Job.create(:perform_less, Object)
197
+
198
+ assert_equal 0, Resque::Failure.count
199
+ worker.work(0)
200
+ assert_equal 1, Resque::Failure.count
201
+ end
202
+
203
+ it "inserts itself into the 'workers' list on startup" do
204
+ @worker.work(0) do
205
+ assert_equal @worker, Resque.workers[0]
206
+ end
207
+ end
208
+
209
+ it "removes itself from the 'workers' list on shutdown" do
210
+ @worker.work(0) do
211
+ assert_equal @worker, Resque.workers[0]
212
+ end
213
+
214
+ assert_equal [], Resque.workers
215
+ end
216
+
217
+ it "removes worker with stringified id" do
218
+ @worker.work(0) do
219
+ worker_id = Resque.workers[0].to_s
220
+ Resque.remove_worker(worker_id)
221
+ assert_equal [], Resque.workers
222
+ end
223
+ end
224
+
225
+ it "records what it is working on" do
226
+ @worker.work(0) do
227
+ task = @worker.job
228
+ assert_equal({"args"=>[20, "/tmp"], "class"=>"SomeJob"}, task['payload'])
229
+ assert task['run_at']
230
+ assert_equal 'jobs', task['queue']
231
+ end
232
+ end
233
+
234
+ it "clears its status when not working on anything" do
235
+ @worker.work(0)
236
+ assert_equal Hash.new, @worker.job
237
+ end
238
+
239
+ it "knows when it is working" do
240
+ @worker.work(0) do
241
+ assert @worker.working?
242
+ end
243
+ end
244
+
245
+ it "knows when it is idle" do
246
+ @worker.work(0)
247
+ assert @worker.idle?
248
+ end
249
+
250
+ it "knows who is working" do
251
+ @worker.work(0) do
252
+ assert_equal [@worker], Resque.working
253
+ end
254
+ end
255
+
256
+ it "keeps track of how many jobs it has processed" do
257
+ Resque::Job.create(:jobs, BadJob)
258
+ Resque::Job.create(:jobs, BadJob)
259
+
260
+ 3.times do
261
+ job = @worker.reserve
262
+ @worker.process job
263
+ end
264
+ assert_equal 3, @worker.processed
265
+ end
266
+
267
+ it "reserve blocks when the queue is empty" do
268
+ worker = Resque::Worker.new(:timeout)
269
+
270
+ assert_raises Timeout::Error do
271
+ Timeout.timeout(1) { worker.reserve(5) }
272
+ end
273
+ end
274
+
275
+ it "reserve returns nil when there is no job and is polling" do
276
+ worker = Resque::Worker.new(:timeout)
277
+
278
+ assert_equal nil, worker.reserve(1)
279
+ end
280
+
281
+ it "keeps track of how many failures it has seen" do
282
+ Resque::Job.create(:jobs, BadJob)
283
+ Resque::Job.create(:jobs, BadJob)
284
+
285
+ 3.times do
286
+ job = @worker.reserve
287
+ @worker.process job
288
+ end
289
+ assert_equal 2, @worker.failed
290
+ end
291
+
292
+ it "stats are erased when the worker goes away" do
293
+ @worker.work(0)
294
+ assert_equal 0, @worker.processed
295
+ assert_equal 0, @worker.failed
296
+ end
297
+
298
+ it "knows when it started" do
299
+ time = Time.now
300
+ @worker.work(0) do
301
+ assert_equal time.to_s, @worker.started.to_s
302
+ end
303
+ end
304
+
305
+ it "knows whether it exists or not" do
306
+ @worker.work(0) do
307
+ assert Resque::Worker.exists?(@worker)
308
+ assert !Resque::Worker.exists?('blah-blah')
309
+ end
310
+ end
311
+
312
+ it "sets $0 while working" do
313
+ @worker.work(0) do
314
+ ver = Resque::Version
315
+ assert_equal "resque-#{ver}: Processing jobs since #{Time.now.to_i}", $0
316
+ end
317
+ end
318
+
319
+ it "can be found" do
320
+ @worker.work(0) do
321
+ found = Resque::Worker.find(@worker.to_s)
322
+ assert_equal @worker.to_s, found.to_s
323
+ assert found.working?
324
+ assert_equal @worker.job, found.job
325
+ end
326
+ end
327
+
328
+ it "doesn't find fakes" do
329
+ @worker.work(0) do
330
+ found = Resque::Worker.find('blah-blah')
331
+ assert_equal nil, found
332
+ end
333
+ end
334
+
335
+ it "cleans up dead worker info on start (crash recovery)" do
336
+ # first we fake out two dead workers
337
+ workerA = Resque::Worker.new(:jobs)
338
+ workerA.instance_variable_set(:@to_s, "#{`hostname`.chomp}:1:jobs")
339
+ workerA.register_worker
340
+
341
+ workerB = Resque::Worker.new(:high, :low)
342
+ workerB.instance_variable_set(:@to_s, "#{`hostname`.chomp}:2:high,low")
343
+ workerB.register_worker
344
+
345
+ assert_equal 2, Resque.workers.size
346
+
347
+ # then we prune them
348
+ @worker.work(0) do
349
+ assert_equal 1, Resque.workers.size
350
+ end
351
+ end
352
+
353
+ it "worker_pids returns pids" do
354
+ known_workers = @worker.worker_pids
355
+ assert !known_workers.empty?
356
+ end
357
+
358
+ it "Processed jobs count" do
359
+ @worker.work(0)
360
+ assert_equal 1, Resque.info[:processed]
361
+ end
362
+
363
+ it "Will call a before_first_fork hook only once" do
364
+ $BEFORE_FORK_CALLED = 0
365
+ Resque.before_first_fork = Proc.new { $BEFORE_FORK_CALLED += 1 }
366
+ workerA = Resque::Worker.new(:jobs)
367
+ Resque::Job.create(:jobs, SomeJob, 20, '/tmp')
368
+
369
+ assert_equal 0, $BEFORE_FORK_CALLED
370
+
371
+ workerA.work(0)
372
+ assert_equal 1, $BEFORE_FORK_CALLED
373
+
374
+ # TODO: Verify it's only run once. Not easy.
375
+ # workerA.work(0)
376
+ # assert_equal 1, $BEFORE_FORK_CALLED
377
+ end
378
+
379
+ it "Will call a before_fork hook before forking" do
380
+ $BEFORE_FORK_CALLED = false
381
+ Resque.before_fork = Proc.new { $BEFORE_FORK_CALLED = true }
382
+ workerA = Resque::Worker.new(:jobs)
383
+
384
+ assert !$BEFORE_FORK_CALLED
385
+ Resque::Job.create(:jobs, SomeJob, 20, '/tmp')
386
+ workerA.work(0)
387
+ assert $BEFORE_FORK_CALLED
388
+ end
389
+
390
+ it "very verbose works in the afternoon" do
391
+ begin
392
+ require 'time'
393
+ last_puts = ""
394
+ Time.fake_time = Time.parse("15:44:33 2011-03-02")
395
+
396
+ @worker.extend(Module.new {
397
+ define_method(:puts) { |thing| last_puts = thing }
398
+ })
399
+
400
+ @worker.very_verbose = true
401
+ @worker.log("some log text")
402
+
403
+ assert_match /\*\* \[15:44:33 2011-03-02\] \d+: some log text/, last_puts
404
+ ensure
405
+ Time.fake_time = nil
406
+ end
407
+ end
408
+
409
+ it "Will call an after_fork hook after forking" do
410
+ $AFTER_FORK_CALLED = false
411
+ Resque.after_fork = Proc.new { $AFTER_FORK_CALLED = true }
412
+ workerA = Resque::Worker.new(:jobs)
413
+
414
+ assert !$AFTER_FORK_CALLED
415
+ Resque::Job.create(:jobs, SomeJob, 20, '/tmp')
416
+ workerA.work(0)
417
+ assert $AFTER_FORK_CALLED
418
+ end
419
+
420
+ it "returns PID of running process" do
421
+ assert_equal @worker.to_s.split(":")[1].to_i, @worker.pid
422
+ end
423
+
424
+ it "requeue failed queue" do
425
+ queue = 'good_job'
426
+ Resque::Failure.create(:exception => Exception.new, :worker => Resque::Worker.new(queue), :queue => queue, :payload => {'class' => 'GoodJob'})
427
+ Resque::Failure.create(:exception => Exception.new, :worker => Resque::Worker.new(queue), :queue => 'some_job', :payload => {'class' => 'SomeJob'})
428
+ Resque::Failure.requeue_queue(queue)
429
+ assert Resque::Failure.all(0).has_key?('retried_at')
430
+ assert !Resque::Failure.all(1).has_key?('retried_at')
431
+ end
432
+
433
+ it "remove failed queue" do
434
+ queue = 'good_job'
435
+ queue2 = 'some_job'
436
+ Resque::Failure.create(:exception => Exception.new, :worker => Resque::Worker.new(queue), :queue => queue, :payload => {'class' => 'GoodJob'})
437
+ Resque::Failure.create(:exception => Exception.new, :worker => Resque::Worker.new(queue2), :queue => queue2, :payload => {'class' => 'SomeJob'})
438
+ Resque::Failure.create(:exception => Exception.new, :worker => Resque::Worker.new(queue), :queue => queue, :payload => {'class' => 'GoodJob'})
439
+ Resque::Failure.remove_queue(queue)
440
+ assert_equal queue2, Resque::Failure.all(0)['queue']
441
+ assert_equal 1, Resque::Failure.count
442
+ end
443
+ end