tom_queue 0.0.1.dev
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.
- data/lib/tom_queue.rb +56 -0
- data/lib/tom_queue/deferred_work_manager.rb +233 -0
- data/lib/tom_queue/deferred_work_set.rb +165 -0
- data/lib/tom_queue/delayed_job.rb +33 -0
- data/lib/tom_queue/delayed_job/external_messages.rb +56 -0
- data/lib/tom_queue/delayed_job/job.rb +365 -0
- data/lib/tom_queue/external_consumer.rb +136 -0
- data/lib/tom_queue/logging_helper.rb +19 -0
- data/lib/tom_queue/queue_manager.rb +264 -0
- data/lib/tom_queue/sorted_array.rb +69 -0
- data/lib/tom_queue/work.rb +62 -0
- data/spec/database.yml +14 -0
- data/spec/helper.rb +75 -0
- data/spec/tom_queue/deferred_work/deferred_work_manager_integration_spec.rb +186 -0
- data/spec/tom_queue/deferred_work/deferred_work_manager_spec.rb +134 -0
- data/spec/tom_queue/deferred_work/deferred_work_set_spec.rb +134 -0
- data/spec/tom_queue/delayed_job/delayed_job_integration_spec.rb +155 -0
- data/spec/tom_queue/delayed_job/delayed_job_spec.rb +818 -0
- data/spec/tom_queue/external_consumer_integration_spec.rb +225 -0
- data/spec/tom_queue/helper.rb +91 -0
- data/spec/tom_queue/logging_helper_spec.rb +152 -0
- data/spec/tom_queue/queue_manager_spec.rb +218 -0
- data/spec/tom_queue/sorted_array_spec.rb +160 -0
- data/spec/tom_queue/tom_queue_integration_spec.rb +296 -0
- data/spec/tom_queue/tom_queue_spec.rb +30 -0
- data/spec/tom_queue/work_spec.rb +35 -0
- data/tom_queue.gemspec +21 -0
- metadata +137 -0
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'tom_queue/helper'
|
2
|
+
|
3
|
+
describe TomQueue::DeferredWorkSet do
|
4
|
+
let(:set) { TomQueue::DeferredWorkSet.new }
|
5
|
+
|
6
|
+
it "should be creatable" do
|
7
|
+
set.should be_a(TomQueue::DeferredWorkSet)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should allow work to be scheduled" do
|
11
|
+
set.schedule(Time.now + 0.2, "something")
|
12
|
+
set.schedule(Time.now + 0.3, "else")
|
13
|
+
set.size.should == 2
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "earliest" do
|
17
|
+
|
18
|
+
it "should return nil if there is no work in the set" do
|
19
|
+
set.earliest.should be_nil
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should return the only item if there is one item in the set" do
|
23
|
+
work = double("Work")
|
24
|
+
set.schedule( Time.now + 0.3, work)
|
25
|
+
set.earliest.should == work
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should return the item in the set with the lowest run_at value" do
|
29
|
+
set.schedule( Time.now + 0.2, work1 = double("Work") )
|
30
|
+
set.schedule( Time.now + 0.1, work2 = double("Work") )
|
31
|
+
set.schedule( Time.now + 0.3, work3 = double("Work") )
|
32
|
+
set.earliest.should == work2
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "pop" do
|
38
|
+
|
39
|
+
it "should return nil when the timeout expires" do
|
40
|
+
set.pop(0.1).should be_nil
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should block for the timeout value if there is no work in the queue" do
|
44
|
+
start_time = Time.now
|
45
|
+
set.pop(0.1)
|
46
|
+
Time.now.should > start_time + 0.1
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should block until the earliest work in the set" do
|
50
|
+
start_time = Time.now
|
51
|
+
set.schedule(start_time + 1.5, "work")
|
52
|
+
set.schedule(start_time + 0.1, "work")
|
53
|
+
set.pop(10)
|
54
|
+
Time.now.should > start_time + 0.1
|
55
|
+
Time.now.should < start_time + 0.2
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should return immediately if tehre is work scheduled in the past" do
|
59
|
+
set.schedule(Time.now - 0.1, "work")
|
60
|
+
set.pop(10).should == "work"
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should have removed the returned work from the set" do
|
64
|
+
set.schedule(Time.now - 0.1, "work")
|
65
|
+
set.pop(10)
|
66
|
+
set.size.should == 0
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should return old work in temporal order" do
|
70
|
+
set.schedule(Time.now - 0.1, "work2")
|
71
|
+
set.schedule(Time.now - 0.2, "work1")
|
72
|
+
set.pop(10).should == "work1"
|
73
|
+
set.pop(10).should == "work2"
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should return the earliest work" do
|
77
|
+
start_time = Time.now
|
78
|
+
set.schedule(start_time + 0.1, "work")
|
79
|
+
set.pop(10).should == "work"
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should return immediately if it is interrupted by an external thread" do
|
83
|
+
Thread.new { sleep 0.1; set.interrupt }
|
84
|
+
start_time = Time.now
|
85
|
+
set.schedule(start_time + 1.5, "work")
|
86
|
+
set.schedule(start_time + 5, "work")
|
87
|
+
set.pop(10)
|
88
|
+
Time.now.should > start_time + 0.1
|
89
|
+
Time.now.should < start_time + 0.2
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should block until the earliest work, even if earlier work is added after the block" do
|
93
|
+
start_time = Time.now
|
94
|
+
Thread.new do
|
95
|
+
sleep 0.1
|
96
|
+
set.schedule(start_time + 0.2, "early")
|
97
|
+
end
|
98
|
+
set.schedule(start_time + 1.5, "late")
|
99
|
+
set.pop(10)
|
100
|
+
Time.now.should > start_time + 0.2
|
101
|
+
Time.now.should < start_time + 0.3
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should raise an exception if two threads try to block on the same work set" do
|
105
|
+
Thread.new do
|
106
|
+
set.pop(1)
|
107
|
+
end
|
108
|
+
sleep 0.1
|
109
|
+
lambda {
|
110
|
+
set.pop(1)
|
111
|
+
}.should raise_exception(/another thread is already blocked/)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should not get deferred items caught outside the cache" do
|
115
|
+
start_time = Time.now
|
116
|
+
50.times { |i| set.schedule(start_time+0.1+i*0.001, "bulk") }
|
117
|
+
set.schedule(start_time+0.2, "missing")
|
118
|
+
|
119
|
+
50.times { set.pop(1).should == "bulk" }
|
120
|
+
|
121
|
+
set.schedule(start_time+0.3, "final")
|
122
|
+
set.pop(1).should == "missing"
|
123
|
+
set.pop(1).should == "final"
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should not delete all elements with the same run_at" do
|
127
|
+
the_time = Time.now + 0.1
|
128
|
+
set.schedule(the_time, "work-1")
|
129
|
+
set.schedule(the_time, "work-2")
|
130
|
+
2.times.collect { set.pop(1) }.sort.should == ["work-1", "work-2"]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'tom_queue/helper'
|
2
|
+
require 'tom_queue/delayed_job'
|
3
|
+
|
4
|
+
describe Delayed::Job, "integration spec", :timeout => 10 do
|
5
|
+
|
6
|
+
class TestJobClass
|
7
|
+
cattr_accessor :perform_hook
|
8
|
+
|
9
|
+
@@flunk_count = 0
|
10
|
+
cattr_accessor :flunk_count
|
11
|
+
|
12
|
+
@@asplode_count = 0
|
13
|
+
cattr_accessor :asplode_count
|
14
|
+
|
15
|
+
def initialize(name)
|
16
|
+
@name = name
|
17
|
+
end
|
18
|
+
|
19
|
+
def perform
|
20
|
+
@@perform_hook && @@perform_hook.call(@name)
|
21
|
+
|
22
|
+
if @@asplode_count > 0
|
23
|
+
@@asplode_count -= 1
|
24
|
+
Thread.exit
|
25
|
+
end
|
26
|
+
|
27
|
+
if @@flunk_count > 0
|
28
|
+
@@flunk_count -= 1
|
29
|
+
raise RuntimeError, "Failed to run job"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def reschedule_at(time, attempts)
|
34
|
+
time + 0.5
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
let(:job_name) { "Job-#{Time.now.to_f}" }
|
40
|
+
|
41
|
+
before do
|
42
|
+
# Clean-slate ...
|
43
|
+
TomQueue.default_prefix = "test-#{Time.now.to_f}"
|
44
|
+
TomQueue::DelayedJob.apply_hook!
|
45
|
+
Delayed::Job.delete_all
|
46
|
+
Delayed::Job.class_variable_set(:@@tomqueue_manager, nil)
|
47
|
+
|
48
|
+
# Keep track of how many times the job is run
|
49
|
+
@called = []
|
50
|
+
TestJobClass.perform_hook = lambda { |name| @called << name }
|
51
|
+
|
52
|
+
# Reset the flunk count
|
53
|
+
TestJobClass.flunk_count = 0
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should actually be using the queue" do
|
57
|
+
Delayed::Job.enqueue(TestJobClass.new(job_name))
|
58
|
+
|
59
|
+
Delayed::Job.tomqueue_manager.queues[TomQueue::NORMAL_PRIORITY].status[:message_count].should == 1
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should integrate with Delayed::Worker" do
|
63
|
+
Delayed::Job.enqueue(TestJobClass.new(job_name))
|
64
|
+
|
65
|
+
Delayed::Worker.new.work_off(1).should == [1, 0] # 1 success, 0 failed
|
66
|
+
@called.first.should == job_name
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should still back-off jobs" do
|
70
|
+
Delayed::Job.enqueue(TestJobClass.new(job_name))
|
71
|
+
TestJobClass.flunk_count = 1
|
72
|
+
|
73
|
+
Benchmark.realtime {
|
74
|
+
Delayed::Worker.new.work_off(1).should == [0, 1]
|
75
|
+
Delayed::Worker.new.work_off(1).should == [1, 0]
|
76
|
+
}.should > 0.5
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should support run_at" do
|
80
|
+
Benchmark.realtime {
|
81
|
+
Delayed::Job.enqueue(TestJobClass.new("job1"), :run_at => Time.now + 0.5)
|
82
|
+
Delayed::Job.enqueue(TestJobClass.new("job2"), :run_at => Time.now + 0.1)
|
83
|
+
Delayed::Worker.new.work_off(2).should == [2, 0]
|
84
|
+
}.should > 0.1
|
85
|
+
@called.should == ["job2", "job1"]
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should support job priorities" do
|
89
|
+
TomQueue::DelayedJob.priority_map[0] = TomQueue::NORMAL_PRIORITY
|
90
|
+
TomQueue::DelayedJob.priority_map[1] = TomQueue::HIGH_PRIORITY
|
91
|
+
Delayed::Job.enqueue(TestJobClass.new("low1"), :priority => 0)
|
92
|
+
Delayed::Job.enqueue(TestJobClass.new("low2"), :priority => 0)
|
93
|
+
Delayed::Job.enqueue(TestJobClass.new("high"), :priority => 1)
|
94
|
+
Delayed::Job.enqueue(TestJobClass.new("low3"), :priority => 0)
|
95
|
+
Delayed::Job.enqueue(TestJobClass.new("low4"), :priority => 0)
|
96
|
+
Delayed::Worker.new.work_off(5)
|
97
|
+
@called.should == ["high", "low1", "low2", "low3", "low4"]
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should not run a failed job" do
|
101
|
+
logfile = Tempfile.new('logfile')
|
102
|
+
TomQueue.logger = Logger.new(logfile.path)
|
103
|
+
Delayed::Job.delete_all
|
104
|
+
# this will send the notification
|
105
|
+
job = "Hello".delay.to_s
|
106
|
+
|
107
|
+
# now make the job look like it has failed
|
108
|
+
job.attempts = 0
|
109
|
+
job.failed_at = Time.now
|
110
|
+
job.last_error = "Some error"
|
111
|
+
job.save
|
112
|
+
|
113
|
+
job.should be_failed
|
114
|
+
|
115
|
+
Delayed::Job.tomqueue_republish
|
116
|
+
|
117
|
+
# The job should get ignored for both runs
|
118
|
+
Delayed::Worker.new.work_off(1)
|
119
|
+
Delayed::Worker.new.work_off(1)
|
120
|
+
|
121
|
+
# And, since it never got run, it should still exist!
|
122
|
+
Delayed::Job.find_by_id(job.id).should_not be_nil
|
123
|
+
# And it should have been noisy, too.
|
124
|
+
File.read(logfile.path).should =~ /Received notification for failed job #{job.id}/
|
125
|
+
end
|
126
|
+
|
127
|
+
# it "should re-run the job once max_run_time is reached if, say, a worker crashes" do
|
128
|
+
# Delayed::Worker.max_run_time = 2
|
129
|
+
|
130
|
+
# job = Delayed::Job.enqueue(TestJobClass.new("work"))
|
131
|
+
|
132
|
+
# # This thread will be abruptly terminated mid-job
|
133
|
+
# TestJobClass.asplode_count = 1
|
134
|
+
# lock_stale_time = Time.now.to_f + Delayed::Worker.max_run_time
|
135
|
+
# Thread.new { Delayed::Worker.new.work_off(1) }.join
|
136
|
+
|
137
|
+
# # This will shutdown the various channels, which should result in the message being
|
138
|
+
# # returned to the broker.
|
139
|
+
# Delayed::Job.tomqueue_manager.setup_amqp!
|
140
|
+
|
141
|
+
# # Make sure the job is still locked
|
142
|
+
# job.reload
|
143
|
+
# job.locked_at.should_not be_nil
|
144
|
+
# job.locked_by.should_not be_nil
|
145
|
+
|
146
|
+
# # Now wait for the max_run_time, which is artificially low
|
147
|
+
# while Delayed::Job.find_by_id(job.id)
|
148
|
+
# Delayed::Worker.new.work_off(1)
|
149
|
+
# end
|
150
|
+
|
151
|
+
# # Ensure the worker blocked until the job's original lock was actually stale.
|
152
|
+
# Time.now.to_f.should > lock_stale_time.to_f
|
153
|
+
# end
|
154
|
+
|
155
|
+
end
|
@@ -0,0 +1,818 @@
|
|
1
|
+
require 'tom_queue/helper'
|
2
|
+
|
3
|
+
describe TomQueue, "once hooked" do
|
4
|
+
|
5
|
+
let(:job) { Delayed::Job.create! }
|
6
|
+
let(:new_job) { Delayed::Job.new }
|
7
|
+
|
8
|
+
|
9
|
+
it "should set the Delayed::Worker sleep delay to 0" do
|
10
|
+
# This makes sure the Delayed::Worker loop spins around on
|
11
|
+
# an empty queue to block on TomQueue::QueueManager#pop, so
|
12
|
+
# the job will start as soon as we receive a push from RMQ
|
13
|
+
Delayed::Worker.sleep_delay.should == 0
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "TomQueue::DelayedJob::Job" do
|
17
|
+
it "should use the TomQueue job as the Delayed::Job" do
|
18
|
+
Delayed::Job.should == TomQueue::DelayedJob::Job
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should be a subclass of ::Delayed::Backend::ActiveRecord::Job" do
|
22
|
+
TomQueue::DelayedJob::Job.superclass.should == ::Delayed::Backend::ActiveRecord::Job
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "Delayed::Job.tomqueue_manager" do
|
27
|
+
it "should return a TomQueue::QueueManager instance" do
|
28
|
+
Delayed::Job.tomqueue_manager.should be_a(TomQueue::QueueManager)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should have used the default prefix configured" do
|
32
|
+
Delayed::Job.tomqueue_manager.prefix.should == TomQueue.default_prefix
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should return the same object on subsequent calls" do
|
36
|
+
Delayed::Job.tomqueue_manager.should == Delayed::Job.tomqueue_manager
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should be reset by rspec (1)" do
|
40
|
+
TomQueue.default_prefix = "foo"
|
41
|
+
Delayed::Job.tomqueue_manager.prefix.should == "foo"
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should be reset by rspec (2)" do
|
45
|
+
TomQueue.default_prefix = "bar"
|
46
|
+
Delayed::Job.tomqueue_manager.prefix.should == "bar"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "Delayed::Job#tomqueue_digest" do
|
51
|
+
|
52
|
+
it "should return a different value when the object is saved" do
|
53
|
+
first_digest = job.tomqueue_digest
|
54
|
+
job.update_attributes(:run_at => Time.now + 10.seconds)
|
55
|
+
job.tomqueue_digest.should_not == first_digest
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should return the same value, regardless of the time zone (regression)" do
|
59
|
+
ActiveRecord::Base.time_zone_aware_attributes = true
|
60
|
+
old_zone, Time.zone = Time.zone, "Hawaii"
|
61
|
+
|
62
|
+
job = Delayed::Job.create!
|
63
|
+
first_digest = job.tomqueue_digest
|
64
|
+
|
65
|
+
Time.zone = "Auckland"
|
66
|
+
|
67
|
+
job = Delayed::Job.find(job.id)
|
68
|
+
job.tomqueue_digest.should == first_digest
|
69
|
+
|
70
|
+
Time.zone = old_zone
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "Delayed::Job#tomqueue_payload" do
|
75
|
+
|
76
|
+
let(:payload) { JSON.load(job.tomqueue_payload)}
|
77
|
+
|
78
|
+
it "should return a hash" do
|
79
|
+
payload.should be_a(Hash)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should contain the job id" do
|
83
|
+
payload['delayed_job_id'].should == job.id
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should contain the current updated_at timestamp (with second-level precision)" do
|
87
|
+
payload['delayed_job_updated_at'].should == job.updated_at.iso8601(0)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should contain the digest after saving" do
|
91
|
+
payload['delayed_job_digest'].should == job.tomqueue_digest
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "Delayed::Job#tomqueue_publish" do
|
96
|
+
|
97
|
+
it "should return nil" do
|
98
|
+
job.tomqueue_publish.should be_nil
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should raise an exception if it is called on an unsaved job" do
|
102
|
+
TomQueue.exception_reporter = double("SilentExceptionReporter", :notify => nil)
|
103
|
+
lambda {
|
104
|
+
Delayed::Job.new.tomqueue_publish
|
105
|
+
}.should raise_exception(ArgumentError, /cannot publish an unsaved Delayed::Job/)
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "when it is called on a persisted job" do
|
109
|
+
|
110
|
+
before do
|
111
|
+
job # create the job first so we don't trigger the expectation twice
|
112
|
+
|
113
|
+
@called = false
|
114
|
+
Delayed::Job.tomqueue_manager.should_receive(:publish) do |payload, opts|
|
115
|
+
@called = true
|
116
|
+
@payload = payload
|
117
|
+
@opts = opts
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should call publish on the queue manager" do
|
122
|
+
job.tomqueue_publish
|
123
|
+
@called.should be_true
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "job priority" do
|
127
|
+
before do
|
128
|
+
TomQueue::DelayedJob.priority_map[-10] = TomQueue::BULK_PRIORITY
|
129
|
+
TomQueue::DelayedJob.priority_map[10] = TomQueue::HIGH_PRIORITY
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should map the priority of the job to the TomQueue priority" do
|
133
|
+
new_job.priority = -10
|
134
|
+
new_job.save
|
135
|
+
@opts[:priority].should == TomQueue::BULK_PRIORITY
|
136
|
+
end
|
137
|
+
|
138
|
+
describe "if an unknown priority value is used" do
|
139
|
+
before do
|
140
|
+
new_job.priority = 99
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should default the priority to TomQueue::NORMAL_PRIORITY" do
|
144
|
+
new_job.save
|
145
|
+
@opts[:priority].should == TomQueue::NORMAL_PRIORITY
|
146
|
+
end
|
147
|
+
|
148
|
+
it "should log a warning" do
|
149
|
+
TomQueue.logger.should_receive(:warn)
|
150
|
+
new_job.save
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "run_at value" do
|
156
|
+
|
157
|
+
it "should use the job's :run_at value by default" do
|
158
|
+
job.tomqueue_publish
|
159
|
+
@opts[:run_at].should == job.run_at
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should use the run_at value provided if provided by the caller" do
|
163
|
+
the_time = Time.now + 10.seconds
|
164
|
+
job.tomqueue_publish(the_time)
|
165
|
+
@opts[:run_at].should == the_time
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
describe "the payload" do
|
171
|
+
|
172
|
+
before { job.stub(:tomqueue_payload => "PAYLOAD") }
|
173
|
+
|
174
|
+
it "should be the return value from #tomqueue_payload" do
|
175
|
+
job.tomqueue_publish
|
176
|
+
@payload.should == "PAYLOAD"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe "if an exception is raised during the publish" do
|
182
|
+
let(:exception) { RuntimeError.new("Bugger. Dropped the ball, sorry.") }
|
183
|
+
|
184
|
+
before do
|
185
|
+
TomQueue.exception_reporter = double("SilentExceptionReporter", :notify => nil)
|
186
|
+
Delayed::Job.tomqueue_manager.should_receive(:publish).and_raise(exception)
|
187
|
+
end
|
188
|
+
|
189
|
+
it "should not be raised out to the caller" do
|
190
|
+
lambda { new_job.save }.should_not raise_exception
|
191
|
+
end
|
192
|
+
|
193
|
+
it "should notify the exception reporter" do
|
194
|
+
TomQueue.exception_reporter.should_receive(:notify).with(exception)
|
195
|
+
new_job.save
|
196
|
+
end
|
197
|
+
|
198
|
+
it "should do nothing if the exception reporter is nil" do
|
199
|
+
TomQueue.exception_reporter = nil
|
200
|
+
lambda { new_job.save }.should_not raise_exception
|
201
|
+
end
|
202
|
+
|
203
|
+
it "should log an error message to the log" do
|
204
|
+
TomQueue.logger.should_receive(:error)
|
205
|
+
new_job.save
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
describe "publish callbacks in Job lifecycle" do
|
211
|
+
|
212
|
+
it "should allow Mock::ExpectationFailed exceptions to escape the callback" do
|
213
|
+
TomQueue.logger = Logger.new("/dev/null")
|
214
|
+
TomQueue.exception_reporter = nil
|
215
|
+
Delayed::Job.tomqueue_manager.should_receive(:publish).with("spurious arguments").once
|
216
|
+
lambda {
|
217
|
+
job.update_attributes(:run_at => Time.now + 5.seconds)
|
218
|
+
}.should raise_exception(RSpec::Mocks::MockExpectationError)
|
219
|
+
|
220
|
+
Delayed::Job.tomqueue_manager.publish("spurious arguments") # do this, otherwise it will fail
|
221
|
+
end
|
222
|
+
|
223
|
+
it "should not publish a message if the job has a non-nil failed_at" do
|
224
|
+
job.should_not_receive(:tomqueue_publish)
|
225
|
+
job.update_attributes(:failed_at => Time.now)
|
226
|
+
end
|
227
|
+
|
228
|
+
it "should be called after create when there is no explicit transaction" do
|
229
|
+
new_job.should_receive(:tomqueue_publish).with(no_args)
|
230
|
+
new_job.save!
|
231
|
+
end
|
232
|
+
|
233
|
+
it "should be called after update when there is no explicit transaction" do
|
234
|
+
job.should_receive(:tomqueue_publish).with(no_args)
|
235
|
+
job.run_at = Time.now + 10.seconds
|
236
|
+
job.save!
|
237
|
+
end
|
238
|
+
|
239
|
+
it "should be called after commit, when a record is saved" do
|
240
|
+
new_job.stub(:tomqueue_publish) { @called = true }
|
241
|
+
Delayed::Job.transaction do
|
242
|
+
new_job.save!
|
243
|
+
|
244
|
+
@called.should be_nil
|
245
|
+
end
|
246
|
+
@called.should be_true
|
247
|
+
end
|
248
|
+
|
249
|
+
it "should be called after commit, when a record is updated" do
|
250
|
+
job.stub(:tomqueue_publish) { @called = true }
|
251
|
+
Delayed::Job.transaction do
|
252
|
+
job.run_at = Time.now + 10.seconds
|
253
|
+
job.save!
|
254
|
+
@called.should be_nil
|
255
|
+
end
|
256
|
+
|
257
|
+
@called.should be_true
|
258
|
+
end
|
259
|
+
|
260
|
+
it "should not be called when a record is destroyed" do
|
261
|
+
job.should_not_receive(:tomqueue_publish)
|
262
|
+
job.destroy
|
263
|
+
end
|
264
|
+
|
265
|
+
it "should not be called by a destroy in a transaction" do
|
266
|
+
job.should_not_receive(:tomqueue_publish)
|
267
|
+
Delayed::Job.transaction { job.destroy }
|
268
|
+
end
|
269
|
+
|
270
|
+
it "should not be called if the update transaction is rolled back" do
|
271
|
+
job.stub(:tomqueue_publish) { @called = true }
|
272
|
+
|
273
|
+
Delayed::Job.transaction do
|
274
|
+
job.run_at = Time.now + 10.seconds
|
275
|
+
job.save!
|
276
|
+
raise ActiveRecord::Rollback
|
277
|
+
end
|
278
|
+
@called.should be_nil
|
279
|
+
end
|
280
|
+
|
281
|
+
it "should not be called if the create transaction is rolled back" do
|
282
|
+
job.should_not_receive(:tomqueue_publish)
|
283
|
+
|
284
|
+
Delayed::Job.transaction do
|
285
|
+
new_job.save!
|
286
|
+
raise ActiveRecord::Rollback
|
287
|
+
end
|
288
|
+
@called.should be_nil
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
describe "Delayed::Job.tomqueue_republish method" do
|
293
|
+
before { Delayed::Job.delete_all }
|
294
|
+
|
295
|
+
it "should exist" do
|
296
|
+
Delayed::Job.respond_to?(:tomqueue_republish).should be_true
|
297
|
+
end
|
298
|
+
|
299
|
+
it "should return nil" do
|
300
|
+
Delayed::Job.tomqueue_republish.should be_nil
|
301
|
+
end
|
302
|
+
|
303
|
+
it "should call #tomqueue_publish on all DB records" do
|
304
|
+
10.times { Delayed::Job.create! }
|
305
|
+
|
306
|
+
Delayed::Job.tomqueue_manager.queues[TomQueue::NORMAL_PRIORITY].purge
|
307
|
+
queue = Delayed::Job.tomqueue_manager.queues[TomQueue::NORMAL_PRIORITY]
|
308
|
+
queue.message_count.should == 0
|
309
|
+
|
310
|
+
Delayed::Job.tomqueue_republish
|
311
|
+
queue.message_count.should == 10
|
312
|
+
end
|
313
|
+
|
314
|
+
it "should work with ActiveRecord scopes" do
|
315
|
+
first_ids = 10.times.collect { Delayed::Job.create!.id }
|
316
|
+
second_ids = 7.times.collect { Delayed::Job.create!.id }
|
317
|
+
|
318
|
+
Delayed::Job.tomqueue_manager.queues[TomQueue::NORMAL_PRIORITY].purge
|
319
|
+
queue = Delayed::Job.tomqueue_manager.queues[TomQueue::NORMAL_PRIORITY]
|
320
|
+
queue.message_count.should == 0
|
321
|
+
|
322
|
+
Delayed::Job.where('id IN (?)', second_ids).tomqueue_republish
|
323
|
+
queue.message_count.should == 7
|
324
|
+
end
|
325
|
+
|
326
|
+
end
|
327
|
+
|
328
|
+
describe "Delayed::Job.acquire_locked_job" do
|
329
|
+
let(:time) { Delayed::Job.db_time_now }
|
330
|
+
before { Delayed::Job.stub(:db_time_now => time) }
|
331
|
+
|
332
|
+
let(:job) { Delayed::Job.create! }
|
333
|
+
let(:worker) { Delayed::Worker.new }
|
334
|
+
|
335
|
+
# make sure the job exists!
|
336
|
+
before { job }
|
337
|
+
|
338
|
+
subject { Delayed::Job.acquire_locked_job(job.id, worker, &@block) }
|
339
|
+
|
340
|
+
describe "when the job doesn't exist" do
|
341
|
+
before { job.destroy }
|
342
|
+
|
343
|
+
it "should return nil" do
|
344
|
+
subject.should be_nil
|
345
|
+
end
|
346
|
+
|
347
|
+
it "should not yield if a block is provided" do
|
348
|
+
@block = lambda { |value| @called = true}
|
349
|
+
subject
|
350
|
+
@called.should be_nil
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
describe "when the job exists" do
|
355
|
+
|
356
|
+
it "should hold an explicit DB lock whilst performing the lock" do
|
357
|
+
pending("Only possible when using mysql2 adapter (ADAPTER=mysql environment)") unless ActiveRecord::Base.connection.class.to_s == "ActiveRecord::ConnectionAdapters::Mysql2Adapter"
|
358
|
+
|
359
|
+
# Ok, fudge a second parallel connection to MySQL
|
360
|
+
second_connection = ActiveRecord::Base.connection.dup
|
361
|
+
second_connection.reconnect!
|
362
|
+
ActiveRecord::Base.connection.reset!
|
363
|
+
|
364
|
+
# Assert we have separate connections
|
365
|
+
second_connection.select("SELECT connection_id() as id;").first["id"].should_not ==
|
366
|
+
ActiveRecord::Base.connection.select("SELECT connection_id() as id;").first["id"]
|
367
|
+
|
368
|
+
# This is called in a thread when the transaction is open to query the job, store the response
|
369
|
+
# and the time when the response comes back
|
370
|
+
parallel_query = lambda do |job_id|
|
371
|
+
begin
|
372
|
+
# Simulate another worker performing a SELECT ... FOR UPDATE request
|
373
|
+
@query_result = second_connection.select("SELECT * FROM delayed_jobs WHERE id=#{job_id} LIMIT 1 FOR UPDATE").first
|
374
|
+
@query_returned_at = Time.now.to_f
|
375
|
+
rescue
|
376
|
+
puts "Failed #{$!.inspect}"
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
# When we have the transaction open, we emit a query on a parallel thread and check the timing
|
381
|
+
@block = lambda do |job|
|
382
|
+
@thread = Thread.new(job.id, ¶llel_query)
|
383
|
+
sleep 0.25
|
384
|
+
@leaving_transaction_at = Time.now.to_f
|
385
|
+
true
|
386
|
+
end
|
387
|
+
|
388
|
+
# Kick it all off !
|
389
|
+
subject
|
390
|
+
|
391
|
+
@thread.join
|
392
|
+
|
393
|
+
# now make sure the parallel thread blocked until the transaction returned
|
394
|
+
@query_returned_at.should > @leaving_transaction_at
|
395
|
+
|
396
|
+
# make sure the returned record showed the lock
|
397
|
+
@query_result["locked_at"].should_not be_nil
|
398
|
+
@query_result["locked_by"].should_not be_nil
|
399
|
+
end
|
400
|
+
|
401
|
+
describe "when the job is marked as failed" do
|
402
|
+
let(:failed_time) { Time.now - 10 }
|
403
|
+
|
404
|
+
before do
|
405
|
+
job.update_attribute(:failed_at, failed_time)
|
406
|
+
end
|
407
|
+
|
408
|
+
it "should return nil" do
|
409
|
+
subject.should be_nil
|
410
|
+
end
|
411
|
+
|
412
|
+
it "should not modify the failed_at value" do
|
413
|
+
subject
|
414
|
+
job.reload
|
415
|
+
job.failed_at.to_i.should == failed_time.to_i
|
416
|
+
end
|
417
|
+
|
418
|
+
it "should not lock the job" do
|
419
|
+
subject
|
420
|
+
job.reload
|
421
|
+
job.locked_by.should be_nil
|
422
|
+
job.locked_at.should be_nil
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
describe "when the notification is delivered too soon" do
|
427
|
+
|
428
|
+
before do
|
429
|
+
actual_time = Delayed::Job.db_time_now
|
430
|
+
Delayed::Job.stub(:db_time_now => actual_time - 10)
|
431
|
+
end
|
432
|
+
|
433
|
+
it "should return nil" do
|
434
|
+
subject.should be_nil
|
435
|
+
end
|
436
|
+
|
437
|
+
it "should re-post a notification" do
|
438
|
+
Delayed::Job.tomqueue_manager.should_receive(:publish) do |payload, args|
|
439
|
+
args[:run_at].to_i.should == job.run_at.to_i
|
440
|
+
end
|
441
|
+
subject
|
442
|
+
end
|
443
|
+
|
444
|
+
it "should not lock the job" do
|
445
|
+
subject
|
446
|
+
job.reload
|
447
|
+
job.locked_by.should be_nil
|
448
|
+
job.locked_at.should be_nil
|
449
|
+
end
|
450
|
+
|
451
|
+
end
|
452
|
+
|
453
|
+
describe "when the job is not locked" do
|
454
|
+
|
455
|
+
it "should acquire the lock fields on the job" do
|
456
|
+
subject
|
457
|
+
job.reload
|
458
|
+
job.locked_at.to_i.should == time.to_i
|
459
|
+
job.locked_by.should == worker.name
|
460
|
+
end
|
461
|
+
|
462
|
+
it "should return the job object" do
|
463
|
+
subject.should be_a(Delayed::Job)
|
464
|
+
subject.id.should == job.id
|
465
|
+
end
|
466
|
+
|
467
|
+
it "should yield the job to the block if present" do
|
468
|
+
@block = lambda { |value| @called = value}
|
469
|
+
subject
|
470
|
+
@called.should be_a(Delayed::Job)
|
471
|
+
@called.id.should == job.id
|
472
|
+
end
|
473
|
+
|
474
|
+
it "should not have locked the job when the block is called" do
|
475
|
+
@block = lambda { |job| @called = [job.id, job.locked_at, job.locked_by]; true }
|
476
|
+
subject
|
477
|
+
@called.should == [job.id, nil, nil]
|
478
|
+
end
|
479
|
+
|
480
|
+
describe "if the supplied block returns true" do
|
481
|
+
before { @block = lambda { |_| true } }
|
482
|
+
|
483
|
+
it "should lock the job" do
|
484
|
+
subject
|
485
|
+
job.reload
|
486
|
+
job.locked_at.to_i.should == time.to_i
|
487
|
+
job.locked_by.should == worker.name
|
488
|
+
end
|
489
|
+
|
490
|
+
it "should return the job" do
|
491
|
+
subject.should be_a(Delayed::Job)
|
492
|
+
subject.id.should == job.id
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
496
|
+
describe "if the supplied block returns false" do
|
497
|
+
before { @block = lambda { |_| false } }
|
498
|
+
|
499
|
+
it "should not lock the job" do
|
500
|
+
subject
|
501
|
+
job.reload
|
502
|
+
job.locked_at.should be_nil
|
503
|
+
job.locked_by.should be_nil
|
504
|
+
end
|
505
|
+
|
506
|
+
it "should return nil" do
|
507
|
+
subject.should be_nil
|
508
|
+
end
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
describe "when the job is locked with a valid lock" do
|
513
|
+
|
514
|
+
before do
|
515
|
+
@old_locked_by = job.locked_by = "some worker"
|
516
|
+
@old_locked_at = job.locked_at = Time.now
|
517
|
+
job.save!
|
518
|
+
end
|
519
|
+
|
520
|
+
it "should not yield to a block if provided" do
|
521
|
+
@called = false
|
522
|
+
@block = lambda { |_| @called = true}
|
523
|
+
subject
|
524
|
+
@called.should be_false
|
525
|
+
end
|
526
|
+
|
527
|
+
it "should return false" do
|
528
|
+
subject.should be_false
|
529
|
+
end
|
530
|
+
|
531
|
+
it "should not change the lock" do
|
532
|
+
subject
|
533
|
+
job.reload
|
534
|
+
job.locked_by.should == @old_locked_by
|
535
|
+
job.locked_at.to_i.should == @old_locked_at.to_i
|
536
|
+
end
|
537
|
+
|
538
|
+
end
|
539
|
+
|
540
|
+
describe "when the job is locked with a stale lock" do
|
541
|
+
before do
|
542
|
+
@old_locked_by = job.locked_by = "some worker"
|
543
|
+
@old_locked_at = job.locked_at = (Time.now - Delayed::Worker.max_run_time - 1)
|
544
|
+
job.save!
|
545
|
+
end
|
546
|
+
|
547
|
+
it "should return the job" do
|
548
|
+
subject.should be_a(Delayed::Job)
|
549
|
+
subject.id.should == job.id
|
550
|
+
end
|
551
|
+
|
552
|
+
it "should update the lock" do
|
553
|
+
subject
|
554
|
+
job.reload
|
555
|
+
job.locked_at.should_not == @old_locked_at
|
556
|
+
job.locked_by.should_not == @old_locked_by
|
557
|
+
end
|
558
|
+
|
559
|
+
# This is tricky - if we have a stale lock, the job object
|
560
|
+
# will have been updated by the first worker, so the digest will
|
561
|
+
# now be invalid (since updated_at will have changed)
|
562
|
+
#
|
563
|
+
# So, we don't yield, we just presume we're carrying on from where
|
564
|
+
# a previous worker left off and don't try and validate the job any
|
565
|
+
# further.
|
566
|
+
it "should not yield the block if supplied" do
|
567
|
+
@called = false
|
568
|
+
@block = lambda { |_| @called = true}
|
569
|
+
subject
|
570
|
+
@called.should be_false
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
describe "Delayed::Job.reserve - return the next job" do
|
578
|
+
let(:job) { Delayed::Job.create! }
|
579
|
+
let(:worker) { double("Worker", :name => "Worker-Name-#{Time.now.to_f}") }
|
580
|
+
let(:payload) { job.tomqueue_payload }
|
581
|
+
let(:work) { double("Work", :payload => payload, :ack! => nil) }
|
582
|
+
|
583
|
+
subject { Delayed::Job.reserve(worker) }
|
584
|
+
|
585
|
+
before do
|
586
|
+
Delayed::Job.tomqueue_manager.stub(:pop => work)
|
587
|
+
end
|
588
|
+
|
589
|
+
it "should call pop on the queue manager" do
|
590
|
+
Delayed::Job.tomqueue_manager.should_receive(:pop)
|
591
|
+
|
592
|
+
subject
|
593
|
+
end
|
594
|
+
|
595
|
+
describe "signal handling" do
|
596
|
+
it "should allow signal handlers during the pop" do
|
597
|
+
Delayed::Worker.raise_signal_exceptions = false
|
598
|
+
Delayed::Job.tomqueue_manager.should_receive(:pop) do
|
599
|
+
Delayed::Worker.raise_signal_exceptions.should be_true
|
600
|
+
work
|
601
|
+
end
|
602
|
+
Delayed::Job.reserve(worker)
|
603
|
+
end
|
604
|
+
|
605
|
+
it "should reset the signal handler var after the pop" do
|
606
|
+
Delayed::Worker.raise_signal_exceptions = false
|
607
|
+
subject
|
608
|
+
Delayed::Worker.raise_signal_exceptions.should == false
|
609
|
+
end
|
610
|
+
|
611
|
+
it "should reset the signal handler var even if it's already true" do
|
612
|
+
Delayed::Worker.raise_signal_exceptions = true
|
613
|
+
subject
|
614
|
+
Delayed::Worker.raise_signal_exceptions.should == true
|
615
|
+
end
|
616
|
+
|
617
|
+
it "should allow exceptions to escape the function" do
|
618
|
+
Delayed::Job.tomqueue_manager.should_receive(:pop) do
|
619
|
+
raise SignalException, "INT"
|
620
|
+
end
|
621
|
+
lambda { subject }.should raise_exception(SignalException)
|
622
|
+
end
|
623
|
+
end
|
624
|
+
|
625
|
+
describe "if a nil message is popped" do
|
626
|
+
before { Delayed::Job.tomqueue_manager.stub(:pop=>nil) }
|
627
|
+
|
628
|
+
it "should return nil" do
|
629
|
+
subject.should be_nil
|
630
|
+
end
|
631
|
+
|
632
|
+
it "should sleep for a second to avoid potentially tight loops" do
|
633
|
+
start_time = Time.now
|
634
|
+
subject
|
635
|
+
(Time.now - start_time).should > 1.0
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
describe "if the work payload doesn't cleanly JSON decode" do
|
640
|
+
before do
|
641
|
+
TomQueue.logger = Logger.new("/dev/null")
|
642
|
+
TomQueue.exception_reporter = nil
|
643
|
+
end
|
644
|
+
|
645
|
+
let(:payload) { "NOT JSON!!1" }
|
646
|
+
|
647
|
+
it "should report an exception" do
|
648
|
+
TomQueue.exception_reporter = mock("Reporter", :notify => nil)
|
649
|
+
TomQueue.exception_reporter.should_receive(:notify).with(instance_of(JSON::ParserError))
|
650
|
+
subject
|
651
|
+
end
|
652
|
+
|
653
|
+
it "should be happy if no exception reporter is set" do
|
654
|
+
TomQueue.exception_reporter = nil
|
655
|
+
subject
|
656
|
+
end
|
657
|
+
|
658
|
+
it "should ack the message" do
|
659
|
+
work.should_receive(:ack!)
|
660
|
+
subject
|
661
|
+
end
|
662
|
+
|
663
|
+
it "should log an error" do
|
664
|
+
TomQueue.logger.should_receive(:error)
|
665
|
+
subject
|
666
|
+
end
|
667
|
+
|
668
|
+
it "should return nil" do
|
669
|
+
subject.should be_nil
|
670
|
+
end
|
671
|
+
end
|
672
|
+
|
673
|
+
it "should call acquire_locked_job with the job_id and the worker" do
|
674
|
+
Delayed::Job.should_receive(:acquire_locked_job).with(job.id, worker)
|
675
|
+
subject
|
676
|
+
end
|
677
|
+
|
678
|
+
it "should attach a block to the call to acquire_locked_job" do
|
679
|
+
def stub_implementation(job_id, worker, &block)
|
680
|
+
block.should_not be_nil
|
681
|
+
end
|
682
|
+
Delayed::Job.should_receive(:acquire_locked_job, &method(:stub_implementation)).and_return(job)
|
683
|
+
subject
|
684
|
+
end
|
685
|
+
|
686
|
+
describe "the block provided to acquire_locked_job" do
|
687
|
+
before do |test|
|
688
|
+
def stub_implementation(job_id, worker, &block)
|
689
|
+
@block = block
|
690
|
+
end
|
691
|
+
Delayed::Job.should_receive(:acquire_locked_job, &method(:stub_implementation)).and_return(job)
|
692
|
+
end
|
693
|
+
|
694
|
+
it "should return true if the digest in the message payload matches the job" do
|
695
|
+
subject
|
696
|
+
@block.call(job).should be_true
|
697
|
+
end
|
698
|
+
|
699
|
+
it "should return false if the digest in the message payload doesn't match the job" do
|
700
|
+
subject
|
701
|
+
job.touch(:updated_at)
|
702
|
+
@block.call(job).should be_true
|
703
|
+
end
|
704
|
+
|
705
|
+
it "should return true if there is no digest in the payload object" do
|
706
|
+
work.stub(:payload => JSON.dump(JSON.load(payload).merge("delayed_job_digest" => nil)))
|
707
|
+
subject
|
708
|
+
@block.call(job).should be_true
|
709
|
+
end
|
710
|
+
|
711
|
+
end
|
712
|
+
|
713
|
+
describe "when acquire_locked_job returns the job object" do
|
714
|
+
# A.K.A We have a locked job!
|
715
|
+
|
716
|
+
let(:returned_job) { Delayed::Job.find(job.id) }
|
717
|
+
before { Delayed::Job.stub(:acquire_locked_job => returned_job) }
|
718
|
+
|
719
|
+
it "should not ack the message" do
|
720
|
+
work.should_not_receive(:ack!)
|
721
|
+
subject
|
722
|
+
end
|
723
|
+
|
724
|
+
it "should return the Job object" do
|
725
|
+
subject.should == returned_job
|
726
|
+
end
|
727
|
+
|
728
|
+
it "should associate the message object with the job" do
|
729
|
+
subject.tomqueue_work.should == work
|
730
|
+
end
|
731
|
+
|
732
|
+
end
|
733
|
+
|
734
|
+
describe "when acquire_locked_job returns false" do
|
735
|
+
# A.K.A The lock is held by another worker.
|
736
|
+
# - we post a notification to re-try after the max_run_time
|
737
|
+
|
738
|
+
before do
|
739
|
+
job.locked_at = Delayed::Job.db_time_now - 10
|
740
|
+
job.locked_by = "foobar"
|
741
|
+
job.save!
|
742
|
+
Delayed::Job.stub(:acquire_locked_job => false)
|
743
|
+
end
|
744
|
+
|
745
|
+
it "should ack the message" do
|
746
|
+
work.should_receive(:ack!)
|
747
|
+
subject
|
748
|
+
end
|
749
|
+
|
750
|
+
it "should publish a notification for after the max-run-time of the job" do
|
751
|
+
Delayed::Job.tomqueue_manager.should_receive(:publish) do |payload, opts|
|
752
|
+
opts[:run_at].to_i.should == job.locked_at.to_i + Delayed::Worker.max_run_time + 1
|
753
|
+
end
|
754
|
+
subject
|
755
|
+
end
|
756
|
+
|
757
|
+
it "should return nil" do
|
758
|
+
subject.should be_nil
|
759
|
+
end
|
760
|
+
|
761
|
+
end
|
762
|
+
|
763
|
+
describe "when acquire_locked_job returns nil" do
|
764
|
+
# A.K.A The job doesn't exist anymore!
|
765
|
+
# - we're done!
|
766
|
+
|
767
|
+
before { Delayed::Job.stub(:acquire_locked_job => nil) }
|
768
|
+
|
769
|
+
it "should ack the message" do
|
770
|
+
work.should_receive(:ack!)
|
771
|
+
subject
|
772
|
+
end
|
773
|
+
|
774
|
+
it "should return nil" do
|
775
|
+
subject.should be_nil
|
776
|
+
end
|
777
|
+
|
778
|
+
end
|
779
|
+
|
780
|
+
|
781
|
+
end
|
782
|
+
|
783
|
+
describe "Job#invoke_job" do
|
784
|
+
let(:payload) { double("DelayedJobPayload", :perform => nil)}
|
785
|
+
let(:job) { Delayed::Job.create!(:payload_object=>payload) }
|
786
|
+
|
787
|
+
it "should perform the job" do
|
788
|
+
payload.should_receive(:perform)
|
789
|
+
job.invoke_job
|
790
|
+
end
|
791
|
+
|
792
|
+
it "should not have a problem if tomqueue_work is nil" do
|
793
|
+
job.tomqueue_work = nil
|
794
|
+
job.invoke_job
|
795
|
+
end
|
796
|
+
|
797
|
+
describe "if there is a tomqueue work object set on the object" do
|
798
|
+
let(:work_object) { double("WorkObject", :ack! => nil)}
|
799
|
+
before { job.tomqueue_work = work_object}
|
800
|
+
|
801
|
+
it "should call ack! on the work object after the job has been invoked" do
|
802
|
+
payload.should_receive(:perform).ordered
|
803
|
+
work_object.should_receive(:ack!).ordered
|
804
|
+
job.invoke_job
|
805
|
+
end
|
806
|
+
|
807
|
+
it "should call ack! on the work object if an exception is raised" do
|
808
|
+
payload.should_receive(:perform).ordered.and_raise(RuntimeError, "OMG!!!11")
|
809
|
+
work_object.should_receive(:ack!).ordered
|
810
|
+
lambda {
|
811
|
+
job.invoke_job
|
812
|
+
}.should raise_exception(RuntimeError, "OMG!!!11")
|
813
|
+
end
|
814
|
+
end
|
815
|
+
end
|
816
|
+
|
817
|
+
|
818
|
+
end
|