delayed_job 4.0.5 → 4.0.6

Sign up to get free protection for your applications and to get access to all the features.
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