delayed_job_csi 2.0.7

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