delayed_job 2.1.4 → 3.0.0.pre
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.
- data/README.textile +4 -1
- data/lib/delayed/backend/base.rb +32 -14
- data/lib/delayed/backend/shared_spec.rb +98 -17
- data/lib/delayed/command.rb +17 -27
- data/lib/delayed/lifecycle.rb +84 -0
- data/lib/delayed/performable_method.rb +3 -1
- data/lib/delayed/plugin.rb +13 -0
- data/lib/delayed/plugins/clear_locks.rb +15 -0
- data/lib/delayed/psych_ext.rb +65 -0
- data/lib/delayed/railtie.rb +0 -2
- data/lib/delayed/serialization/active_record.rb +5 -1
- data/lib/delayed/syck_ext.rb +34 -0
- data/lib/delayed/tasks.rb +1 -1
- data/lib/delayed/worker.rb +68 -27
- data/lib/delayed/yaml_ext.rb +5 -36
- data/lib/delayed_job.rb +10 -1
- data/lib/generators/delayed_job/delayed_job_generator.rb +1 -24
- data/spec/autoloaded/instance_clazz.rb +6 -0
- data/spec/autoloaded/instance_struct.rb +6 -0
- data/spec/delayed/backend/test.rb +112 -0
- data/spec/delayed/serialization/test.rb +0 -0
- data/spec/lifecycle_spec.rb +107 -0
- data/spec/message_sending_spec.rb +2 -2
- data/spec/performable_method_spec.rb +26 -1
- data/spec/sample_jobs.rb +6 -0
- data/spec/spec_helper.rb +17 -28
- data/spec/test_backend_spec.rb +13 -0
- data/spec/worker_spec.rb +2 -20
- data/spec/yaml_ext_spec.rb +19 -19
- metadata +107 -133
- data/lib/delayed/backend/active_record.rb +0 -82
- data/lib/generators/delayed_job/templates/migration.rb +0 -21
- data/spec/active_record_job_spec.rb +0 -36
- data/spec/database.yml +0 -4
data/lib/delayed/worker.rb
CHANGED
@@ -2,16 +2,22 @@ require 'timeout'
|
|
2
2
|
require 'active_support/core_ext/numeric/time'
|
3
3
|
require 'active_support/core_ext/class/attribute_accessors'
|
4
4
|
require 'active_support/core_ext/kernel'
|
5
|
+
require 'active_support/core_ext/enumerable'
|
5
6
|
require 'logger'
|
6
7
|
|
7
8
|
module Delayed
|
8
9
|
class Worker
|
9
|
-
cattr_accessor :min_priority, :max_priority, :max_attempts, :max_run_time, :default_priority, :sleep_delay, :logger, :delay_jobs
|
10
|
+
cattr_accessor :min_priority, :max_priority, :max_attempts, :max_run_time, :default_priority, :sleep_delay, :logger, :delay_jobs, :queues
|
10
11
|
self.sleep_delay = 5
|
11
12
|
self.max_attempts = 25
|
12
13
|
self.max_run_time = 4.hours
|
13
14
|
self.default_priority = 0
|
14
15
|
self.delay_jobs = true
|
16
|
+
self.queues = []
|
17
|
+
|
18
|
+
# Add or remove plugins in this list before the worker is instantiated
|
19
|
+
cattr_accessor :plugins
|
20
|
+
self.plugins = [Delayed::Plugins::ClearLocks]
|
15
21
|
|
16
22
|
# By default failed jobs are destroyed after too many attempts. If you want to keep them around
|
17
23
|
# (perhaps to inspect the reason for the failure), set this to false.
|
@@ -31,7 +37,7 @@ module Delayed
|
|
31
37
|
|
32
38
|
def self.backend=(backend)
|
33
39
|
if backend.is_a? Symbol
|
34
|
-
require "delayed/serialization/#{backend}"
|
40
|
+
require "delayed/serialization/#{backend}" if YAML.parser.class.name =~ /syck/i
|
35
41
|
require "delayed/backend/#{backend}"
|
36
42
|
backend = "Delayed::Backend::#{backend.to_s.classify}::Job".constantize
|
37
43
|
end
|
@@ -40,7 +46,35 @@ module Delayed
|
|
40
46
|
end
|
41
47
|
|
42
48
|
def self.guess_backend
|
43
|
-
|
49
|
+
warn "[DEPRECATION] guess_backend is deprecated. Please remove it from your code."
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.before_fork
|
53
|
+
unless @files_to_reopen
|
54
|
+
@files_to_reopen = []
|
55
|
+
ObjectSpace.each_object(File) do |file|
|
56
|
+
@files_to_reopen << file unless file.closed?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
backend.before_fork
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.after_fork
|
64
|
+
# Re-open file handles
|
65
|
+
@files_to_reopen.each do |file|
|
66
|
+
begin
|
67
|
+
file.reopen file.path, "a+"
|
68
|
+
file.sync = true
|
69
|
+
rescue ::Exception
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
backend.after_fork
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.lifecycle
|
77
|
+
@lifecycle ||= Delayed::Lifecycle.new
|
44
78
|
end
|
45
79
|
|
46
80
|
def initialize(options={})
|
@@ -48,6 +82,9 @@ module Delayed
|
|
48
82
|
self.class.min_priority = options[:min_priority] if options.has_key?(:min_priority)
|
49
83
|
self.class.max_priority = options[:max_priority] if options.has_key?(:max_priority)
|
50
84
|
self.class.sleep_delay = options[:sleep_delay] if options.has_key?(:sleep_delay)
|
85
|
+
self.class.queues = options[:queues] if options.has_key?(:queues)
|
86
|
+
|
87
|
+
self.plugins.each { |klass| klass.new }
|
51
88
|
end
|
52
89
|
|
53
90
|
# Every worker has a unique name which by default is the pid of the process. There are some
|
@@ -66,33 +103,38 @@ module Delayed
|
|
66
103
|
end
|
67
104
|
|
68
105
|
def start
|
106
|
+
trap('TERM') { say 'Exiting...'; stop }
|
107
|
+
trap('INT') { say 'Exiting...'; stop }
|
108
|
+
|
69
109
|
say "Starting job worker"
|
70
110
|
|
71
|
-
|
72
|
-
|
111
|
+
self.class.lifecycle.run_callbacks(:execute, self) do
|
112
|
+
loop do
|
113
|
+
self.class.lifecycle.run_callbacks(:loop, self) do
|
114
|
+
result = nil
|
73
115
|
|
74
|
-
|
75
|
-
|
116
|
+
realtime = Benchmark.realtime do
|
117
|
+
result = work_off
|
118
|
+
end
|
76
119
|
|
77
|
-
|
78
|
-
result = work_off
|
79
|
-
end
|
120
|
+
count = result.sum
|
80
121
|
|
81
|
-
|
122
|
+
break if @exit
|
82
123
|
|
83
|
-
|
124
|
+
if count.zero?
|
125
|
+
sleep(self.class.sleep_delay)
|
126
|
+
else
|
127
|
+
say "#{count} jobs processed at %.4f j/s, %d failed ..." % [count / realtime, result.last]
|
128
|
+
end
|
129
|
+
end
|
84
130
|
|
85
|
-
|
86
|
-
sleep(self.class.sleep_delay)
|
87
|
-
else
|
88
|
-
say "#{count} jobs processed at %.4f j/s, %d failed ..." % [count / realtime, result.last]
|
131
|
+
break if @exit
|
89
132
|
end
|
90
|
-
|
91
|
-
break if $exit
|
92
133
|
end
|
134
|
+
end
|
93
135
|
|
94
|
-
|
95
|
-
|
136
|
+
def stop
|
137
|
+
@exit = true
|
96
138
|
end
|
97
139
|
|
98
140
|
# Do num jobs and return stats on success/failure.
|
@@ -126,7 +168,7 @@ module Delayed
|
|
126
168
|
job.last_error = "{#{error.message}\n#{error.backtrace.join('\n')}"
|
127
169
|
failed(job)
|
128
170
|
rescue Exception => error
|
129
|
-
handle_failed_job(job, error)
|
171
|
+
self.class.lifecycle.run_callbacks(:error, self, job){ handle_failed_job(job, error) }
|
130
172
|
return false # work failed
|
131
173
|
end
|
132
174
|
|
@@ -145,11 +187,10 @@ module Delayed
|
|
145
187
|
end
|
146
188
|
|
147
189
|
def failed(job)
|
148
|
-
|
149
|
-
|
150
|
-
|
190
|
+
self.class.lifecycle.run_callbacks(:failure, self, job) do
|
191
|
+
job.hook(:failure)
|
192
|
+
self.class.destroy_failed_jobs ? job.destroy : job.fail!
|
151
193
|
end
|
152
|
-
self.class.destroy_failed_jobs ? job.destroy : job.update_attributes(:failed_at => Delayed::Job.db_time_now)
|
153
194
|
end
|
154
195
|
|
155
196
|
def say(text, level = Logger::INFO)
|
@@ -161,7 +202,7 @@ module Delayed
|
|
161
202
|
def max_attempts(job)
|
162
203
|
job.max_attempts || self.class.max_attempts
|
163
204
|
end
|
164
|
-
|
205
|
+
|
165
206
|
protected
|
166
207
|
|
167
208
|
def handle_failed_job(job, error)
|
@@ -174,7 +215,7 @@ module Delayed
|
|
174
215
|
# If no jobs are left we return nil
|
175
216
|
def reserve_and_run_one_job
|
176
217
|
job = Delayed::Job.reserve(self)
|
177
|
-
run(job) if job
|
218
|
+
self.class.lifecycle.run_callbacks(:perform, self, job){ result = run(job) } if job
|
178
219
|
end
|
179
220
|
end
|
180
221
|
|
data/lib/delayed/yaml_ext.rb
CHANGED
@@ -2,40 +2,9 @@
|
|
2
2
|
# Classes, Modules and Structs
|
3
3
|
|
4
4
|
require 'yaml'
|
5
|
-
YAML
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
def self.yaml_new(klass, tag, val)
|
11
|
-
val.constantize
|
12
|
-
end
|
13
|
-
|
14
|
-
def to_yaml( opts = {} )
|
15
|
-
YAML::quick_emit( nil, opts ) { |out|
|
16
|
-
out.scalar(taguri, self.name, :plain)
|
17
|
-
}
|
18
|
-
end
|
19
|
-
|
20
|
-
def yaml_tag_read_class(name)
|
21
|
-
# Constantize the object so that ActiveSupport can attempt
|
22
|
-
# its auto loading magic. Will raise LoadError if not successful.
|
23
|
-
name.constantize
|
24
|
-
name
|
25
|
-
end
|
26
|
-
|
27
|
-
end
|
28
|
-
|
29
|
-
class Class
|
30
|
-
yaml_as "tag:ruby.yaml.org,2002:class"
|
31
|
-
remove_method :to_yaml if respond_to?(:to_yaml) && method(:to_yaml).owner == Class # use Module's to_yaml
|
32
|
-
end
|
33
|
-
|
34
|
-
class Struct
|
35
|
-
def self.yaml_tag_read_class(name)
|
36
|
-
# Constantize the object so that ActiveSupport can attempt
|
37
|
-
# its auto loading magic. Will raise LoadError if not successful.
|
38
|
-
name.constantize
|
39
|
-
"Struct::#{ name }"
|
40
|
-
end
|
5
|
+
if YAML.parser.class.name =~ /syck/i
|
6
|
+
require File.expand_path('../syck_ext', __FILE__)
|
7
|
+
require File.expand_path('../serialization/active_record', __FILE__)
|
8
|
+
else
|
9
|
+
require File.expand_path('../psych_ext', __FILE__)
|
41
10
|
end
|
data/lib/delayed_job.rb
CHANGED
@@ -2,8 +2,17 @@ require 'active_support'
|
|
2
2
|
|
3
3
|
require File.dirname(__FILE__) + '/delayed/message_sending'
|
4
4
|
require File.dirname(__FILE__) + '/delayed/performable_method'
|
5
|
-
|
5
|
+
|
6
|
+
# PerformableMailer is compatible with ActionMailer 3 (and possibly 3.1)
|
7
|
+
if defined?(ActionMailer)
|
8
|
+
require 'action_mailer/version'
|
9
|
+
require File.dirname(__FILE__) + '/delayed/performable_mailer' if 3 == ActionMailer::VERSION::MAJOR
|
10
|
+
end
|
11
|
+
|
6
12
|
require File.dirname(__FILE__) + '/delayed/yaml_ext'
|
13
|
+
require File.dirname(__FILE__) + '/delayed/lifecycle'
|
14
|
+
require File.dirname(__FILE__) + '/delayed/plugin'
|
15
|
+
require File.dirname(__FILE__) + '/delayed/plugins/clear_locks'
|
7
16
|
require File.dirname(__FILE__) + '/delayed/backend/base'
|
8
17
|
require File.dirname(__FILE__) + '/delayed/worker'
|
9
18
|
require File.dirname(__FILE__) + '/delayed/deserialization_error'
|
@@ -1,34 +1,11 @@
|
|
1
1
|
require 'rails/generators'
|
2
|
-
require 'rails/generators/migration'
|
3
2
|
|
4
3
|
class DelayedJobGenerator < Rails::Generators::Base
|
5
|
-
|
6
|
-
include Rails::Generators::Migration
|
7
4
|
|
8
|
-
|
9
|
-
@source_root ||= File.join(File.dirname(__FILE__), 'templates')
|
10
|
-
end
|
11
|
-
|
12
|
-
# Implement the required interface for Rails::Generators::Migration.
|
13
|
-
#
|
14
|
-
def self.next_migration_number(dirname) #:nodoc:
|
15
|
-
next_migration_number = current_migration_number(dirname) + 1
|
16
|
-
if ActiveRecord::Base.timestamped_migrations
|
17
|
-
[Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
|
18
|
-
else
|
19
|
-
"%.3d" % next_migration_number
|
20
|
-
end
|
21
|
-
end
|
5
|
+
self.source_paths << File.join(File.dirname(__FILE__), 'templates')
|
22
6
|
|
23
7
|
def create_script_file
|
24
8
|
template 'script', 'script/delayed_job'
|
25
9
|
chmod 'script/delayed_job', 0755
|
26
10
|
end
|
27
|
-
|
28
|
-
def create_migration_file
|
29
|
-
if defined?(ActiveRecord)
|
30
|
-
migration_template 'migration.rb', 'db/migrate/create_delayed_jobs.rb'
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
11
|
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
# An in-memory backend suitable only for testing. Tries to behave as if it were an ORM.
|
4
|
+
module Delayed
|
5
|
+
module Backend
|
6
|
+
module Test
|
7
|
+
class Job
|
8
|
+
attr_accessor :id
|
9
|
+
attr_accessor :priority
|
10
|
+
attr_accessor :attempts
|
11
|
+
attr_accessor :handler
|
12
|
+
attr_accessor :last_error
|
13
|
+
attr_accessor :run_at
|
14
|
+
attr_accessor :locked_at
|
15
|
+
attr_accessor :locked_by
|
16
|
+
attr_accessor :failed_at
|
17
|
+
attr_accessor :queue
|
18
|
+
|
19
|
+
include Delayed::Backend::Base
|
20
|
+
|
21
|
+
cattr_accessor :id
|
22
|
+
self.id = 0
|
23
|
+
|
24
|
+
def initialize(hash = {})
|
25
|
+
self.attempts = 0
|
26
|
+
self.priority = 0
|
27
|
+
self.id = (self.class.id += 1)
|
28
|
+
hash.each{|k,v| send(:"#{k}=", v)}
|
29
|
+
end
|
30
|
+
|
31
|
+
@jobs = []
|
32
|
+
def self.all
|
33
|
+
@jobs
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.count
|
37
|
+
all.size
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.delete_all
|
41
|
+
all.clear
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.create(attrs = {})
|
45
|
+
new(attrs).tap do |o|
|
46
|
+
o.save
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.create!(*args); create(*args); end
|
51
|
+
|
52
|
+
def self.clear_locks!(worker_name)
|
53
|
+
all.select{|j| j.locked_by == worker_name}.each {|j| j.locked_by = nil; j.locked_at = nil}
|
54
|
+
end
|
55
|
+
|
56
|
+
# Find a few candidate jobs to run (in case some immediately get locked by others).
|
57
|
+
def self.find_available(worker_name, limit = 5, max_run_time = Worker.max_run_time)
|
58
|
+
jobs = all.select do |j|
|
59
|
+
j.run_at <= db_time_now &&
|
60
|
+
(j.locked_at.nil? || j.locked_at < db_time_now - max_run_time || j.locked_by == worker_name) &&
|
61
|
+
j.failed_at.nil?
|
62
|
+
end
|
63
|
+
|
64
|
+
jobs = jobs.select{|j| Worker.queues.include?(j.queue)} if Worker.queues.any?
|
65
|
+
jobs = jobs.select{|j| j.priority >= Worker.min_priority} if Worker.min_priority
|
66
|
+
jobs = jobs.select{|j| j.priority <= Worker.max_priority} if Worker.max_priority
|
67
|
+
jobs.sort_by{|j| [j.priority, j.run_at]}[0..limit-1]
|
68
|
+
end
|
69
|
+
|
70
|
+
# Lock this job for this worker.
|
71
|
+
# Returns true if we have the lock, false otherwise.
|
72
|
+
def lock_exclusively!(max_run_time, worker)
|
73
|
+
now = self.class.db_time_now
|
74
|
+
if locked_by != worker
|
75
|
+
# We don't own this job so we will update the locked_by name and the locked_at
|
76
|
+
self.locked_at = now
|
77
|
+
self.locked_by = worker
|
78
|
+
end
|
79
|
+
|
80
|
+
return true
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.db_time_now
|
84
|
+
Time.current
|
85
|
+
end
|
86
|
+
|
87
|
+
def update_attributes(attrs = {})
|
88
|
+
attrs.each{|k,v| send(:"#{k}=", v)}
|
89
|
+
save
|
90
|
+
end
|
91
|
+
|
92
|
+
def destroy
|
93
|
+
self.class.all.delete(self)
|
94
|
+
end
|
95
|
+
|
96
|
+
def save
|
97
|
+
self.run_at ||= Time.current
|
98
|
+
|
99
|
+
self.class.all << self unless self.class.all.include?(self)
|
100
|
+
true
|
101
|
+
end
|
102
|
+
|
103
|
+
def save!; save; end
|
104
|
+
|
105
|
+
def reload
|
106
|
+
reset
|
107
|
+
self
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
File without changes
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Delayed::Lifecycle do
|
4
|
+
let(:lifecycle) { Delayed::Lifecycle.new }
|
5
|
+
let(:callback) { lambda {|*args|} }
|
6
|
+
let(:arguments) { [1] }
|
7
|
+
let(:behavior) { mock(Object, :before! => nil, :after! => nil, :inside! => nil) }
|
8
|
+
let(:wrapped_block) { Proc.new { behavior.inside! } }
|
9
|
+
|
10
|
+
describe "before callbacks" do
|
11
|
+
before(:each) do
|
12
|
+
lifecycle.before(:execute, &callback)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should execute before wrapped block' do
|
16
|
+
callback.should_receive(:call).with(*arguments).ordered
|
17
|
+
behavior.should_receive(:inside!).ordered
|
18
|
+
lifecycle.run_callbacks :execute, *arguments, &wrapped_block
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "after callbacks" do
|
23
|
+
before(:each) do
|
24
|
+
lifecycle.after(:execute, &callback)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should execute after wrapped block' do
|
28
|
+
behavior.should_receive(:inside!).ordered
|
29
|
+
callback.should_receive(:call).with(*arguments).ordered
|
30
|
+
lifecycle.run_callbacks :execute, *arguments, &wrapped_block
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "around callbacks" do
|
35
|
+
before(:each) do
|
36
|
+
lifecycle.around(:execute) do |*args, &block|
|
37
|
+
behavior.before!
|
38
|
+
block.call(*args)
|
39
|
+
behavior.after!
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should before and after wrapped block' do
|
44
|
+
behavior.should_receive(:before!).ordered
|
45
|
+
behavior.should_receive(:inside!).ordered
|
46
|
+
behavior.should_receive(:after!).ordered
|
47
|
+
lifecycle.run_callbacks :execute, *arguments, &wrapped_block
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should execute multiple callbacks in order" do
|
51
|
+
behavior.should_receive(:one).ordered
|
52
|
+
behavior.should_receive(:two).ordered
|
53
|
+
behavior.should_receive(:three).ordered
|
54
|
+
|
55
|
+
lifecycle.around(:execute) { |*args, &block| behavior.one; block.call(*args) }
|
56
|
+
lifecycle.around(:execute) { |*args, &block| behavior.two; block.call(*args) }
|
57
|
+
lifecycle.around(:execute) { |*args, &block| behavior.three; block.call(*args) }
|
58
|
+
|
59
|
+
lifecycle.run_callbacks(:execute, *arguments, &wrapped_block)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should raise if callback is executed with wrong number of parameters" do
|
64
|
+
lifecycle.before(:execute, &callback)
|
65
|
+
expect { lifecycle.run_callbacks(:execute, 1,2,3) {} }.to raise_error(ArgumentError, /1 parameter/)
|
66
|
+
end
|
67
|
+
|
68
|
+
# # This is a spectacularly crappy way to test callbacks. What's a better way?
|
69
|
+
# describe 'arguments callbacks' do
|
70
|
+
# subject do
|
71
|
+
# class Testarguments < Delayed::arguments
|
72
|
+
# def before_execute; end
|
73
|
+
# def before_loop; end
|
74
|
+
# def before_perform; end
|
75
|
+
#
|
76
|
+
# set_callback :execute, :before, :before_execute
|
77
|
+
# set_callback :loop, :before, :before_loop
|
78
|
+
# set_callback :perform, :before, :before_perform
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# Testarguments.new.tap { |w| w.stop }
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# it "should trigger for execute event" do
|
85
|
+
# subject.should_receive(:before_execute).with()
|
86
|
+
# subject.start
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# it "should trigger for loop event" do
|
90
|
+
# subject.should_receive(:before_loop).with()
|
91
|
+
# subject.start
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# it "should trigger for perform event" do
|
95
|
+
# "foo".delay.length
|
96
|
+
# subject.should_receive(:before_perform).with()
|
97
|
+
# subject.start
|
98
|
+
# end
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# describe 'job callbacks' do
|
102
|
+
# it "should trigger for enqueue event" do
|
103
|
+
# pending 'figure out how to test this'
|
104
|
+
# end
|
105
|
+
# end
|
106
|
+
|
107
|
+
end
|
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Delayed::MessageSending do
|
4
4
|
describe "handle_asynchronously" do
|
5
|
-
class Story
|
5
|
+
class Story
|
6
6
|
def tell!(arg)
|
7
7
|
end
|
8
8
|
handle_asynchronously :tell!
|
@@ -14,7 +14,7 @@ describe Delayed::MessageSending do
|
|
14
14
|
end
|
15
15
|
|
16
16
|
it "should create a PerformableMethod" do
|
17
|
-
story = Story.
|
17
|
+
story = Story.new
|
18
18
|
lambda {
|
19
19
|
job = story.tell!(1)
|
20
20
|
job.payload_object.class.should == Delayed::PerformableMethod
|
@@ -47,18 +47,43 @@ describe Delayed::PerformableMethod do
|
|
47
47
|
story.delay.tell.invoke_job
|
48
48
|
end
|
49
49
|
end
|
50
|
-
|
50
|
+
|
51
|
+
%w(before after success).each do |hook|
|
52
|
+
it "should delegate #{hook} hook to object when delay_jobs = false" do
|
53
|
+
Delayed::Worker.delay_jobs = false
|
54
|
+
story = Story.new
|
55
|
+
story.should_receive(hook).with(an_instance_of(Delayed::Job))
|
56
|
+
story.delay.tell
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
51
60
|
it "should delegate error hook to object" do
|
52
61
|
story = Story.new
|
53
62
|
story.should_receive(:error).with(an_instance_of(Delayed::Job), an_instance_of(RuntimeError))
|
54
63
|
story.should_receive(:tell).and_raise(RuntimeError)
|
55
64
|
lambda { story.delay.tell.invoke_job }.should raise_error
|
56
65
|
end
|
66
|
+
|
67
|
+
it "should delegate error hook to object when delay_jobs = false" do
|
68
|
+
Delayed::Worker.delay_jobs = false
|
69
|
+
story = Story.new
|
70
|
+
story.should_receive(:error).with(an_instance_of(Delayed::Job), an_instance_of(RuntimeError))
|
71
|
+
story.should_receive(:tell).and_raise(RuntimeError)
|
72
|
+
lambda { story.delay.tell }.should raise_error
|
73
|
+
end
|
57
74
|
|
58
75
|
it "should delegate failure hook to object" do
|
59
76
|
method = Delayed::PerformableMethod.new("object", :size, [])
|
60
77
|
method.object.should_receive(:failure)
|
61
78
|
method.failure
|
62
79
|
end
|
80
|
+
|
81
|
+
it "should delegate failure hook to object when delay_jobs = false" do
|
82
|
+
Delayed::Worker.delay_jobs = false
|
83
|
+
method = Delayed::PerformableMethod.new("object", :size, [])
|
84
|
+
method.object.should_receive(:failure)
|
85
|
+
method.failure
|
86
|
+
end
|
87
|
+
|
63
88
|
end
|
64
89
|
end
|
data/spec/sample_jobs.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -6,8 +6,9 @@ require 'rspec'
|
|
6
6
|
require 'logger'
|
7
7
|
|
8
8
|
require 'rails'
|
9
|
-
require 'active_record'
|
10
9
|
require 'action_mailer'
|
10
|
+
require 'active_support/dependencies'
|
11
|
+
require 'active_record'
|
11
12
|
|
12
13
|
require 'delayed_job'
|
13
14
|
require 'delayed/backend/shared_spec'
|
@@ -15,44 +16,32 @@ require 'delayed/backend/shared_spec'
|
|
15
16
|
Delayed::Worker.logger = Logger.new('/tmp/dj.log')
|
16
17
|
ENV['RAILS_ENV'] = 'test'
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
Delayed::Worker.backend = :test
|
20
|
+
|
21
|
+
# Add this directory so the ActiveSupport autoloading works
|
22
|
+
ActiveSupport::Dependencies.autoload_paths << File.dirname(__FILE__)
|
23
|
+
|
24
|
+
# Add this to simulate Railtie initializer being executed
|
25
|
+
ActionMailer::Base.send(:extend, Delayed::DelayMail)
|
26
|
+
|
27
|
+
|
28
|
+
# Used to test interactions between DJ and an ORM
|
29
|
+
ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
|
21
30
|
ActiveRecord::Base.logger = Delayed::Worker.logger
|
22
31
|
ActiveRecord::Migration.verbose = false
|
23
32
|
|
24
33
|
ActiveRecord::Schema.define do
|
25
|
-
create_table :
|
26
|
-
table.integer :priority, :default => 0
|
27
|
-
table.integer :attempts, :default => 0
|
28
|
-
table.text :handler
|
29
|
-
table.text :last_error
|
30
|
-
table.datetime :run_at
|
31
|
-
table.datetime :locked_at
|
32
|
-
table.datetime :failed_at
|
33
|
-
table.string :locked_by
|
34
|
-
table.timestamps
|
35
|
-
end
|
36
|
-
|
37
|
-
add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority'
|
38
|
-
|
39
|
-
create_table :stories, :force => true do |table|
|
34
|
+
create_table :stories, :primary_key => :story_id, :force => true do |table|
|
40
35
|
table.string :text
|
36
|
+
table.boolean :scoped, :default => true
|
41
37
|
end
|
42
38
|
end
|
43
39
|
|
44
|
-
# Purely useful for test cases...
|
45
40
|
class Story < ActiveRecord::Base
|
41
|
+
set_primary_key :story_id
|
46
42
|
def tell; text; end
|
47
43
|
def whatever(n, _); tell*n; end
|
44
|
+
default_scope where(:scoped => true)
|
48
45
|
|
49
46
|
handle_asynchronously :whatever
|
50
47
|
end
|
51
|
-
|
52
|
-
Delayed::Worker.backend = :active_record
|
53
|
-
|
54
|
-
# Add this directory so the ActiveSupport autoloading works
|
55
|
-
ActiveSupport::Dependencies.autoload_paths << File.dirname(__FILE__)
|
56
|
-
|
57
|
-
# Add this to simulate Railtie initializer being executed
|
58
|
-
ActionMailer::Base.send(:extend, Delayed::DelayMail)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Delayed::Backend::Test::Job do
|
4
|
+
it_should_behave_like 'a delayed_job backend'
|
5
|
+
|
6
|
+
describe "#reload" do
|
7
|
+
it 'should cause the payload object to be reloaded' do
|
8
|
+
job = "foo".delay.length
|
9
|
+
o = job.payload_object
|
10
|
+
o.object_id.should_not == job.reload.payload_object.object_id
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|