Skip to content

Commit

Permalink
Don't depend on arity, which returns negative numbers when there are …
Browse files Browse the repository at this point in the history
…variable arguments
  • Loading branch information
bkeepers committed Sep 9, 2010
1 parent 3e8a196 commit 5dc030e
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 44 deletions.
3 changes: 1 addition & 2 deletions lib/delayed/backend/base.rb
Expand Up @@ -82,8 +82,7 @@ def unlock
def hook(name, *args)
if payload_object.respond_to?(name)
method = payload_object.method(name)
args.unshift(self)
method.call(*args.slice(0, method.arity))
method.arity == 0 ? method.call : method.call(self, *args)
end
end

Expand Down
92 changes: 50 additions & 42 deletions lib/delayed/backend/shared_spec.rb
Expand Up @@ -12,7 +12,7 @@ def create_job(opts = {})
SimpleJob.runs = 0
described_class.delete_all
end

it "should set run_at automatically if not set" do
described_class.create(:payload_object => ErrorJob.new ).run_at.should_not be_nil
end
Expand All @@ -21,7 +21,7 @@ def create_job(opts = {})
later = described_class.db_time_now + 5.minutes
described_class.create(:payload_object => ErrorJob.new, :run_at => later).run_at.should be_close(later, 1)
end

describe "enqueue" do
it "should raise ArgumentError when handler doesn't respond_to :perform" do
lambda { described_class.enqueue(Object.new) }.should raise_error(ArgumentError)
Expand Down Expand Up @@ -54,12 +54,20 @@ def create_job(opts = {})
lambda { job.invoke_job }.should change { M::ModuleJob.runs }.from(0).to(1)
end
end

describe "callbacks" do
before(:each) do
CallbackJob.messages = []
end


%w(before success after).each do |callback|
it "should call #{callback} with job" do
job = described_class.enqueue(CallbackJob.new)
job.payload_object.should_receive(callback).with(job)
job.invoke_job
end
end

it "should call before and after callbacks" do
job = described_class.enqueue(CallbackJob.new)
CallbackJob.messages.should == ["enqueue"]
Expand All @@ -70,19 +78,19 @@ def create_job(opts = {})
it "should call the after callback with an error" do
job = described_class.enqueue(CallbackJob.new)
job.payload_object.should_receive(:perform).and_raise(RuntimeError.new("fail"))

lambda { job.invoke_job }.should raise_error
CallbackJob.messages.should == ["enqueue", "before", "error: RuntimeError", "after"]
end

it "should call error when before raises an error" do
job = described_class.enqueue(CallbackJob.new)
job.payload_object.should_receive(:before).and_raise(RuntimeError.new("fail"))
lambda { job.invoke_job }.should raise_error(RuntimeError)
CallbackJob.messages.should == ["enqueue", "error: RuntimeError", "after"]
end
end

describe "payload_object" do
it "should raise a DeserializationError when the job class is totally unknown" do
job = described_class.new :handler => "--- !ruby/object:JobThatDoesNotExist {}"
Expand All @@ -94,33 +102,33 @@ def create_job(opts = {})
lambda { job.payload_object }.should raise_error(Delayed::Backend::DeserializationError)
end
end

describe "find_available" do
it "should not find failed jobs" do
@job = create_job :attempts => 50, :failed_at => described_class.db_time_now
described_class.find_available('worker', 5, 1.second).should_not include(@job)
end

it "should not find jobs scheduled for the future" do
@job = create_job :run_at => (described_class.db_time_now + 1.minute)
described_class.find_available('worker', 5, 4.hours).should_not include(@job)
end

it "should not find jobs locked by another worker" do
@job = create_job(:locked_by => 'other_worker', :locked_at => described_class.db_time_now - 1.minute)
described_class.find_available('worker', 5, 4.hours).should_not include(@job)
end

it "should find open jobs" do
@job = create_job
described_class.find_available('worker', 5, 4.hours).should include(@job)
end

it "should find expired jobs" do
@job = create_job(:locked_by => 'worker', :locked_at => described_class.db_time_now - 2.minutes)
described_class.find_available('worker', 5, 1.minute).should include(@job)
end

it "should find own jobs" do
@job = create_job(:locked_by => 'worker', :locked_at => (described_class.db_time_now - 1.minutes))
described_class.find_available('worker', 5, 4.hours).should include(@job)
Expand All @@ -131,7 +139,7 @@ def create_job(opts = {})
described_class.find_available('worker', 7, 4.hours).should have(7).jobs
end
end

context "when another worker is already performing an task, it" do
before :each do
@job = described_class.create :payload_object => SimpleJob.new, :locked_by => 'worker1', :locked_at => described_class.db_time_now - 5.minutes
Expand All @@ -143,8 +151,8 @@ def create_job(opts = {})

it "should allow a second worker to get exclusive access if the timeout has passed" do
@job.lock_exclusively!(1.minute, 'worker2').should == true
end
end

it "should be able to get access to the task if it was started more then max_age ago" do
@job.locked_at = described_class.db_time_now - 5.hours
@job.save
Expand All @@ -167,9 +175,9 @@ def create_job(opts = {})
@job.lock_exclusively!(5.minutes, 'worker1').should be_true
@job.lock_exclusively!(5.minutes, 'worker1').should be_true
@job.lock_exclusively!(5.minutes, 'worker1').should be_true
end
end
end

context "when another worker has worked on a task since the job was found to be available, it" do

before :each do
Expand All @@ -192,7 +200,7 @@ def create_job(opts = {})
it "should be the class name of the job that was enqueued" do
described_class.create(:payload_object => ErrorJob.new ).name.should == 'ErrorJob'
end

it "should be the method that will be called if its a performable method object" do
job = described_class.new(:payload_object => NamedJob.new)
job.name.should == 'named_job'
Expand All @@ -203,7 +211,7 @@ def create_job(opts = {})
@job.name.should == 'Story#save'
end
end

context "worker prioritization" do
before(:each) do
Delayed::Worker.max_priority = nil
Expand All @@ -214,7 +222,7 @@ def create_job(opts = {})
10.times { described_class.enqueue SimpleJob.new, rand(10) }
jobs = described_class.find_available('worker', 10)
jobs.size.should == 10
jobs.each_cons(2) do |a, b|
jobs.each_cons(2) do |a, b|
a.priority.should <= b.priority
end
end
Expand All @@ -235,23 +243,23 @@ def create_job(opts = {})
jobs.each {|job| job.priority.should <= max}
end
end

context "clear_locks!" do
before do
@job = create_job(:locked_by => 'worker', :locked_at => described_class.db_time_now)
end

it "should clear locks for the given worker" do
described_class.clear_locks!('worker')
described_class.find_available('worker2', 5, 1.minute).should include(@job)
end

it "should not clear locks for other workers" do
described_class.clear_locks!('worker1')
described_class.find_available('worker1', 5, 1.minute).should_not include(@job)
end
end

context "unlock" do
before do
@job = create_job(:locked_by => 'worker', :locked_at => described_class.db_time_now)
Expand All @@ -263,13 +271,13 @@ def create_job(opts = {})
@job.locked_at.should be_nil
end
end

context "large handler" do
before do
text = "Lorem ipsum dolor sit amet. " * 1000
@job = described_class.enqueue Delayed::PerformableMethod.new(text, :length, {})
end

it "should have an id" do
@job.id.should_not be_nil
end
Expand Down Expand Up @@ -343,23 +351,23 @@ def create_job(opts = {})
before(:each) do
@worker.name = 'worker1'
end

it "should not run jobs locked by another worker" do
create_job(:locked_by => 'other_worker', :locked_at => (Delayed::Job.db_time_now - 1.minutes))
lambda { @worker.work_off }.should_not change { SimpleJob.runs }
end

it "should run open jobs" do
create_job
lambda { @worker.work_off }.should change { SimpleJob.runs }.from(0).to(1)
end

it "should run expired jobs" do
expired_time = Delayed::Job.db_time_now - (1.minutes + Delayed::Worker.max_run_time)
create_job(:locked_by => 'other_worker', :locked_at => expired_time)
lambda { @worker.work_off }.should change { SimpleJob.runs }.from(0).to(1)
end

it "should run own jobs" do
create_job(:locked_by => @worker.name, :locked_at => (Delayed::Job.db_time_now - 1.minutes))
lambda { @worker.work_off }.should change { SimpleJob.runs }.from(0).to(1)
Expand All @@ -384,7 +392,7 @@ def create_job(opts = {})
@job.attempts.should == 1
@job.failed_at.should_not be_nil
end

it "should re-schedule jobs after failing" do
@worker.run(@job)
@job.reload
Expand All @@ -400,7 +408,7 @@ def create_job(opts = {})
before do
@job = Delayed::Job.create :payload_object => SimpleJob.new
end

share_examples_for "any failure more than Worker.max_attempts times" do
context "when the job's payload has a #failure hook" do
before do
Expand All @@ -415,18 +423,18 @@ def create_job(opts = {})
end

context "when the job's payload has no #failure hook" do
# It's a little tricky to test this in a straightforward way,
# because putting a should_not_receive expectation on
# It's a little tricky to test this in a straightforward way,
# because putting a should_not_receive expectation on
# @job.payload_object.failure makes that object
# incorrectly return true to
# incorrectly return true to
# payload_object.respond_to? :failure, which is what
# reschedule uses to decide whether to call failure.
# So instead, we just make sure that the payload_object as it
# reschedule uses to decide whether to call failure.
# So instead, we just make sure that the payload_object as it
# already stands doesn't respond_to? failure, then
# shove it through the iterated reschedule loop and make sure we
# don't get a NoMethodError (caused by calling that nonexistent
# failure method).

before do
@job.payload_object.should_not respond_to(:failure)
end
Expand All @@ -450,18 +458,18 @@ def create_job(opts = {})
@job.should_receive(:destroy)
Delayed::Worker.max_attempts.times { @worker.reschedule(@job) }
end

it "should not be destroyed if failed fewer than Worker.max_attempts times" do
@job.should_not_receive(:destroy)
(Delayed::Worker.max_attempts - 1).times { @worker.reschedule(@job) }
end
end

context "and we don't want to destroy jobs" do
before do
Delayed::Worker.destroy_failed_jobs = false
end

it_should_behave_like "any failure more than Worker.max_attempts times"

it "should be failed if it failed more than Worker.max_attempts times" do
Expand Down

0 comments on commit 5dc030e

Please sign in to comment.