delayed_job_with_named_queues 2.0.7.1

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.
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe Object do
4
+ before { Delayed::Job.delete_all }
5
+
6
+ it "should call #delay on methods which are wrapped with handle_asynchronously" do
7
+ story = Story.create :text => 'Once upon...'
8
+
9
+ Delayed::Job.count.should == 0
10
+
11
+ story.whatever(1, 5)
12
+
13
+ Delayed::Job.count.should == 1
14
+ job = Delayed::Job.first
15
+ job.payload_object.class.should == Delayed::PerformableMethod
16
+ job.payload_object.method.should == :whatever_without_delay
17
+ job.payload_object.args.should == [1, 5]
18
+ job.payload_object.perform.should == 'Once upon...'
19
+ end
20
+
21
+ context "delay" do
22
+ it "should raise a ArgumentError if target method doesn't exist" do
23
+ lambda { Object.new.delay.method_that_does_not_exist }.should raise_error(NoMethodError)
24
+ end
25
+
26
+ it "should add a new entry to the job table when delay is called on it" do
27
+ lambda { Object.new.delay.to_s }.should change { Delayed::Job.count }.by(1)
28
+ end
29
+
30
+ it "should add a new entry to the job table when delay is called on the class" do
31
+ lambda { Object.delay.to_s }.should change { Delayed::Job.count }.by(1)
32
+ end
33
+
34
+ it "should set job options" do
35
+ run_at = 1.day.from_now
36
+ job = Object.delay(:priority => 20, :run_at => run_at).to_s
37
+ job.run_at.should == run_at
38
+ job.priority.should == 20
39
+ end
40
+
41
+ it "should save args for original method" do
42
+ job = 3.delay.+(5)
43
+ job.payload_object.args.should == [5]
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+
3
+ describe Delayed::MessageSending do
4
+ describe "handle_asynchronously" do
5
+ class Story < ActiveRecord::Base
6
+ def tell!(arg)
7
+ end
8
+ handle_asynchronously :tell!
9
+ end
10
+
11
+ it "should alias original method" do
12
+ Story.new.should respond_to(:tell_without_delay!)
13
+ Story.new.should respond_to(:tell_with_delay!)
14
+ end
15
+
16
+ it "should create a PerformableMethod" do
17
+ story = Story.create!
18
+ lambda {
19
+ job = story.tell!(1)
20
+ job.payload_object.class.should == Delayed::PerformableMethod
21
+ job.payload_object.method.should == :tell_without_delay!
22
+ job.payload_object.args.should == [1]
23
+ }.should change { Delayed::Job.count }
24
+ end
25
+
26
+ describe 'with options' do
27
+ class Fable
28
+ class << self
29
+ attr_accessor :importance
30
+ end
31
+ def tell
32
+ end
33
+ handle_asynchronously :tell, :priority => Proc.new { self.importance }
34
+ end
35
+
36
+ it 'should set the priority based on the Fable importance' do
37
+ Fable.importance = 10
38
+ job = Fable.new.tell
39
+ job.priority.should == 10
40
+
41
+ Fable.importance = 20
42
+ job = Fable.new.tell
43
+ job.priority.should == 20
44
+ end
45
+
46
+ describe 'using a proc with parament' do
47
+ class Yarn
48
+ attr_accessor :importance
49
+ def spin
50
+ end
51
+ handle_asynchronously :spin, :priority => Proc.new {|y| y.importance }
52
+ end
53
+
54
+ it 'should set the priority based on the Fable importance' do
55
+ job = Yarn.new.tap {|y| y.importance = 10 }.spin
56
+ job.priority.should == 10
57
+
58
+ job = Yarn.new.tap {|y| y.importance = 20 }.spin
59
+ job.priority.should == 20
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ context "delay" do
66
+ it "should create a new PerformableMethod job" do
67
+ lambda {
68
+ job = "hello".delay.count('l')
69
+ job.payload_object.class.should == Delayed::PerformableMethod
70
+ job.payload_object.method.should == :count
71
+ job.payload_object.args.should == ['l']
72
+ }.should change { Delayed::Job.count }.by(1)
73
+ end
74
+
75
+ it "should set default priority" do
76
+ Delayed::Worker.default_priority = 99
77
+ job = Object.delay.to_s
78
+ job.priority.should == 99
79
+ Delayed::Worker.default_priority = 0
80
+ end
81
+
82
+ it "should set job options" do
83
+ run_at = Time.parse('2010-05-03 12:55 AM')
84
+ job = Object.delay(:priority => 20, :run_at => run_at).to_s
85
+ job.run_at.should == run_at
86
+ job.priority.should == 20
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ class StoryReader
4
+ def read(story)
5
+ "Epilog: #{story.tell}"
6
+ end
7
+ end
8
+
9
+ describe Delayed::PerformableMethod do
10
+
11
+ it "should ignore ActiveRecord::RecordNotFound errors because they are permanent" do
12
+ story = Story.create :text => 'Once upon...'
13
+ p = Delayed::PerformableMethod.new(story, :tell, [])
14
+ story.destroy
15
+ lambda { p.perform }.should_not raise_error
16
+ end
17
+
18
+ it "should store the object as string if its an active record" do
19
+ story = Story.create :text => 'Once upon...'
20
+ p = Delayed::PerformableMethod.new(story, :tell, [])
21
+ p.class.should == Delayed::PerformableMethod
22
+ p.object.should == "LOAD;Story;#{story.id}"
23
+ p.method.should == :tell
24
+ p.args.should == []
25
+ p.perform.should == 'Once upon...'
26
+ end
27
+
28
+ it "should allow class methods to be called on ActiveRecord models" do
29
+ p = Delayed::PerformableMethod.new(Story, :count, [])
30
+ lambda { p.send(:load, p.object) }.should_not raise_error
31
+ end
32
+
33
+ it "should store arguments as string if they are active record objects" do
34
+ story = Story.create :text => 'Once upon...'
35
+ reader = StoryReader.new
36
+ p = Delayed::PerformableMethod.new(reader, :read, [story])
37
+ p.class.should == Delayed::PerformableMethod
38
+ p.method.should == :read
39
+ p.args.should == ["LOAD;Story;#{story.id}"]
40
+ p.perform.should == 'Epilog: Once upon...'
41
+ end
42
+
43
+ it "should not raise NoMethodError if target method is private" do
44
+ clazz = Class.new do
45
+ def private_method
46
+ end
47
+ private :private_method
48
+ end
49
+ lambda {
50
+ Delayed::PerformableMethod.new(clazz.new, :private_method, [])
51
+ }.should_not raise_error(NoMethodError)
52
+ end
53
+ end
@@ -0,0 +1,26 @@
1
+ class SimpleJob
2
+ cattr_accessor :runs; self.runs = 0
3
+ def perform; @@runs += 1; end
4
+ end
5
+
6
+ class ErrorJob
7
+ cattr_accessor :runs; self.runs = 0
8
+ def perform; raise 'did not work'; end
9
+ end
10
+
11
+ class LongRunningJob
12
+ def perform; sleep 250; end
13
+ end
14
+
15
+ class OnPermanentFailureJob < SimpleJob
16
+ def on_permanent_failure
17
+ end
18
+ def max_attempts; 1; end
19
+ end
20
+
21
+ module M
22
+ class ModuleJob
23
+ cattr_accessor :runs; self.runs = 0
24
+ def perform; @@runs += 1; end
25
+ end
26
+ end
@@ -0,0 +1,34 @@
1
+ require 'active_record'
2
+
3
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
4
+ ActiveRecord::Base.logger = Delayed::Worker.logger
5
+ ActiveRecord::Migration.verbose = false
6
+
7
+ ActiveRecord::Schema.define do
8
+ create_table :delayed_jobs, :force => true do |table|
9
+ table.integer :priority, :default => 0
10
+ table.integer :attempts, :default => 0
11
+ table.text :handler
12
+ table.text :last_error
13
+ table.datetime :run_at
14
+ table.datetime :locked_at
15
+ table.datetime :failed_at
16
+ table.string :locked_by
17
+ table.string :queue
18
+ table.timestamps
19
+ end
20
+
21
+ add_index :delayed_jobs, [:priority, :run_at, :queue], :name => 'delayed_jobs_priority'
22
+
23
+ create_table :stories, :force => true do |table|
24
+ table.string :text
25
+ end
26
+ end
27
+
28
+ # Purely useful for test cases...
29
+ class Story < ActiveRecord::Base
30
+ def tell; text; end
31
+ def whatever(n, _); tell*n; end
32
+
33
+ handle_asynchronously :whatever
34
+ end
@@ -0,0 +1,8 @@
1
+ require 'dm-core'
2
+ require 'dm-validations'
3
+
4
+ require 'delayed/backend/data_mapper'
5
+
6
+ DataMapper.logger = Delayed::Worker.logger
7
+ DataMapper.setup(:default, "sqlite3::memory:")
8
+ DataMapper.auto_migrate!
@@ -0,0 +1,17 @@
1
+ require 'mongo_mapper'
2
+
3
+ MongoMapper.config = {
4
+ RAILS_ENV => {'database' => 'delayed_job'}
5
+ }
6
+ MongoMapper.connect RAILS_ENV
7
+
8
+ unless defined?(Story)
9
+ class Story
10
+ include ::MongoMapper::Document
11
+ def tell; text; end
12
+ def whatever(n, _); tell*n; end
13
+ def self.count; end
14
+
15
+ handle_asynchronously :whatever
16
+ end
17
+ end
@@ -0,0 +1,28 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
2
+
3
+ require 'rubygems'
4
+ require 'spec'
5
+ require 'logger'
6
+
7
+ gem 'rails', '~>2.3.5'
8
+
9
+ require 'delayed_job'
10
+ require 'sample_jobs'
11
+
12
+ Delayed::Worker.logger = Logger.new('/tmp/dj.log')
13
+ RAILS_ENV = 'test'
14
+
15
+ # determine the available backends
16
+ BACKENDS = []
17
+ Dir.glob("#{File.dirname(__FILE__)}/setup/*.rb") do |backend|
18
+ begin
19
+ backend = File.basename(backend, '.rb')
20
+ require "setup/#{backend}"
21
+ require "backend/#{backend}_job_spec"
22
+ BACKENDS << backend.to_sym
23
+ rescue LoadError, Exception
24
+ puts "Unable to load #{backend} backend! #{$!}"
25
+ end
26
+ end
27
+
28
+ Delayed::Worker.backend = BACKENDS.first
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe "A story" do
4
+
5
+ before(:all) do
6
+ @story = Story.create :text => "Once upon a time..."
7
+ end
8
+
9
+ it "should be shared" do
10
+ @story.tell.should == 'Once upon a time...'
11
+ end
12
+
13
+ it "should not return its result if it storytelling is delayed" do
14
+ @story.delay.tell.should_not == 'Once upon a time...'
15
+ end
16
+
17
+ end
@@ -0,0 +1,237 @@
1
+ require 'spec_helper'
2
+
3
+ describe Delayed::Worker do
4
+ def job_create(opts = {})
5
+ Delayed::Job.create(opts.merge(:payload_object => SimpleJob.new))
6
+ end
7
+
8
+ describe "backend=" do
9
+ it "should set the Delayed::Job constant to the backend" do
10
+ @clazz = Class.new
11
+ Delayed::Worker.backend = @clazz
12
+ Delayed::Job.should == @clazz
13
+ end
14
+
15
+ it "should set backend with a symbol" do
16
+ Delayed::Worker.backend = Class.new
17
+ Delayed::Worker.backend = :active_record
18
+ Delayed::Worker.backend.should == Delayed::Backend::ActiveRecord::Job
19
+ end
20
+ end
21
+
22
+ BACKENDS.each do |backend|
23
+ describe "with the #{backend} backend" do
24
+ before do
25
+ Delayed::Worker.backend = backend
26
+ Delayed::Job.delete_all
27
+
28
+ @worker = Delayed::Worker.new(:max_priority => nil, :min_priority => nil, :quiet => true)
29
+
30
+ SimpleJob.runs = 0
31
+ end
32
+
33
+ describe "running a job" do
34
+ it "should fail after Worker.max_run_time" do
35
+ begin
36
+ old_max_run_time = Delayed::Worker.max_run_time
37
+ Delayed::Worker.max_run_time = 1.second
38
+ @job = Delayed::Job.create :payload_object => LongRunningJob.new
39
+ @worker.run(@job)
40
+ @job.reload.last_error.should =~ /expired/
41
+ @job.attempts.should == 1
42
+ ensure
43
+ Delayed::Worker.max_run_time = old_max_run_time
44
+ end
45
+ end
46
+ end
47
+
48
+ context "worker prioritization" do
49
+ before(:each) do
50
+ @worker = Delayed::Worker.new(:max_priority => 5, :min_priority => -5, :quiet => true)
51
+ end
52
+
53
+ it "should only work_off jobs that are >= min_priority" do
54
+ SimpleJob.runs.should == 0
55
+
56
+ job_create(:priority => -10)
57
+ job_create(:priority => 0)
58
+ @worker.work_off
59
+
60
+ SimpleJob.runs.should == 1
61
+ end
62
+
63
+ it "should only work_off jobs that are <= max_priority" do
64
+ SimpleJob.runs.should == 0
65
+
66
+ job_create(:priority => 10)
67
+ job_create(:priority => 0)
68
+
69
+ @worker.work_off
70
+
71
+ SimpleJob.runs.should == 1
72
+ end
73
+ end
74
+
75
+ context "while running with locked and expired jobs" do
76
+ before(:each) do
77
+ @worker.name = 'worker1'
78
+ end
79
+
80
+ it "should not run jobs locked by another worker" do
81
+ job_create(:locked_by => 'other_worker', :locked_at => (Delayed::Job.db_time_now - 1.minutes))
82
+ lambda { @worker.work_off }.should_not change { SimpleJob.runs }
83
+ end
84
+
85
+ it "should run open jobs" do
86
+ job_create
87
+ lambda { @worker.work_off }.should change { SimpleJob.runs }.from(0).to(1)
88
+ end
89
+
90
+ it "should run expired jobs" do
91
+ expired_time = Delayed::Job.db_time_now - (1.minutes + Delayed::Worker.max_run_time)
92
+ job_create(:locked_by => 'other_worker', :locked_at => expired_time)
93
+ lambda { @worker.work_off }.should change { SimpleJob.runs }.from(0).to(1)
94
+ end
95
+
96
+ it "should run own jobs" do
97
+ job_create(:locked_by => @worker.name, :locked_at => (Delayed::Job.db_time_now - 1.minutes))
98
+ lambda { @worker.work_off }.should change { SimpleJob.runs }.from(0).to(1)
99
+ end
100
+ end
101
+
102
+ describe "failed jobs" do
103
+ before do
104
+ # reset defaults
105
+ Delayed::Worker.destroy_failed_jobs = true
106
+ Delayed::Worker.max_attempts = 25
107
+ Delayed::Job.delete_all
108
+
109
+ @job = Delayed::Job.enqueue ErrorJob.new
110
+ end
111
+
112
+ it "should record last_error when destroy_failed_jobs = false, max_attempts = 1" do
113
+ Delayed::Worker.destroy_failed_jobs = false
114
+ Delayed::Worker.max_attempts = 1
115
+ @worker.run(@job)
116
+ @job.reload
117
+ @job.last_error.should =~ /did not work/
118
+ @job.last_error.should =~ /worker_spec.rb/
119
+ @job.attempts.should == 1
120
+ @job.failed_at.should_not be_nil
121
+ end
122
+
123
+ it "should re-schedule jobs after failing" do
124
+ @worker.work_off
125
+ @job.reload
126
+ @job.last_error.should =~ /did not work/
127
+ @job.last_error.should =~ /sample_jobs.rb:8:in `perform'/
128
+ @job.attempts.should == 1
129
+ @job.run_at.should > Delayed::Job.db_time_now - 10.minutes
130
+ @job.run_at.should < Delayed::Job.db_time_now + 10.minutes
131
+ @job.locked_at.should be_nil
132
+ @job.locked_by.should be_nil
133
+ end
134
+
135
+ context "when the job's payload implements #reschedule_at" do
136
+ before(:each) do
137
+ @reschedule_at = Time.current + 7.hours
138
+ @job.payload_object.stub!(:reschedule_at).and_return(@reschedule_at)
139
+ end
140
+
141
+ it 'should invoke the strategy to re-schedule' do
142
+ @job.payload_object.should_receive(:reschedule_at) do |time, attempts|
143
+ (Delayed::Job.db_time_now - time).should < 2
144
+ attempts.should == 1
145
+
146
+ Delayed::Job.db_time_now + 5
147
+ end
148
+
149
+ @worker.run(@job)
150
+ end
151
+ end
152
+ end
153
+
154
+ context "reschedule" do
155
+ before do
156
+ @job = Delayed::Job.create :payload_object => SimpleJob.new
157
+ end
158
+
159
+ share_examples_for "any failure more than Worker.max_attempts times" do
160
+ context "when the job's payload has an #on_permanent_failure hook" do
161
+ before do
162
+ @job = Delayed::Job.create :payload_object => OnPermanentFailureJob.new
163
+ @job.payload_object.should respond_to :on_permanent_failure
164
+ end
165
+
166
+ it "should run that hook" do
167
+ @job.payload_object.should_receive :on_permanent_failure
168
+ @worker.reschedule(@job)
169
+ end
170
+ end
171
+
172
+ context "when the job's payload has no #on_permanent_failure hook" do
173
+ # It's a little tricky to test this in a straightforward way,
174
+ # because putting a should_not_receive expectation on
175
+ # @job.payload_object.on_permanent_failure makes that object
176
+ # incorrectly return true to
177
+ # payload_object.respond_to? :on_permanent_failure, which is what
178
+ # reschedule uses to decide whether to call on_permanent_failure.
179
+ # So instead, we just make sure that the payload_object as it
180
+ # already stands doesn't respond_to? on_permanent_failure, then
181
+ # shove it through the iterated reschedule loop and make sure we
182
+ # don't get a NoMethodError (caused by calling that nonexistent
183
+ # on_permanent_failure method).
184
+
185
+ before do
186
+ @job.payload_object.should_not respond_to(:on_permanent_failure)
187
+ end
188
+
189
+ it "should not try to run that hook" do
190
+ lambda do
191
+ Delayed::Worker.max_attempts.times { @worker.reschedule(@job) }
192
+ end.should_not raise_exception(NoMethodError)
193
+ end
194
+ end
195
+ end
196
+
197
+ context "and we want to destroy jobs" do
198
+ before do
199
+ Delayed::Worker.destroy_failed_jobs = true
200
+ end
201
+
202
+ it_should_behave_like "any failure more than Worker.max_attempts times"
203
+
204
+ it "should be destroyed if it failed more than Worker.max_attempts times" do
205
+ @job.should_receive(:destroy)
206
+ Delayed::Worker.max_attempts.times { @worker.reschedule(@job) }
207
+ end
208
+
209
+ it "should not be destroyed if failed fewer than Worker.max_attempts times" do
210
+ @job.should_not_receive(:destroy)
211
+ (Delayed::Worker.max_attempts - 1).times { @worker.reschedule(@job) }
212
+ end
213
+ end
214
+
215
+ context "and we don't want to destroy jobs" do
216
+ before do
217
+ Delayed::Worker.destroy_failed_jobs = false
218
+ end
219
+
220
+ it_should_behave_like "any failure more than Worker.max_attempts times"
221
+
222
+ it "should be failed if it failed more than Worker.max_attempts times" do
223
+ @job.reload.failed_at.should == nil
224
+ Delayed::Worker.max_attempts.times { @worker.reschedule(@job) }
225
+ @job.reload.failed_at.should_not == nil
226
+ end
227
+
228
+ it "should not be failed if it failed fewer than Worker.max_attempts times" do
229
+ (Delayed::Worker.max_attempts - 1).times { @worker.reschedule(@job) }
230
+ @job.reload.failed_at.should == nil
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end
236
+
237
+ end