delayed_job_with_named_queues 2.0.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+ require 'backend/shared_backend_spec'
3
+ require 'delayed/backend/data_mapper'
4
+
5
+ describe Delayed::Backend::DataMapper::Job do
6
+ before(:all) do
7
+ @backend = Delayed::Backend::DataMapper::Job
8
+ end
9
+
10
+ before(:each) do
11
+ # reset database before each example is run
12
+ DataMapper.auto_migrate!
13
+ end
14
+
15
+ it_should_behave_like 'a backend'
16
+ end
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+ require 'backend/shared_backend_spec'
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 "indexes" do
17
+ it "should have combo index on priority and run_at" do
18
+ @backend.collection.index_information.detect { |index| index[0] == 'priority_1_run_at_1' }.should_not be_nil
19
+ end
20
+
21
+ it "should have index on locked_by" do
22
+ @backend.collection.index_information.detect { |index| index[0] == 'locked_by_1' }.should_not be_nil
23
+ end
24
+ end
25
+
26
+ describe "delayed method" do
27
+ class MongoStoryReader
28
+ def read(story)
29
+ "Epilog: #{story.tell}"
30
+ end
31
+ end
32
+
33
+ class MongoStory
34
+ include ::MongoMapper::Document
35
+ key :text, String
36
+
37
+ def tell
38
+ text
39
+ end
40
+ end
41
+
42
+ it "should ignore not found errors because they are permanent" do
43
+ story = MongoStory.create :text => 'Once upon a time...'
44
+ job = story.delay.tell
45
+ story.destroy
46
+ lambda { job.invoke_job }.should_not raise_error
47
+ end
48
+
49
+ it "should store the object as string" do
50
+ story = MongoStory.create :text => 'Once upon a time...'
51
+ job = story.delay.tell
52
+
53
+ job.payload_object.class.should == Delayed::PerformableMethod
54
+ job.payload_object.object.should == story
55
+ job.payload_object.method.should == :tell
56
+ job.payload_object.args.should == []
57
+ job.payload_object.perform.should == 'Once upon a time...'
58
+ end
59
+
60
+ it "should store arguments as string" do
61
+ story = MongoStory.create :text => 'Once upon a time...'
62
+ job = MongoStoryReader.new.delay.read(story)
63
+ job.payload_object.class.should == Delayed::PerformableMethod
64
+ job.payload_object.method.should == :read
65
+ job.payload_object.args.should == [story]
66
+ job.payload_object.perform.should == 'Epilog: Once upon a time...'
67
+ end
68
+ end
69
+
70
+ describe "before_fork" do
71
+ after do
72
+ MongoMapper.connection.connect
73
+ end
74
+
75
+ it "should disconnect" do
76
+ lambda do
77
+ Delayed::Backend::MongoMapper::Job.before_fork
78
+ end.should change { !!MongoMapper.connection.connected? }.from(true).to(false)
79
+ end
80
+ end
81
+
82
+ describe "after_fork" do
83
+ before do
84
+ MongoMapper.connection.close
85
+ end
86
+
87
+ it "should call reconnect" do
88
+ lambda do
89
+ Delayed::Backend::MongoMapper::Job.after_fork
90
+ end.should change { !!MongoMapper.connection.connected? }.from(false).to(true)
91
+ end
92
+ end
93
+
94
+ end
@@ -0,0 +1,372 @@
1
+ shared_examples_for 'a backend' do
2
+ let(:worker) {Delayed::Worker.new }
3
+
4
+ def create_job(opts = {})
5
+ @backend.create(opts.merge(:payload_object => SimpleJob.new))
6
+ end
7
+
8
+ before do
9
+ Delayed::Worker.max_priority = nil
10
+ Delayed::Worker.min_priority = nil
11
+ Delayed::Worker.default_priority = 99
12
+ SimpleJob.runs = 0
13
+ end
14
+
15
+ it "should set run_at automatically if not set" do
16
+ @backend.create(:payload_object => ErrorJob.new ).run_at.should_not be_nil
17
+ end
18
+
19
+ it "should not set run_at automatically if already set" do
20
+ later = @backend.db_time_now + 5.minutes
21
+ @backend.create(:payload_object => ErrorJob.new, :run_at => later).run_at.should be_close(later, 1)
22
+ end
23
+
24
+ it "should raise ArgumentError when handler doesn't respond_to :perform" do
25
+ lambda { @backend.enqueue(Object.new) }.should raise_error(ArgumentError)
26
+ end
27
+
28
+ it "should increase count after enqueuing items" do
29
+ @backend.enqueue SimpleJob.new
30
+ @backend.count.should == 1
31
+ end
32
+
33
+ it "should be able to set priority when enqueuing items" do
34
+ @job = @backend.enqueue SimpleJob.new, 5
35
+ @job.priority.should == 5
36
+ end
37
+
38
+ it "should use default priority when it is not set" do
39
+ @job = @backend.enqueue SimpleJob.new
40
+ @job.priority.should == 99
41
+ end
42
+
43
+ it "should be able to set run_at when enqueuing items" do
44
+ later = @backend.db_time_now + 5.minutes
45
+ @job = @backend.enqueue SimpleJob.new, 5, later
46
+ @job.run_at.should be_close(later, 1)
47
+ end
48
+
49
+ it "should work with jobs in modules" do
50
+ M::ModuleJob.runs = 0
51
+ job = @backend.enqueue M::ModuleJob.new
52
+ lambda { job.invoke_job }.should change { M::ModuleJob.runs }.from(0).to(1)
53
+ end
54
+
55
+ it "should raise an DeserializationError when the job class is totally unknown" do
56
+ job = @backend.new :handler => "--- !ruby/object:JobThatDoesNotExist {}"
57
+ lambda { job.payload_object.perform }.should raise_error(Delayed::Backend::DeserializationError)
58
+ end
59
+
60
+ it "should try to load the class when it is unknown at the time of the deserialization" do
61
+ job = @backend.new :handler => "--- !ruby/object:JobThatDoesNotExist {}"
62
+ job.should_receive(:attempt_to_load).with('JobThatDoesNotExist').and_return(true)
63
+ lambda { job.payload_object.perform }.should raise_error(Delayed::Backend::DeserializationError)
64
+ end
65
+
66
+ it "should try include the namespace when loading unknown objects" do
67
+ job = @backend.new :handler => "--- !ruby/object:Delayed::JobThatDoesNotExist {}"
68
+ job.should_receive(:attempt_to_load).with('Delayed::JobThatDoesNotExist').and_return(true)
69
+ lambda { job.payload_object.perform }.should raise_error(Delayed::Backend::DeserializationError)
70
+ end
71
+
72
+ it "should also try to load structs when they are unknown (raises TypeError)" do
73
+ job = @backend.new :handler => "--- !ruby/struct:JobThatDoesNotExist {}"
74
+ job.should_receive(:attempt_to_load).with('JobThatDoesNotExist').and_return(true)
75
+ lambda { job.payload_object.perform }.should raise_error(Delayed::Backend::DeserializationError)
76
+ end
77
+
78
+ it "should try include the namespace when loading unknown structs" do
79
+ job = @backend.new :handler => "--- !ruby/struct:Delayed::JobThatDoesNotExist {}"
80
+ job.should_receive(:attempt_to_load).with('Delayed::JobThatDoesNotExist').and_return(true)
81
+ lambda { job.payload_object.perform }.should raise_error(Delayed::Backend::DeserializationError)
82
+ end
83
+
84
+ describe "find_available" do
85
+ it "should not find failed jobs" do
86
+ @job = create_job :attempts => 50, :failed_at => @backend.db_time_now
87
+ @backend.find_available('worker', 5, 1.second).should_not include(@job)
88
+ end
89
+
90
+ it "should not find jobs scheduled for the future" do
91
+ @job = create_job :run_at => (@backend.db_time_now + 1.minute)
92
+ @backend.find_available('worker', 5, 4.hours).should_not include(@job)
93
+ end
94
+
95
+ it "should not find jobs locked by another worker" do
96
+ @job = create_job(:locked_by => 'other_worker', :locked_at => @backend.db_time_now - 1.minute)
97
+ @backend.find_available('worker', 5, 4.hours).should_not include(@job)
98
+ end
99
+
100
+ it "should find open jobs" do
101
+ @job = create_job
102
+ @backend.find_available('worker', 5, 4.hours).should include(@job)
103
+ end
104
+
105
+ it "should find expired jobs" do
106
+ @job = create_job(:locked_by => 'worker', :locked_at => @backend.db_time_now - 2.minutes)
107
+ @backend.find_available('worker', 5, 1.minute).should include(@job)
108
+ end
109
+
110
+ it "should find own jobs" do
111
+ @job = create_job(:locked_by => 'worker', :locked_at => (@backend.db_time_now - 1.minutes))
112
+ @backend.find_available('worker', 5, 4.hours).should include(@job)
113
+ end
114
+
115
+ it "should find only the right amount of jobs" do
116
+ 10.times { create_job }
117
+ @backend.find_available('worker', 7, 4.hours).should have(7).jobs
118
+ end
119
+ end
120
+
121
+ context "when another worker is already performing an task, it" do
122
+
123
+ before :each do
124
+ @job = @backend.create :payload_object => SimpleJob.new, :locked_by => 'worker1', :locked_at => @backend.db_time_now - 5.minutes
125
+ end
126
+
127
+ it "should not allow a second worker to get exclusive access" do
128
+ @job.lock_exclusively!(4.hours, 'worker2').should == false
129
+ end
130
+
131
+ it "should allow a second worker to get exclusive access if the timeout has passed" do
132
+ @job.lock_exclusively!(1.minute, 'worker2').should == true
133
+ end
134
+
135
+ it "should be able to get access to the task if it was started more then max_age ago" do
136
+ @job.locked_at = 5.hours.ago
137
+ @job.save
138
+
139
+ @job.lock_exclusively! 4.hours, 'worker2'
140
+ @job.reload
141
+ @job.locked_by.should == 'worker2'
142
+ @job.locked_at.should > 1.minute.ago
143
+ end
144
+
145
+ it "should not be found by another worker" do
146
+ @backend.find_available('worker2', 1, 6.minutes).length.should == 0
147
+ end
148
+
149
+ it "should be found by another worker if the time has expired" do
150
+ @backend.find_available('worker2', 1, 4.minutes).length.should == 1
151
+ end
152
+
153
+ it "should be able to get exclusive access again when the worker name is the same" do
154
+ @job.lock_exclusively!(5.minutes, 'worker1').should be_true
155
+ @job.lock_exclusively!(5.minutes, 'worker1').should be_true
156
+ @job.lock_exclusively!(5.minutes, 'worker1').should be_true
157
+ end
158
+ end
159
+
160
+ context "when another worker has worked on a task since the job was found to be available, it" do
161
+
162
+ before :each do
163
+ @job = @backend.create :payload_object => SimpleJob.new
164
+ @job_copy_for_worker_2 = @backend.find(@job.id)
165
+ end
166
+
167
+ it "should not allow a second worker to get exclusive access if already successfully processed by worker1" do
168
+ @job.destroy
169
+ @job_copy_for_worker_2.lock_exclusively!(4.hours, 'worker2').should == false
170
+ end
171
+
172
+ 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
173
+ @job.update_attributes(:attempts => 1, :run_at => 1.day.from_now)
174
+ @job_copy_for_worker_2.lock_exclusively!(4.hours, 'worker2').should == false
175
+ end
176
+ end
177
+
178
+ describe "reserve" do
179
+ before do
180
+ Delayed::Worker.max_run_time = 2.minutes
181
+ @worker = Delayed::Worker.new(:quiet => true)
182
+ end
183
+
184
+ it "should not reserve failed jobs" do
185
+ create_job :attempts => 50, :failed_at => described_class.db_time_now
186
+ described_class.reserve(@worker).should be_nil
187
+ end
188
+
189
+ it "should not reserve jobs scheduled for the future" do
190
+ create_job :run_at => (described_class.db_time_now + 1.minute)
191
+ described_class.reserve(@worker).should be_nil
192
+ end
193
+
194
+ it "should lock the job so other workers can't reserve it" do
195
+ job = create_job
196
+ described_class.reserve(@worker).should == job
197
+ new_worker = Delayed::Worker.new(:quiet => true)
198
+ new_worker.name = 'worker2'
199
+ described_class.reserve(new_worker).should be_nil
200
+ end
201
+
202
+ it "should reserve open jobs" do
203
+ job = create_job
204
+ described_class.reserve(@worker).should == job
205
+ end
206
+
207
+ it "should reserve expired jobs" do
208
+ job = create_job(:locked_by => @worker.name, :locked_at => described_class.db_time_now - 3.minutes)
209
+ described_class.reserve(@worker).should == job
210
+ end
211
+
212
+ it "should reserve own jobs" do
213
+ job = create_job(:locked_by => @worker.name, :locked_at => (described_class.db_time_now - 1.minutes))
214
+ described_class.reserve(@worker).should == job
215
+ end
216
+ end
217
+
218
+ context "#name" do
219
+ it "should be the class name of the job that was enqueued" do
220
+ @backend.create(:payload_object => ErrorJob.new ).name.should == 'ErrorJob'
221
+ end
222
+
223
+ it "should be the method that will be called if its a performable method object" do
224
+ @job = Story.delay.create
225
+ @job.name.should == "Story.create"
226
+ end
227
+
228
+ it "should be the instance method that will be called if its a performable method object" do
229
+ @job = Story.create(:text => "...").delay.save
230
+ @job.name.should == 'Story#save'
231
+ end
232
+ end
233
+
234
+ context "worker prioritization" do
235
+ before(:each) do
236
+ Delayed::Worker.max_priority = nil
237
+ Delayed::Worker.min_priority = nil
238
+ end
239
+
240
+ it "should fetch jobs ordered by priority" do
241
+ 10.times { @backend.enqueue SimpleJob.new, rand(10) }
242
+ jobs = @backend.find_available('worker', 10)
243
+ jobs.size.should == 10
244
+ jobs.each_cons(2) do |a, b|
245
+ a.priority.should <= b.priority
246
+ end
247
+ end
248
+
249
+ it "should only find jobs greater than or equal to min priority" do
250
+ min = 5
251
+ Delayed::Worker.min_priority = min
252
+ 10.times {|i| @backend.enqueue SimpleJob.new, i }
253
+ jobs = @backend.find_available('worker', 10)
254
+ jobs.each {|job| job.priority.should >= min}
255
+ end
256
+
257
+ it "should only find jobs less than or equal to max priority" do
258
+ max = 5
259
+ Delayed::Worker.max_priority = max
260
+ 10.times {|i| @backend.enqueue SimpleJob.new, i }
261
+ jobs = @backend.find_available('worker', 10)
262
+ jobs.each {|job| job.priority.should <= max}
263
+ end
264
+ end
265
+
266
+ context "clear_locks!" do
267
+ before do
268
+ @job = create_job(:locked_by => 'worker', :locked_at => @backend.db_time_now)
269
+ end
270
+
271
+ it "should clear locks for the given worker" do
272
+ @backend.clear_locks!('worker')
273
+ @backend.find_available('worker2', 5, 1.minute).should include(@job)
274
+ end
275
+
276
+ it "should not clear locks for other workers" do
277
+ @backend.clear_locks!('worker1')
278
+ @backend.find_available('worker1', 5, 1.minute).should_not include(@job)
279
+ end
280
+ end
281
+
282
+ context "unlock" do
283
+ before do
284
+ @job = create_job(:locked_by => 'worker', :locked_at => @backend.db_time_now)
285
+ end
286
+
287
+ it "should clear locks" do
288
+ @job.unlock
289
+ @job.locked_by.should be_nil
290
+ @job.locked_at.should be_nil
291
+ end
292
+ end
293
+
294
+ context "large handler" do
295
+ before do
296
+ text = "Lorem ipsum dolor sit amet. " * 1000
297
+ @job = @backend.enqueue Delayed::PerformableMethod.new(text, :length, {})
298
+ end
299
+
300
+ it "should have an id" do
301
+ @job.id.should_not be_nil
302
+ end
303
+ end
304
+
305
+ context "named queues" do
306
+ context "when worker has one queue set" do
307
+ before(:each) do
308
+ worker.queues = ['large']
309
+ end
310
+
311
+ it "should only work off jobs which are from its queue" do
312
+ SimpleJob.runs.should == 0
313
+
314
+ create_job(:queue => "large")
315
+ create_job(:queue => "small")
316
+ worker.work_off
317
+
318
+ SimpleJob.runs.should == 1
319
+ end
320
+ end
321
+
322
+ context "when worker has two queue set" do
323
+ before(:each) do
324
+ worker.queues = ['large', 'small']
325
+ end
326
+
327
+ it "should only work off jobs which are from its queue" do
328
+ SimpleJob.runs.should == 0
329
+
330
+ create_job(:queue => "large")
331
+ create_job(:queue => "small")
332
+ create_job(:queue => "medium")
333
+ create_job
334
+ worker.work_off
335
+
336
+ SimpleJob.runs.should == 2
337
+ end
338
+ end
339
+
340
+ context "when worker does not have queue set" do
341
+ before(:each) do
342
+ worker.queues = []
343
+ end
344
+
345
+ it "should work off all jobs" do
346
+ SimpleJob.runs.should == 0
347
+
348
+ create_job(:queue => "one")
349
+ create_job(:queue => "two")
350
+ create_job
351
+ worker.work_off
352
+
353
+ SimpleJob.runs.should == 3
354
+ end
355
+ end
356
+ end
357
+
358
+ context "max_attempts" do
359
+ before(:each) do
360
+ @job = described_class.enqueue SimpleJob.new
361
+ end
362
+
363
+ it 'should not be defined' do
364
+ @job.max_attempts.should be_nil
365
+ end
366
+
367
+ it 'should use the max_retries value on the payload when defined' do
368
+ @job.payload_object.stub!(:max_attempts).and_return(99)
369
+ @job.max_attempts.should == 99
370
+ end
371
+ end
372
+ end