delayed_job 4.0.5 → 4.0.6

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7aeb53385b816641ccbee1ffeb9571f8a9fc277f
4
- data.tar.gz: 54d26795c4f7f72f5c70e63e8ec0eca4c937def8
3
+ metadata.gz: 437dd17835ae0e764f1fbd9ce13c55be2b7ddead
4
+ data.tar.gz: abb136834dd2e39edb5a6cf5c846613734c3b518
5
5
  SHA512:
6
- metadata.gz: 5bacca5b8cdb6ebd37341a7f574f00da50b1d201b6f453840b2123a84bd19c865cba84b090d9f2e85d11f75b233f3e0d4ce484cc674627a9146a1f551f89b933
7
- data.tar.gz: 85db710c5c82ed12d7d230fbee0be2b3921275c4c174b921620d91491be6d320a7fda7b1335fb8bcfe6a5c878552425d00acfb48d0a1298d62dd7d30f53424f3
6
+ metadata.gz: b89e00ad9d939277b9f2da9e94a1b45310c9b7ea3b7e3f7535f434eb8b9ad46a6589ef541e76501e5c2da07cb982fb337453d6c5b642b2e20274daa1c0321452
7
+ data.tar.gz: 3cb7e586ac2a1a7ea5967d5e35bf3cd325943bf37e28e913e77a787363c0efa46ab891c07c5f157b88a19f8bfda5444839b9b8071e9bc1feb8bcb715773089f9
@@ -1,3 +1,7 @@
1
+ 4.0.6 - 2014-12-22
2
+ ==================
3
+ * Revert removing test files from the gem
4
+
1
5
  4.0.5 - 2014-12-22
2
6
  ==================
3
7
  * Support for Rails 4.2
@@ -0,0 +1,15 @@
1
+ require 'bundler/setup'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ desc 'Run the specs'
6
+ RSpec::Core::RakeTask.new do |r|
7
+ r.verbose = false
8
+ end
9
+
10
+ task :test => :spec
11
+
12
+ require 'rubocop/rake_task'
13
+ RuboCop::RakeTask.new
14
+
15
+ task :default => [:spec, :rubocop]
@@ -3,12 +3,13 @@ Gem::Specification.new do |spec|
3
3
  spec.authors = ['Brandon Keepers', 'Brian Ryckbost', 'Chris Gaffney', 'David Genord II', 'Erik Michaels-Ober', 'Matt Griffin', 'Steve Richert', 'Tobias Lütke']
4
4
  spec.description = 'Delayed_job (or DJ) encapsulates the common pattern of asynchronously executing longer tasks in the background. It is a direct extraction from Shopify where the job table is responsible for a multitude of core tasks.'
5
5
  spec.email = ['brian@collectiveidea.com']
6
- spec.files = %w[CHANGELOG.md CONTRIBUTING.md LICENSE.md README.md delayed_job.gemspec]
7
- spec.files += Dir['{contrib,lib,recipes}/**/*']
6
+ spec.files = %w[CHANGELOG.md CONTRIBUTING.md LICENSE.md README.md Rakefile delayed_job.gemspec]
7
+ spec.files += Dir.glob('{contrib,lib,recipes,spec}/**/*')
8
8
  spec.homepage = 'http://github.com/collectiveidea/delayed_job'
9
9
  spec.licenses = ['MIT']
10
10
  spec.name = 'delayed_job'
11
11
  spec.require_paths = ['lib']
12
12
  spec.summary = 'Database-backed asynchronous priority queue system -- Extracted from Shopify'
13
- spec.version = '4.0.5'
13
+ spec.test_files = Dir.glob('spec/**/*')
14
+ spec.version = '4.0.6'
14
15
  end
@@ -0,0 +1,7 @@
1
+ # Make sure this file does not get required manually
2
+ module Autoloaded
3
+ class Clazz
4
+ def perform
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ module Autoloaded
2
+ class InstanceClazz
3
+ def perform
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Autoloaded
2
+ class InstanceStruct < ::Struct.new(nil)
3
+ def perform
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ # Make sure this file does not get required manually
2
+ module Autoloaded
3
+ class Struct < ::Struct.new(nil)
4
+ def perform
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,117 @@
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
+ def self.all
32
+ @jobs ||= []
33
+ end
34
+
35
+ def self.count
36
+ all.size
37
+ end
38
+
39
+ def self.delete_all
40
+ all.clear
41
+ end
42
+
43
+ def self.create(attrs = {})
44
+ new(attrs).tap do |o|
45
+ o.save
46
+ end
47
+ end
48
+
49
+ def self.create!(*args)
50
+ create(*args)
51
+ end
52
+
53
+ def self.clear_locks!(worker_name)
54
+ all.select { |j| j.locked_by == worker_name }.each do |j|
55
+ j.locked_by = nil
56
+ j.locked_at = nil
57
+ end
58
+ end
59
+
60
+ # Find a few candidate jobs to run (in case some immediately get locked by others).
61
+ def self.find_available(worker_name, limit = 5, max_run_time = Worker.max_run_time) # rubocop:disable CyclomaticComplexity, PerceivedComplexity
62
+ jobs = all.select do |j|
63
+ j.run_at <= db_time_now &&
64
+ (j.locked_at.nil? || j.locked_at < db_time_now - max_run_time || j.locked_by == worker_name) &&
65
+ !j.failed?
66
+ end
67
+ jobs.select! { |j| j.priority <= Worker.max_priority } if Worker.max_priority
68
+ jobs.select! { |j| j.priority >= Worker.min_priority } if Worker.min_priority
69
+ jobs.select! { |j| Worker.queues.include?(j.queue) } if Worker.queues.any?
70
+ jobs.sort_by! { |j| [j.priority, j.run_at] }[0..limit - 1]
71
+ end
72
+
73
+ # Lock this job for this worker.
74
+ # Returns true if we have the lock, false otherwise.
75
+ def lock_exclusively!(_max_run_time, worker)
76
+ now = self.class.db_time_now
77
+ if locked_by != worker
78
+ # We don't own this job so we will update the locked_by name and the locked_at
79
+ self.locked_at = now
80
+ self.locked_by = worker
81
+ end
82
+
83
+ true
84
+ end
85
+
86
+ def self.db_time_now
87
+ Time.current
88
+ end
89
+
90
+ def update_attributes(attrs = {})
91
+ attrs.each { |k, v| send(:"#{k}=", v) }
92
+ save
93
+ end
94
+
95
+ def destroy
96
+ self.class.all.delete(self)
97
+ end
98
+
99
+ def save
100
+ self.run_at ||= Time.current
101
+
102
+ self.class.all << self unless self.class.all.include?(self)
103
+ true
104
+ end
105
+
106
+ def save!
107
+ save
108
+ end
109
+
110
+ def reload
111
+ reset
112
+ self
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,57 @@
1
+ require 'helper'
2
+ require 'delayed/command'
3
+
4
+ describe Delayed::Command do
5
+ describe 'parsing --pool argument' do
6
+ it 'should parse --pool correctly' do
7
+ command = Delayed::Command.new(['--pool=*:1', '--pool=test_queue:4', '--pool=mailers,misc:2'])
8
+
9
+ expect(command.worker_pools).to eq [
10
+ [[], 1],
11
+ [['test_queue'], 4],
12
+ [%w[mailers misc], 2]
13
+ ]
14
+ end
15
+
16
+ it 'should allow * or blank to specify any pools' do
17
+ command = Delayed::Command.new(['--pool=*:4'])
18
+ expect(command.worker_pools).to eq [
19
+ [[], 4],
20
+ ]
21
+
22
+ command = Delayed::Command.new(['--pool=:4'])
23
+ expect(command.worker_pools).to eq [
24
+ [[], 4],
25
+ ]
26
+ end
27
+
28
+ it 'should default to one worker if not specified' do
29
+ command = Delayed::Command.new(['--pool=mailers'])
30
+ expect(command.worker_pools).to eq [
31
+ [['mailers'], 1],
32
+ ]
33
+ end
34
+ end
35
+
36
+ describe 'running worker pools defined by multiple --pool arguments' do
37
+ it 'should run the correct worker processes' do
38
+ command = Delayed::Command.new(['--pool=*:1', '--pool=test_queue:4', '--pool=mailers,misc:2'])
39
+
40
+ expect(Dir).to receive(:mkdir).with('./tmp/pids').once
41
+
42
+ [
43
+ ['delayed_job.0', {:quiet => true, :pid_dir => './tmp/pids', :queues => []}],
44
+ ['delayed_job.1', {:quiet => true, :pid_dir => './tmp/pids', :queues => ['test_queue']}],
45
+ ['delayed_job.2', {:quiet => true, :pid_dir => './tmp/pids', :queues => ['test_queue']}],
46
+ ['delayed_job.3', {:quiet => true, :pid_dir => './tmp/pids', :queues => ['test_queue']}],
47
+ ['delayed_job.4', {:quiet => true, :pid_dir => './tmp/pids', :queues => ['test_queue']}],
48
+ ['delayed_job.5', {:quiet => true, :pid_dir => './tmp/pids', :queues => %w[mailers misc]}],
49
+ ['delayed_job.6', {:quiet => true, :pid_dir => './tmp/pids', :queues => %w[mailers misc]}]
50
+ ].each do |args|
51
+ expect(command).to receive(:run_process).with(*args).once
52
+ end
53
+
54
+ command.daemonize
55
+ end
56
+ end
57
+ end
File without changes
@@ -0,0 +1,85 @@
1
+ require 'simplecov'
2
+ require 'coveralls'
3
+
4
+ SimpleCov.formatters = [SimpleCov::Formatter::HTMLFormatter, Coveralls::SimpleCov::Formatter]
5
+
6
+ SimpleCov.start do
7
+ add_filter '/spec/'
8
+ # Each version of ruby and version of rails test different things
9
+ # This should probably just be removed.
10
+ minimum_coverage(85.0)
11
+ end
12
+
13
+ require 'logger'
14
+ require 'rspec'
15
+
16
+ require 'action_mailer'
17
+ require 'active_support/dependencies'
18
+ require 'active_record'
19
+
20
+ require 'delayed_job'
21
+ require 'delayed/backend/shared_spec'
22
+
23
+ if ENV['DEBUG_LOGS']
24
+ Delayed::Worker.logger = Logger.new(STDOUT)
25
+ else
26
+ require 'tempfile'
27
+
28
+ tf = Tempfile.new('dj.log')
29
+ Delayed::Worker.logger = Logger.new(tf.path)
30
+ tf.unlink
31
+ end
32
+ ENV['RAILS_ENV'] = 'test'
33
+
34
+ # Trigger AR to initialize
35
+ ActiveRecord::Base # rubocop:disable Void
36
+
37
+ module Rails
38
+ def self.root
39
+ '.'
40
+ end
41
+ end
42
+
43
+ Delayed::Worker.backend = :test
44
+
45
+ # Add this directory so the ActiveSupport autoloading works
46
+ ActiveSupport::Dependencies.autoload_paths << File.dirname(__FILE__)
47
+
48
+ # Add this to simulate Railtie initializer being executed
49
+ ActionMailer::Base.extend(Delayed::DelayMail)
50
+
51
+ # Used to test interactions between DJ and an ORM
52
+ ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
53
+ ActiveRecord::Base.logger = Delayed::Worker.logger
54
+ ActiveRecord::Migration.verbose = false
55
+
56
+ ActiveRecord::Schema.define do
57
+ create_table :stories, :primary_key => :story_id, :force => true do |table|
58
+ table.string :text
59
+ table.boolean :scoped, :default => true
60
+ end
61
+ end
62
+
63
+ class Story < ActiveRecord::Base
64
+ self.primary_key = 'story_id'
65
+ def tell
66
+ text
67
+ end
68
+
69
+ def whatever(n, _)
70
+ tell * n
71
+ end
72
+ default_scope { where(:scoped => true) }
73
+
74
+ handle_asynchronously :whatever
75
+ end
76
+
77
+ RSpec.configure do |config|
78
+ config.after(:each) do
79
+ Delayed::Worker.reset
80
+ end
81
+
82
+ config.expect_with :rspec do |c|
83
+ c.syntax = :expect
84
+ end
85
+ end
@@ -0,0 +1,75 @@
1
+ require '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) { double(Object, :before! => nil, :after! => nil, :inside! => nil) }
8
+ let(:wrapped_block) { proc { behavior.inside! } }
9
+
10
+ describe 'before callbacks' do
11
+ before(:each) do
12
+ lifecycle.before(:execute, &callback)
13
+ end
14
+
15
+ it 'executes before wrapped block' do
16
+ expect(callback).to receive(:call).with(*arguments).ordered
17
+ expect(behavior).to 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 'executes after wrapped block' do
28
+ expect(behavior).to receive(:inside!).ordered
29
+ expect(callback).to 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 'wraps a block' do
44
+ expect(behavior).to receive(:before!).ordered
45
+ expect(behavior).to receive(:inside!).ordered
46
+ expect(behavior).to receive(:after!).ordered
47
+ lifecycle.run_callbacks :execute, *arguments, &wrapped_block
48
+ end
49
+
50
+ it 'executes multiple callbacks in order' do
51
+ expect(behavior).to receive(:one).ordered
52
+ expect(behavior).to receive(:two).ordered
53
+ expect(behavior).to receive(:three).ordered
54
+
55
+ lifecycle.around(:execute) do |*args, &block|
56
+ behavior.one
57
+ block.call(*args)
58
+ end
59
+ lifecycle.around(:execute) do |*args, &block|
60
+ behavior.two
61
+ block.call(*args)
62
+ end
63
+ lifecycle.around(:execute) do |*args, &block|
64
+ behavior.three
65
+ block.call(*args)
66
+ end
67
+ lifecycle.run_callbacks(:execute, *arguments, &wrapped_block)
68
+ end
69
+ end
70
+
71
+ it 'raises if callback is executed with wrong number of parameters' do
72
+ lifecycle.before(:execute, &callback)
73
+ expect { lifecycle.run_callbacks(:execute, 1, 2, 3) {} }.to raise_error(ArgumentError, /1 parameter/)
74
+ end
75
+ end
@@ -0,0 +1,122 @@
1
+ require 'helper'
2
+
3
+ describe Delayed::MessageSending do
4
+ describe 'handle_asynchronously' do
5
+ class Story
6
+ def tell!(_arg); end
7
+ handle_asynchronously :tell!
8
+ end
9
+
10
+ it 'aliases original method' do
11
+ expect(Story.new).to respond_to(:tell_without_delay!)
12
+ expect(Story.new).to respond_to(:tell_with_delay!)
13
+ end
14
+
15
+ it 'creates a PerformableMethod' do
16
+ story = Story.create
17
+ expect do
18
+ job = story.tell!(1)
19
+ expect(job.payload_object.class).to eq(Delayed::PerformableMethod)
20
+ expect(job.payload_object.method_name).to eq(:tell_without_delay!)
21
+ expect(job.payload_object.args).to eq([1])
22
+ end.to change { Delayed::Job.count }
23
+ end
24
+
25
+ describe 'with options' do
26
+ class Fable
27
+ cattr_accessor :importance
28
+ def tell; end
29
+ handle_asynchronously :tell, :priority => proc { importance }
30
+ end
31
+
32
+ it 'sets the priority based on the Fable importance' do
33
+ Fable.importance = 10
34
+ job = Fable.new.tell
35
+ expect(job.priority).to eq(10)
36
+
37
+ Fable.importance = 20
38
+ job = Fable.new.tell
39
+ expect(job.priority).to eq(20)
40
+ end
41
+
42
+ describe 'using a proc with parameters' do
43
+ class Yarn
44
+ attr_accessor :importance
45
+ def spin
46
+ end
47
+ handle_asynchronously :spin, :priority => proc { |y| y.importance }
48
+ end
49
+
50
+ it 'sets the priority based on the Fable importance' do
51
+ job = Yarn.new.tap { |y| y.importance = 10 }.spin
52
+ expect(job.priority).to eq(10)
53
+
54
+ job = Yarn.new.tap { |y| y.importance = 20 }.spin
55
+ expect(job.priority).to eq(20)
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ context 'delay' do
62
+ class FairyTail
63
+ attr_accessor :happy_ending
64
+ def self.princesses; end
65
+ def tell
66
+ @happy_ending = true
67
+ end
68
+ end
69
+
70
+ after do
71
+ Delayed::Worker.default_queue_name = nil
72
+ end
73
+
74
+ it 'creates a new PerformableMethod job' do
75
+ expect do
76
+ job = 'hello'.delay.count('l')
77
+ expect(job.payload_object.class).to eq(Delayed::PerformableMethod)
78
+ expect(job.payload_object.method_name).to eq(:count)
79
+ expect(job.payload_object.args).to eq(['l'])
80
+ end.to change { Delayed::Job.count }.by(1)
81
+ end
82
+
83
+ it 'sets default priority' do
84
+ Delayed::Worker.default_priority = 99
85
+ job = FairyTail.delay.to_s
86
+ expect(job.priority).to eq(99)
87
+ end
88
+
89
+ it 'sets default queue name' do
90
+ Delayed::Worker.default_queue_name = 'abbazabba'
91
+ job = FairyTail.delay.to_s
92
+ expect(job.queue).to eq('abbazabba')
93
+ end
94
+
95
+ it 'sets job options' do
96
+ run_at = Time.parse('2010-05-03 12:55 AM')
97
+ job = FairyTail.delay(:priority => 20, :run_at => run_at).to_s
98
+ expect(job.run_at).to eq(run_at)
99
+ expect(job.priority).to eq(20)
100
+ end
101
+
102
+ it 'does not delay the job when delay_jobs is false' do
103
+ Delayed::Worker.delay_jobs = false
104
+ fairy_tail = FairyTail.new
105
+ expect do
106
+ expect do
107
+ fairy_tail.delay.tell
108
+ end.to change(fairy_tail, :happy_ending).from(nil).to(true)
109
+ end.not_to change { Delayed::Job.count }
110
+ end
111
+
112
+ it 'does delay the job when delay_jobs is true' do
113
+ Delayed::Worker.delay_jobs = true
114
+ fairy_tail = FairyTail.new
115
+ expect do
116
+ expect do
117
+ fairy_tail.delay.tell
118
+ end.not_to change(fairy_tail, :happy_ending)
119
+ end.to change { Delayed::Job.count }.by(1)
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,43 @@
1
+ require 'helper'
2
+
3
+ require 'action_mailer'
4
+ class MyMailer < ActionMailer::Base
5
+ def signup(email)
6
+ mail :to => email, :subject => 'Delaying Emails', :from => 'delayedjob@example.com', :body => 'Delaying Emails Body'
7
+ end
8
+ end
9
+
10
+ describe ActionMailer::Base do
11
+ describe 'delay' do
12
+ it 'enqueues a PerformableEmail job' do
13
+ expect do
14
+ job = MyMailer.delay.signup('john@example.com')
15
+ expect(job.payload_object.class).to eq(Delayed::PerformableMailer)
16
+ expect(job.payload_object.method_name).to eq(:signup)
17
+ expect(job.payload_object.args).to eq(['john@example.com'])
18
+ end.to change { Delayed::Job.count }.by(1)
19
+ end
20
+ end
21
+
22
+ describe 'delay on a mail object' do
23
+ it 'raises an exception' do
24
+ expect do
25
+ MyMailer.signup('john@example.com').delay
26
+ end.to raise_error(RuntimeError)
27
+ end
28
+ end
29
+
30
+ describe Delayed::PerformableMailer do
31
+ describe 'perform' do
32
+ it 'calls the method and #deliver on the mailer' do
33
+ email = double('email', :deliver => true)
34
+ mailer_class = double('MailerClass', :signup => email)
35
+ mailer = Delayed::PerformableMailer.new(mailer_class, :signup, ['john@example.com'])
36
+
37
+ expect(mailer_class).to receive(:signup).with('john@example.com')
38
+ expect(email).to receive(:deliver)
39
+ mailer.perform
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,111 @@
1
+ require 'helper'
2
+
3
+ describe Delayed::PerformableMethod do
4
+ describe 'perform' do
5
+ before do
6
+ @method = Delayed::PerformableMethod.new('foo', :count, ['o'])
7
+ end
8
+
9
+ context 'with the persisted record cannot be found' do
10
+ before do
11
+ @method.object = nil
12
+ end
13
+
14
+ it 'does nothing if object is nil' do
15
+ expect { @method.perform }.not_to raise_error
16
+ end
17
+ end
18
+
19
+ it 'calls the method on the object' do
20
+ expect(@method.object).to receive(:count).with('o')
21
+ @method.perform
22
+ end
23
+ end
24
+
25
+ it "raises a NoMethodError if target method doesn't exist" do
26
+ expect do
27
+ Delayed::PerformableMethod.new(Object, :method_that_does_not_exist, [])
28
+ end.to raise_error(NoMethodError)
29
+ end
30
+
31
+ it 'does not raise NoMethodError if target method is private' do
32
+ clazz = Class.new do
33
+ def private_method
34
+ end
35
+ private :private_method
36
+ end
37
+ expect { Delayed::PerformableMethod.new(clazz.new, :private_method, []) }.not_to raise_error
38
+ end
39
+
40
+ describe 'display_name' do
41
+ it 'returns class_name#method_name for instance methods' do
42
+ expect(Delayed::PerformableMethod.new('foo', :count, ['o']).display_name).to eq('String#count')
43
+ end
44
+
45
+ it 'returns class_name.method_name for class methods' do
46
+ expect(Delayed::PerformableMethod.new(Class, :inspect, []).display_name).to eq('Class.inspect')
47
+ end
48
+ end
49
+
50
+ describe 'hooks' do
51
+ %w[before after success].each do |hook|
52
+ it "delegates #{hook} hook to object" do
53
+ story = Story.create
54
+ job = story.delay.tell
55
+
56
+ expect(story).to receive(hook).with(job)
57
+ job.invoke_job
58
+ end
59
+ end
60
+
61
+ it 'delegates enqueue hook to object' do
62
+ story = Story.create
63
+ expect(story).to receive(:enqueue).with(an_instance_of(Delayed::Job))
64
+ story.delay.tell
65
+ end
66
+
67
+ it 'delegates error hook to object' do
68
+ story = Story.create
69
+ expect(story).to receive(:error).with(an_instance_of(Delayed::Job), an_instance_of(RuntimeError))
70
+ expect(story).to receive(:tell).and_raise(RuntimeError)
71
+ expect { story.delay.tell.invoke_job }.to raise_error
72
+ end
73
+
74
+ it 'delegates failure hook to object' do
75
+ method = Delayed::PerformableMethod.new('object', :size, [])
76
+ expect(method.object).to receive(:failure)
77
+ method.failure
78
+ end
79
+
80
+ context 'with delay_job == false' do
81
+ before do
82
+ Delayed::Worker.delay_jobs = false
83
+ end
84
+
85
+ after do
86
+ Delayed::Worker.delay_jobs = true
87
+ end
88
+
89
+ %w[before after success].each do |hook|
90
+ it "delegates #{hook} hook to object" do
91
+ story = Story.create
92
+ expect(story).to receive(hook).with(an_instance_of(Delayed::Job))
93
+ story.delay.tell
94
+ end
95
+ end
96
+
97
+ it 'delegates error hook to object' do
98
+ story = Story.create
99
+ expect(story).to receive(:error).with(an_instance_of(Delayed::Job), an_instance_of(RuntimeError))
100
+ expect(story).to receive(:tell).and_raise(RuntimeError)
101
+ expect { story.delay.tell }.to raise_error
102
+ end
103
+
104
+ it 'delegates failure hook to object' do
105
+ method = Delayed::PerformableMethod.new('object', :size, [])
106
+ expect(method.object).to receive(:failure)
107
+ method.failure
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,12 @@
1
+ require 'helper'
2
+
3
+ describe 'Psych::Visitors::ToRuby', :if => defined?(Psych::Visitors::ToRuby) do
4
+ context BigDecimal do
5
+ it 'deserializes correctly' do
6
+ deserialized = YAML.load("--- !ruby/object:BigDecimal 18:0.1337E2\n...\n")
7
+
8
+ expect(deserialized).to be_an_instance_of(BigDecimal)
9
+ expect(deserialized).to eq(BigDecimal('13.37'))
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,109 @@
1
+ class NamedJob < Struct.new(:perform)
2
+ def display_name
3
+ 'named_job'
4
+ end
5
+ end
6
+
7
+ class SimpleJob
8
+ cattr_accessor :runs
9
+ @runs = 0
10
+ def perform
11
+ self.class.runs += 1
12
+ end
13
+ end
14
+
15
+ class NamedQueueJob < SimpleJob
16
+ def queue_name
17
+ 'job_tracking'
18
+ end
19
+ end
20
+
21
+ class ErrorJob
22
+ cattr_accessor :runs
23
+ @runs = 0
24
+ def perform
25
+ raise 'did not work'
26
+ end
27
+ end
28
+
29
+ class CustomRescheduleJob < Struct.new(:offset)
30
+ cattr_accessor :runs
31
+ @runs = 0
32
+ def perform
33
+ raise 'did not work'
34
+ end
35
+
36
+ def reschedule_at(time, _attempts)
37
+ time + offset
38
+ end
39
+ end
40
+
41
+ class LongRunningJob
42
+ def perform
43
+ sleep 250
44
+ end
45
+ end
46
+
47
+ class OnPermanentFailureJob < SimpleJob
48
+ attr_writer :raise_error
49
+
50
+ def initialize
51
+ @raise_error = false
52
+ end
53
+
54
+ def failure
55
+ raise 'did not work' if @raise_error
56
+ end
57
+
58
+ def max_attempts
59
+ 1
60
+ end
61
+ end
62
+
63
+ module M
64
+ class ModuleJob
65
+ cattr_accessor :runs
66
+ @runs = 0
67
+ def perform
68
+ self.class.runs += 1
69
+ end
70
+ end
71
+ end
72
+
73
+ class CallbackJob
74
+ cattr_accessor :messages
75
+
76
+ def enqueue(_job)
77
+ self.class.messages << 'enqueue'
78
+ end
79
+
80
+ def before(_job)
81
+ self.class.messages << 'before'
82
+ end
83
+
84
+ def perform
85
+ self.class.messages << 'perform'
86
+ end
87
+
88
+ def after(_job)
89
+ self.class.messages << 'after'
90
+ end
91
+
92
+ def success(_job)
93
+ self.class.messages << 'success'
94
+ end
95
+
96
+ def error(_job, error)
97
+ self.class.messages << "error: #{error.class}"
98
+ end
99
+
100
+ def failure(_job)
101
+ self.class.messages << 'failure'
102
+ end
103
+ end
104
+
105
+ class EnqueueJobMod < SimpleJob
106
+ def enqueue(job)
107
+ job.run_at = 20.minutes.from_now
108
+ end
109
+ end
@@ -0,0 +1,13 @@
1
+ require '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 'causes the payload object to be reloaded' do
8
+ job = 'foo'.delay.length
9
+ o = job.payload_object
10
+ expect(o.object_id).not_to eq(job.reload.payload_object.object_id)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,157 @@
1
+ require 'helper'
2
+
3
+ describe Delayed::Worker do
4
+ describe 'backend=' do
5
+ before do
6
+ @clazz = Class.new
7
+ Delayed::Worker.backend = @clazz
8
+ end
9
+
10
+ after do
11
+ Delayed::Worker.backend = :test
12
+ end
13
+
14
+ it 'sets the Delayed::Job constant to the backend' do
15
+ expect(Delayed::Job).to eq(@clazz)
16
+ end
17
+
18
+ it 'sets backend with a symbol' do
19
+ Delayed::Worker.backend = :test
20
+ expect(Delayed::Worker.backend).to eq(Delayed::Backend::Test::Job)
21
+ end
22
+ end
23
+
24
+ describe 'job_say' do
25
+ before do
26
+ @worker = Delayed::Worker.new
27
+ @job = double('job', :id => 123, :name => 'ExampleJob')
28
+ end
29
+
30
+ it 'logs with job name and id' do
31
+ expect(@worker).to receive(:say).
32
+ with('Job ExampleJob (id=123) message', Delayed::Worker.default_log_level)
33
+ @worker.job_say(@job, 'message')
34
+ end
35
+
36
+ it 'has a configurable default log level' do
37
+ Delayed::Worker.default_log_level = 'error'
38
+
39
+ expect(@worker).to receive(:say).
40
+ with('Job ExampleJob (id=123) message', 'error')
41
+ @worker.job_say(@job, 'message')
42
+ end
43
+ end
44
+
45
+ context 'worker read-ahead' do
46
+ before do
47
+ @read_ahead = Delayed::Worker.read_ahead
48
+ end
49
+
50
+ after do
51
+ Delayed::Worker.read_ahead = @read_ahead
52
+ end
53
+
54
+ it 'reads five jobs' do
55
+ expect(Delayed::Job).to receive(:find_available).with(anything, 5, anything).and_return([])
56
+ Delayed::Job.reserve(Delayed::Worker.new)
57
+ end
58
+
59
+ it 'reads a configurable number of jobs' do
60
+ Delayed::Worker.read_ahead = 15
61
+ expect(Delayed::Job).to receive(:find_available).with(anything, Delayed::Worker.read_ahead, anything).and_return([])
62
+ Delayed::Job.reserve(Delayed::Worker.new)
63
+ end
64
+ end
65
+
66
+ context 'worker exit on complete' do
67
+ before do
68
+ Delayed::Worker.exit_on_complete = true
69
+ end
70
+
71
+ after do
72
+ Delayed::Worker.exit_on_complete = false
73
+ end
74
+
75
+ it 'exits the loop when no jobs are available' do
76
+ worker = Delayed::Worker.new
77
+ Timeout.timeout(2) do
78
+ worker.start
79
+ end
80
+ end
81
+ end
82
+
83
+ context 'worker job reservation' do
84
+ before do
85
+ Delayed::Worker.exit_on_complete = true
86
+ end
87
+
88
+ after do
89
+ Delayed::Worker.exit_on_complete = false
90
+ end
91
+
92
+ it 'handles error during job reservation' do
93
+ expect(Delayed::Job).to receive(:reserve).and_raise(Exception)
94
+ Delayed::Worker.new.work_off
95
+ end
96
+
97
+ it 'gives up after 10 backend failures' do
98
+ expect(Delayed::Job).to receive(:reserve).exactly(10).times.and_raise(Exception)
99
+ worker = Delayed::Worker.new
100
+ 9.times { worker.work_off }
101
+ expect(lambda { worker.work_off }).to raise_exception Delayed::FatalBackendError
102
+ end
103
+
104
+ it 'allows the backend to attempt recovery from reservation errors' do
105
+ expect(Delayed::Job).to receive(:reserve).and_raise(Exception)
106
+ expect(Delayed::Job).to receive(:recover_from).with(instance_of(Exception))
107
+ Delayed::Worker.new.work_off
108
+ end
109
+ end
110
+
111
+ context '#say' do
112
+ before(:each) do
113
+ @worker = Delayed::Worker.new
114
+ @worker.name = 'ExampleJob'
115
+ @worker.logger = double('job')
116
+ time = Time.now
117
+ allow(Time).to receive(:now).and_return(time)
118
+ @text = 'Job executed'
119
+ @worker_name = '[Worker(ExampleJob)]'
120
+ @expected_time = time.strftime('%FT%T%z')
121
+ end
122
+
123
+ after(:each) do
124
+ @worker.logger = nil
125
+ end
126
+
127
+ shared_examples_for 'a worker which logs on the correct severity' do |severity|
128
+ it "logs a message on the #{severity[:level].upcase} level given a string" do
129
+ expect(@worker.logger).to receive(:send).
130
+ with(severity[:level], "#{@expected_time}: #{@worker_name} #{@text}")
131
+ @worker.say(@text, severity[:level])
132
+ end
133
+
134
+ it "logs a message on the #{severity[:level].upcase} level given a fixnum" do
135
+ expect(@worker.logger).to receive(:send).
136
+ with(severity[:level], "#{@expected_time}: #{@worker_name} #{@text}")
137
+ @worker.say(@text, severity[:index])
138
+ end
139
+ end
140
+
141
+ severities = [{:index => 0, :level => 'debug'},
142
+ {:index => 1, :level => 'info'},
143
+ {:index => 2, :level => 'warn'},
144
+ {:index => 3, :level => 'error'},
145
+ {:index => 4, :level => 'fatal'},
146
+ {:index => 5, :level => 'unknown'}]
147
+ severities.each do |severity|
148
+ it_behaves_like 'a worker which logs on the correct severity', severity
149
+ end
150
+
151
+ it 'logs a message on the default log\'s level' do
152
+ expect(@worker.logger).to receive(:send).
153
+ with('info', "#{@expected_time}: #{@worker_name} #{@text}")
154
+ @worker.say(@text, Delayed::Worker.default_log_level)
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,48 @@
1
+ require 'helper'
2
+
3
+ describe 'YAML' do
4
+ it 'autoloads classes' do
5
+ expect do
6
+ yaml = "--- !ruby/class Autoloaded::Clazz\n"
7
+ expect(load_with_delayed_visitor(yaml)).to eq(Autoloaded::Clazz)
8
+ end.not_to raise_error
9
+ end
10
+
11
+ it 'autoloads the class of a struct' do
12
+ expect do
13
+ yaml = "--- !ruby/class Autoloaded::Struct\n"
14
+ expect(load_with_delayed_visitor(yaml)).to eq(Autoloaded::Struct)
15
+ end.not_to raise_error
16
+ end
17
+
18
+ it 'autoloads the class for the instance of a struct' do
19
+ expect do
20
+ yaml = '--- !ruby/struct:Autoloaded::InstanceStruct {}'
21
+ expect(load_with_delayed_visitor(yaml).class).to eq(Autoloaded::InstanceStruct)
22
+ end.not_to raise_error
23
+ end
24
+
25
+ it 'autoloads the class of an anonymous struct' do
26
+ expect do
27
+ yaml = "--- !ruby/struct\nn: 1\n"
28
+ object = YAML.load(yaml)
29
+ expect(object).to be_kind_of(Struct)
30
+ expect(object.n).to eq(1)
31
+ end.not_to raise_error
32
+ end
33
+
34
+ it 'autoloads the class for the instance' do
35
+ expect do
36
+ yaml = "--- !ruby/object:Autoloaded::InstanceClazz {}\n"
37
+ expect(load_with_delayed_visitor(yaml).class).to eq(Autoloaded::InstanceClazz)
38
+ end.not_to raise_error
39
+ end
40
+
41
+ it 'does not throw an uninitialized constant Syck::Syck when using YAML.load with poorly formed yaml' do
42
+ expect { YAML.load(YAML.dump('foo: *bar')) }.not_to raise_error
43
+ end
44
+
45
+ def load_with_delayed_visitor(yaml)
46
+ YAML.load_dj(yaml)
47
+ end
48
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: delayed_job
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.5
4
+ version: 4.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brandon Keepers
@@ -50,6 +50,7 @@ files:
50
50
  - CONTRIBUTING.md
51
51
  - LICENSE.md
52
52
  - README.md
53
+ - Rakefile
53
54
  - contrib/delayed_job.monitrc
54
55
  - contrib/delayed_job_multiple.monitrc
55
56
  - contrib/delayed_job_rails_4.monitrc
@@ -79,6 +80,23 @@ files:
79
80
  - lib/generators/delayed_job/delayed_job_generator.rb
80
81
  - lib/generators/delayed_job/templates/script
81
82
  - recipes/delayed_job.rb
83
+ - spec/autoloaded/clazz.rb
84
+ - spec/autoloaded/instance_clazz.rb
85
+ - spec/autoloaded/instance_struct.rb
86
+ - spec/autoloaded/struct.rb
87
+ - spec/delayed/backend/test.rb
88
+ - spec/delayed/command_spec.rb
89
+ - spec/delayed/serialization/test.rb
90
+ - spec/helper.rb
91
+ - spec/lifecycle_spec.rb
92
+ - spec/message_sending_spec.rb
93
+ - spec/performable_mailer_spec.rb
94
+ - spec/performable_method_spec.rb
95
+ - spec/psych_ext_spec.rb
96
+ - spec/sample_jobs.rb
97
+ - spec/test_backend_spec.rb
98
+ - spec/worker_spec.rb
99
+ - spec/yaml_ext_spec.rb
82
100
  homepage: http://github.com/collectiveidea/delayed_job
83
101
  licenses:
84
102
  - MIT
@@ -103,4 +121,21 @@ rubygems_version: 2.4.4
103
121
  signing_key:
104
122
  specification_version: 4
105
123
  summary: Database-backed asynchronous priority queue system -- Extracted from Shopify
106
- test_files: []
124
+ test_files:
125
+ - spec/autoloaded/clazz.rb
126
+ - spec/autoloaded/instance_clazz.rb
127
+ - spec/autoloaded/instance_struct.rb
128
+ - spec/autoloaded/struct.rb
129
+ - spec/delayed/backend/test.rb
130
+ - spec/delayed/command_spec.rb
131
+ - spec/delayed/serialization/test.rb
132
+ - spec/helper.rb
133
+ - spec/lifecycle_spec.rb
134
+ - spec/message_sending_spec.rb
135
+ - spec/performable_mailer_spec.rb
136
+ - spec/performable_method_spec.rb
137
+ - spec/psych_ext_spec.rb
138
+ - spec/sample_jobs.rb
139
+ - spec/test_backend_spec.rb
140
+ - spec/worker_spec.rb
141
+ - spec/yaml_ext_spec.rb