moneypools-delayed_job 1.8.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,22 @@
1
+ require 'active_support'
2
+
3
+ require File.dirname(__FILE__) + '/delayed/message_sending'
4
+ require File.dirname(__FILE__) + '/delayed/performable_method'
5
+ require File.dirname(__FILE__) + '/delayed/backend/base'
6
+ require File.dirname(__FILE__) + '/delayed/worker'
7
+
8
+ Object.send(:include, Delayed::MessageSending)
9
+ Module.send(:include, Delayed::MessageSending::ClassMethods)
10
+
11
+ if defined?(ActiveRecord)
12
+ Delayed::Worker.backend = :active_record
13
+ elsif defined?(MongoMapper)
14
+ Delayed::Worker.backend = :mongo_mapper
15
+ else
16
+ $stderr.puts "Could not decide on a backend, defaulting to active_record"
17
+ Delayed::Worker.backend = :active_record
18
+ end
19
+
20
+ if defined?(Merb::Plugins)
21
+ Merb::Plugins.add_rakefiles File.dirname(__FILE__) / 'delayed' / 'tasks'
22
+ end
@@ -0,0 +1 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'delayed', 'recipes'))
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+ require 'delayed/backend/active_record'
3
+
4
+ describe Delayed::Job do
5
+ before(:all) do
6
+ @backend = Delayed::Job
7
+ end
8
+
9
+ before(:each) do
10
+ Delayed::Worker.max_priority = nil
11
+ Delayed::Worker.min_priority = nil
12
+ Delayed::Job.delete_all
13
+ SimpleJob.runs = 0
14
+ end
15
+
16
+ after do
17
+ Time.zone = nil
18
+ end
19
+
20
+ it_should_behave_like 'a backend'
21
+
22
+ context "db_time_now" do
23
+ it "should return time in current time zone if set" do
24
+ Time.zone = 'Eastern Time (US & Canada)'
25
+ Delayed::Job.db_time_now.zone.should == 'EST'
26
+ end
27
+
28
+ it "should return UTC time if that is the AR default" do
29
+ Time.zone = nil
30
+ ActiveRecord::Base.default_timezone = :utc
31
+ Delayed::Job.db_time_now.zone.should == 'UTC'
32
+ end
33
+
34
+ it "should return local time if that is the AR default" do
35
+ Time.zone = 'Central Time (US & Canada)'
36
+ ActiveRecord::Base.default_timezone = :local
37
+ Delayed::Job.db_time_now.zone.should == 'CST'
38
+ end
39
+ end
40
+
41
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ require 'delayed/backend/mongo_mapper'
4
+
5
+ describe Delayed::Backend::MongoMapper::Job do
6
+ before(:all) do
7
+ @backend = Delayed::Backend::MongoMapper::Job
8
+ end
9
+
10
+ before(:each) do
11
+ MongoMapper.database.collections.each(&:remove)
12
+ end
13
+
14
+ it_should_behave_like 'a backend'
15
+
16
+ describe "delayed method" do
17
+ class MongoStoryReader
18
+ def read(story)
19
+ "Epilog: #{story.tell}"
20
+ end
21
+ end
22
+
23
+ class MongoStory
24
+ include ::MongoMapper::Document
25
+ key :text, String
26
+
27
+ def tell
28
+ text
29
+ end
30
+ end
31
+
32
+ it "should ignore not found errors because they are permanent" do
33
+ story = MongoStory.create :text => 'Once upon a time…'
34
+ job = story.send_later(:tell)
35
+ story.destroy
36
+ lambda { job.invoke_job }.should_not raise_error
37
+ end
38
+
39
+ it "should store the object as string" do
40
+ story = MongoStory.create :text => 'Once upon a time…'
41
+ job = story.send_later(:tell)
42
+
43
+ job.payload_object.class.should == Delayed::PerformableMethod
44
+ job.payload_object.object.should == "LOAD;MongoStory;#{story.id}"
45
+ job.payload_object.method.should == :tell
46
+ job.payload_object.args.should == []
47
+ job.payload_object.perform.should == 'Once upon a time…'
48
+ end
49
+
50
+ it "should store arguments as string" do
51
+ story = MongoStory.create :text => 'Once upon a time…'
52
+ job = MongoStoryReader.new.send_later(:read, story)
53
+ job.payload_object.class.should == Delayed::PerformableMethod
54
+ job.payload_object.method.should == :read
55
+ job.payload_object.args.should == ["LOAD;MongoStory;#{story.id}"]
56
+ job.payload_object.perform.should == 'Epilog: Once upon a time…'
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,244 @@
1
+ shared_examples_for 'a backend' do
2
+ def create_job(opts = {})
3
+ @backend.create(opts.merge(:payload_object => SimpleJob.new))
4
+ end
5
+
6
+ before do
7
+ SimpleJob.runs = 0
8
+ end
9
+
10
+ it "should set run_at automatically if not set" do
11
+ @backend.create(:payload_object => ErrorJob.new ).run_at.should_not be_nil
12
+ end
13
+
14
+ it "should not set run_at automatically if already set" do
15
+ later = @backend.db_time_now + 5.minutes
16
+ @backend.create(:payload_object => ErrorJob.new, :run_at => later).run_at.should be_close(later, 1)
17
+ end
18
+
19
+ it "should raise ArgumentError when handler doesn't respond_to :perform" do
20
+ lambda { @backend.enqueue(Object.new) }.should raise_error(ArgumentError)
21
+ end
22
+
23
+ it "should increase count after enqueuing items" do
24
+ @backend.enqueue SimpleJob.new
25
+ @backend.count.should == 1
26
+ end
27
+
28
+ it "should be able to set priority when enqueuing items" do
29
+ @job = @backend.enqueue SimpleJob.new, 5
30
+ @job.priority.should == 5
31
+ end
32
+
33
+ it "should be able to set run_at when enqueuing items" do
34
+ later = @backend.db_time_now + 5.minutes
35
+ @job = @backend.enqueue SimpleJob.new, 5, later
36
+ @job.run_at.should be_close(later, 1)
37
+ end
38
+
39
+ it "should work with jobs in modules" do
40
+ M::ModuleJob.runs = 0
41
+ job = @backend.enqueue M::ModuleJob.new
42
+ lambda { job.invoke_job }.should change { M::ModuleJob.runs }.from(0).to(1)
43
+ end
44
+
45
+ it "should raise an DeserializationError when the job class is totally unknown" do
46
+ job = @backend.new :handler => "--- !ruby/object:JobThatDoesNotExist {}"
47
+ lambda { job.payload_object.perform }.should raise_error(Delayed::Backend::DeserializationError)
48
+ end
49
+
50
+ it "should try to load the class when it is unknown at the time of the deserialization" do
51
+ job = @backend.new :handler => "--- !ruby/object:JobThatDoesNotExist {}"
52
+ job.should_receive(:attempt_to_load).with('JobThatDoesNotExist').and_return(true)
53
+ lambda { job.payload_object.perform }.should raise_error(Delayed::Backend::DeserializationError)
54
+ end
55
+
56
+ it "should try include the namespace when loading unknown objects" do
57
+ job = @backend.new :handler => "--- !ruby/object:Delayed::JobThatDoesNotExist {}"
58
+ job.should_receive(:attempt_to_load).with('Delayed::JobThatDoesNotExist').and_return(true)
59
+ lambda { job.payload_object.perform }.should raise_error(Delayed::Backend::DeserializationError)
60
+ end
61
+
62
+ it "should also try to load structs when they are unknown (raises TypeError)" do
63
+ job = @backend.new :handler => "--- !ruby/struct:JobThatDoesNotExist {}"
64
+ job.should_receive(:attempt_to_load).with('JobThatDoesNotExist').and_return(true)
65
+ lambda { job.payload_object.perform }.should raise_error(Delayed::Backend::DeserializationError)
66
+ end
67
+
68
+ it "should try include the namespace when loading unknown structs" do
69
+ job = @backend.new :handler => "--- !ruby/struct:Delayed::JobThatDoesNotExist {}"
70
+ job.should_receive(:attempt_to_load).with('Delayed::JobThatDoesNotExist').and_return(true)
71
+ lambda { job.payload_object.perform }.should raise_error(Delayed::Backend::DeserializationError)
72
+ end
73
+
74
+ describe "find_available" do
75
+ it "should not find failed jobs" do
76
+ @job = create_job :attempts => 50, :failed_at => @backend.db_time_now
77
+ @backend.find_available('worker', 5, 1.second).should_not include(@job)
78
+ end
79
+
80
+ it "should not find jobs scheduled for the future" do
81
+ @job = create_job :run_at => (@backend.db_time_now + 1.minute)
82
+ @backend.find_available('worker', 5, 4.hours).should_not include(@job)
83
+ end
84
+
85
+ it "should not find jobs locked by another worker" do
86
+ @job = create_job(:locked_by => 'other_worker', :locked_at => @backend.db_time_now - 1.minute)
87
+ @backend.find_available('worker', 5, 4.hours).should_not include(@job)
88
+ end
89
+
90
+ it "should find open jobs" do
91
+ @job = create_job
92
+ @backend.find_available('worker', 5, 4.hours).should include(@job)
93
+ end
94
+
95
+ it "should find expired jobs" do
96
+ @job = create_job(:locked_by => 'worker', :locked_at => @backend.db_time_now - 2.minutes)
97
+ @backend.find_available('worker', 5, 1.minute).should include(@job)
98
+ end
99
+
100
+ it "should find own jobs" do
101
+ @job = create_job(:locked_by => 'worker', :locked_at => (@backend.db_time_now - 1.minutes))
102
+ @backend.find_available('worker', 5, 4.hours).should include(@job)
103
+ end
104
+
105
+ it "should find only the right amount of jobs" do
106
+ 10.times { create_job }
107
+ @backend.find_available('worker', 7, 4.hours).should have(7).jobs
108
+ end
109
+ end
110
+
111
+ context "when another worker is already performing an task, it" do
112
+
113
+ before :each do
114
+ @job = @backend.create :payload_object => SimpleJob.new, :locked_by => 'worker1', :locked_at => @backend.db_time_now - 5.minutes
115
+ end
116
+
117
+ it "should not allow a second worker to get exclusive access" do
118
+ @job.lock_exclusively!(4.hours, 'worker2').should == false
119
+ end
120
+
121
+ it "should allow a second worker to get exclusive access if the timeout has passed" do
122
+ @job.lock_exclusively!(1.minute, 'worker2').should == true
123
+ end
124
+
125
+ it "should be able to get access to the task if it was started more then max_age ago" do
126
+ @job.locked_at = 5.hours.ago
127
+ @job.save
128
+
129
+ @job.lock_exclusively! 4.hours, 'worker2'
130
+ @job.reload
131
+ @job.locked_by.should == 'worker2'
132
+ @job.locked_at.should > 1.minute.ago
133
+ end
134
+
135
+ it "should not be found by another worker" do
136
+ @backend.find_available('worker2', 1, 6.minutes).length.should == 0
137
+ end
138
+
139
+ it "should be found by another worker if the time has expired" do
140
+ @backend.find_available('worker2', 1, 4.minutes).length.should == 1
141
+ end
142
+
143
+ it "should be able to get exclusive access again when the worker name is the same" do
144
+ @job.lock_exclusively!(5.minutes, 'worker1').should be_true
145
+ @job.lock_exclusively!(5.minutes, 'worker1').should be_true
146
+ @job.lock_exclusively!(5.minutes, 'worker1').should be_true
147
+ end
148
+ end
149
+
150
+ context "when another worker has worked on a task since the job was found to be available, it" do
151
+
152
+ before :each do
153
+ @job = @backend.create :payload_object => SimpleJob.new
154
+ @job_copy_for_worker_2 = @backend.find(@job.id)
155
+ end
156
+
157
+ it "should not allow a second worker to get exclusive access if already successfully processed by worker1" do
158
+ @job.destroy
159
+ @job_copy_for_worker_2.lock_exclusively!(4.hours, 'worker2').should == false
160
+ end
161
+
162
+ it "should not allow a second worker to get exclusive access if failed to be processed by worker1 and run_at time is now in future (due to backing off behaviour)" do
163
+ @job.update_attributes(:attempts => 1, :run_at => 1.day.from_now)
164
+ @job_copy_for_worker_2.lock_exclusively!(4.hours, 'worker2').should == false
165
+ end
166
+ end
167
+
168
+ context "#name" do
169
+ it "should be the class name of the job that was enqueued" do
170
+ @backend.create(:payload_object => ErrorJob.new ).name.should == 'ErrorJob'
171
+ end
172
+
173
+ it "should be the method that will be called if its a performable method object" do
174
+ @job = Story.send_later(:create)
175
+ @job.name.should == "Story.create"
176
+ end
177
+
178
+ it "should be the instance method that will be called if its a performable method object" do
179
+ @job = Story.create(:text => "...").send_later(:save)
180
+ @job.name.should == 'Story#save'
181
+ end
182
+ end
183
+
184
+ context "worker prioritization" do
185
+ before(:each) do
186
+ Delayed::Worker.max_priority = nil
187
+ Delayed::Worker.min_priority = nil
188
+ end
189
+
190
+ it "should fetch jobs ordered by priority" do
191
+ 10.times { @backend.enqueue SimpleJob.new, rand(10) }
192
+ jobs = @backend.find_available('worker', 10)
193
+ jobs.size.should == 10
194
+ jobs.each_cons(2) do |a, b|
195
+ a.priority.should <= b.priority
196
+ end
197
+ end
198
+
199
+ it "should only find jobs greater than or equal to min priority" do
200
+ min = 5
201
+ Delayed::Worker.min_priority = min
202
+ 10.times {|i| @backend.enqueue SimpleJob.new, i }
203
+ jobs = @backend.find_available('worker', 10)
204
+ jobs.each {|job| job.priority.should >= min}
205
+ end
206
+
207
+ it "should only find jobs less than or equal to max priority" do
208
+ max = 5
209
+ Delayed::Worker.max_priority = max
210
+ 10.times {|i| @backend.enqueue SimpleJob.new, i }
211
+ jobs = @backend.find_available('worker', 10)
212
+ jobs.each {|job| job.priority.should <= max}
213
+ end
214
+ end
215
+
216
+ context "clear_locks!" do
217
+ before do
218
+ @job = create_job(:locked_by => 'worker', :locked_at => @backend.db_time_now)
219
+ end
220
+
221
+ it "should clear locks for the given worker" do
222
+ @backend.clear_locks!('worker')
223
+ @backend.find_available('worker2', 5, 1.minute).should include(@job)
224
+ end
225
+
226
+ it "should not clear locks for other workers" do
227
+ @backend.clear_locks!('worker1')
228
+ @backend.find_available('worker1', 5, 1.minute).should_not include(@job)
229
+ end
230
+ end
231
+
232
+ context "unlock" do
233
+ before do
234
+ @job = create_job(:locked_by => 'worker', :locked_at => @backend.db_time_now)
235
+ end
236
+
237
+ it "should clear locks" do
238
+ @job.unlock
239
+ @job.locked_by.should be_nil
240
+ @job.locked_at.should be_nil
241
+ end
242
+ end
243
+
244
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'random ruby objects' do
4
+ before { Delayed::Job.delete_all }
5
+
6
+ it "should respond_to :send_later method" do
7
+ Object.new.respond_to?(:send_later)
8
+ end
9
+
10
+ it "should raise a ArgumentError if send_later is called but the target method doesn't exist" do
11
+ lambda { Object.new.send_later(:method_that_deos_not_exist) }.should raise_error(NoMethodError)
12
+ end
13
+
14
+ it "should add a new entry to the job table when send_later is called on it" do
15
+ lambda { Object.new.send_later(:to_s) }.should change { Delayed::Job.count }.by(1)
16
+ end
17
+
18
+ it "should add a new entry to the job table when send_later is called on the class" do
19
+ lambda { Object.send_later(:to_s) }.should change { Delayed::Job.count }.by(1)
20
+ end
21
+
22
+ it "should call send later on methods which are wrapped with handle_asynchronously" do
23
+ story = Story.create :text => 'Once upon...'
24
+
25
+ Delayed::Job.count.should == 0
26
+
27
+ story.whatever(1, 5)
28
+
29
+ Delayed::Job.count.should == 1
30
+ job = Delayed::Job.first
31
+ job.payload_object.class.should == Delayed::PerformableMethod
32
+ job.payload_object.method.should == :whatever_without_send_later
33
+ job.payload_object.args.should == [1, 5]
34
+ job.payload_object.perform.should == 'Once upon...'
35
+ end
36
+
37
+ context "send_at" do
38
+ it "should queue a new job" do
39
+ lambda do
40
+ "string".send_at(1.hour.from_now, :length)
41
+ end.should change { Delayed::Job.count }.by(1)
42
+ end
43
+
44
+ it "should schedule the job in the future" do
45
+ time = 1.hour.from_now.utc.to_time
46
+ job = "string".send_at(time, :length)
47
+ job.run_at.to_i.should == time.to_i
48
+ end
49
+
50
+ it "should store payload as PerformableMethod" do
51
+ job = "string".send_at(1.hour.from_now, :count, 'r')
52
+ job.payload_object.class.should == Delayed::PerformableMethod
53
+ job.payload_object.method.should == :count
54
+ job.payload_object.args.should == ['r']
55
+ job.payload_object.perform.should == 1
56
+ end
57
+ end
58
+
59
+ end
@@ -0,0 +1,42 @@
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
+ end