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,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