ctt-background-jobs 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/.gitignore +18 -0
  2. data/.rspec +1 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/CTT-Background-Jobs.gemspec +37 -0
  6. data/Gemfile +6 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +25 -0
  9. data/Rakefile +29 -0
  10. data/lib/background_jobs/configuration.rb +27 -0
  11. data/lib/background_jobs/job.rb +17 -0
  12. data/lib/background_jobs/job_factory.rb +9 -0
  13. data/lib/background_jobs/job_id_generator.rb +15 -0
  14. data/lib/background_jobs/job_notifier.rb +31 -0
  15. data/lib/background_jobs/job_queue_factory.rb +36 -0
  16. data/lib/background_jobs/job_registry.rb +65 -0
  17. data/lib/background_jobs/queue_service.rb +17 -0
  18. data/lib/background_jobs/strategies/direct_call/job_queue.rb +11 -0
  19. data/lib/background_jobs/strategies/sidekiq/job_adapter.rb +21 -0
  20. data/lib/background_jobs/strategies/sidekiq/job_attributes_adapter.rb +141 -0
  21. data/lib/background_jobs/strategies/sidekiq/job_queue.rb +42 -0
  22. data/lib/background_jobs/strategies/sidekiq/setup.rb +31 -0
  23. data/lib/version.rb +3 -0
  24. data/spec/fast_helper.rb +11 -0
  25. data/spec/integration/sidekiq_spec.rb +34 -0
  26. data/spec/lib/background_jobs/configuration_spec.rb +13 -0
  27. data/spec/lib/background_jobs/job_factory_fast_spec.rb +24 -0
  28. data/spec/lib/background_jobs/job_fast_spec.rb +38 -0
  29. data/spec/lib/background_jobs/job_id_generator_fast_spec.rb +23 -0
  30. data/spec/lib/background_jobs/job_notififer_spec.rb +9 -0
  31. data/spec/lib/background_jobs/job_queue_factory_fast_spec.rb +58 -0
  32. data/spec/lib/background_jobs/queue_service_fast_spec.rb +36 -0
  33. data/spec/lib/background_jobs/strategies/direct_call/job_queue_spec.rb +27 -0
  34. data/spec/lib/background_jobs/strategies/sidekiq/job_adapter_spec.rb +30 -0
  35. data/spec/lib/background_jobs/strategies/sidekiq/job_attributes_adapter_spec.rb +129 -0
  36. data/spec/lib/background_jobs/strategies/sidekiq/job_queue_spec.rb +63 -0
  37. data/spec/lib/background_jobs/torquebox_jobs_queue_fast_spec.rb +31 -0
  38. data/spec/support/job_queue.rb +7 -0
  39. metadata +281 -0
@@ -0,0 +1,42 @@
1
+ require 'sidekiq'
2
+ require 'background_jobs/job_registry'
3
+ require 'background_jobs/strategies/sidekiq/job_adapter'
4
+ require 'background_jobs/strategies/sidekiq/job_attributes_adapter'
5
+
6
+ module BackgroundJobs
7
+ module Sidekiq
8
+ class JobQueue
9
+
10
+ def enqueue(job_type, job_id, attributes, options = {})
11
+ priority = options[:priority] || 1
12
+ unique = options[:unique] || false
13
+ at = (Time.now + options[:in]).to_i if options[:in]
14
+
15
+ return if unique && is_scheduled?(job_type)
16
+
17
+ queue = BackgroundJobs.job_registry.get_type(job_type)
18
+ attributes.unshift job_id
19
+ attributes.unshift job_type
20
+
21
+ args = {
22
+ 'queue' => queue.to_s, # queue by priority?
23
+ 'class' => JobAdapter,
24
+ 'args' => JobAttributesAdapter.new(attributes).encode
25
+ }
26
+
27
+ args['at'] = at if at
28
+
29
+ ::Sidekiq::Client.push(args)
30
+ end
31
+
32
+ private
33
+
34
+ def is_scheduled?(job_name)
35
+ entries = ::Sidekiq::ScheduledSet.new.entries
36
+ job_names = entries.map(&:item).map {|item| item['args'].first }
37
+
38
+ job_names.include?(job_name.to_s)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,31 @@
1
+ require 'sidekiq'
2
+ require 'background_jobs/job_registry'
3
+
4
+ module BackgroundJobs
5
+ module Sidekiq
6
+ def self.setup(options)
7
+ ::Sidekiq.configure_server do |config|
8
+ config.redis = options[:redis] if options[:redis]
9
+ end
10
+
11
+ BackgroundJobs.job_registry.register_callback = method(:register_job_callback)
12
+ end
13
+
14
+ def self.register_job_callback(job_metadata)
15
+ options = job_metadata.options
16
+
17
+ BackgroundJobs::QueueService.enqueue(
18
+ job_metadata.name, [], :in => options[:schedule], :unique => true
19
+ ) if options[:schedule]
20
+ end
21
+
22
+ private
23
+
24
+ def self.is_scheduled?(job_name)
25
+ entries = ::Sidekiq::ScheduledSet.new.entries
26
+ job_names = entries.map(&:item).map {|item| item['args'].first }
27
+
28
+ job_names.include?(job_name.to_s)
29
+ end
30
+ end
31
+ end
data/lib/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module BackgroundJobs
2
+ VERSION = '0.8.0'
3
+ end
@@ -0,0 +1,11 @@
1
+ # This file is copied to spec/ when you run 'rails generate rspec:install'
2
+ ENV["RAILS_ENV"] ||= 'test'
3
+
4
+ require 'rspec'
5
+ require 'shoulda'
6
+ require 'require_relative'
7
+ require 'timecop'
8
+
9
+ RSpec.configure do |config|
10
+ config.mock_with :mocha
11
+ end
@@ -0,0 +1,34 @@
1
+ require 'fast_helper'
2
+ require 'background_jobs/configuration'
3
+ require 'background_jobs/queue_service'
4
+ require 'background_jobs/strategies/sidekiq/setup'
5
+
6
+ describe 'Sidekiq' do
7
+
8
+ let(:sidekiq_fake_job) { Class.new }
9
+
10
+ let(:clock) { stub('clock') }
11
+ let(:clock_schedule) { stub('clock_schedule') }
12
+
13
+ before do
14
+ BackgroundJobs.configure do |config|
15
+ config.strategy = 'sidekiq'
16
+ end
17
+ end
18
+
19
+ context 'with schedule' do
20
+ let(:scheduled_set) { stub('scheduled_set') }
21
+
22
+ it 'should schedule the job if schedule is set' do
23
+ BackgroundJobs::QueueService.expects(:enqueue).with(:fake_job, [], :in => 60*10, :unique => true)
24
+
25
+ BackgroundJobs.register_job :fake_job, :core, sidekiq_fake_job, :schedule => 60*10
26
+ end
27
+ end
28
+
29
+ it 'should not schedule the job if schedule not set' do
30
+ BackgroundJobs::QueueService.expects(:enqueue).never
31
+
32
+ BackgroundJobs.register_job :fake_job, :core, sidekiq_fake_job
33
+ end
34
+ end
@@ -0,0 +1,13 @@
1
+ require 'fast_helper'
2
+ require 'background_jobs/configuration'
3
+
4
+ module BackgroundJobs
5
+ describe Configuration do
6
+ describe '#strategy' do
7
+ context 'with env configuration set' do
8
+ before { BackgroundJobs.configure {|c| c.strategy = 'foo' } }
9
+ it { BackgroundJobs.config.strategy.should == 'foo' }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ require 'fast_helper'
2
+ require 'background_jobs/job_factory'
3
+
4
+ module BackgroundJobs
5
+ describe JobFactory do
6
+ before do
7
+ BackgroundJobs.register_job :foo, :bar, Imitate.mock('Foo')
8
+ end
9
+
10
+ describe '.build' do
11
+ context 'for existing job' do
12
+ it 'should create job instance' do
13
+ JobFactory.build(:foo).should be_instance_of Foo
14
+ end
15
+ end
16
+
17
+ context 'for not-existing job' do
18
+ it 'should throw an exception' do
19
+ expect { JobFactory.build(:not_existing_job) }.to raise_error("Unknown job 'not_existing_job'!")
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,38 @@
1
+ require 'fast_helper'
2
+ require 'background_jobs/job'
3
+
4
+ describe BackgroundJobs::Job do
5
+ before do
6
+ Imitate.mock 'Airbrake' do
7
+ def self.notify(param); end
8
+ end
9
+ end
10
+
11
+ let(:job) { BackgroundJobs::Job.new }
12
+
13
+ subject { job }
14
+
15
+ describe '#perform' do
16
+ it { expect { subject.perform }.to raise_error NotImplementedError }
17
+ end
18
+
19
+ describe '#id=' do
20
+ before { job.id = 123 }
21
+
22
+ it { subject.instance_variable_get(:@id).should eq 123 }
23
+ end
24
+
25
+ describe '#execute' do
26
+ it { expect { subject.execute }.to raise_error NotImplementedError }
27
+ end
28
+
29
+ describe 'error' do
30
+ let(:exception) { stub('exception') }
31
+
32
+ before do
33
+ Airbrake.expects(:notify).with(exception)
34
+ end
35
+
36
+ it { job.error('whatever', exception) }
37
+ end
38
+ end
@@ -0,0 +1,23 @@
1
+ require 'fast_helper'
2
+ require 'background_jobs/job_id_generator'
3
+ require 'imitate'
4
+
5
+ describe BackgroundJobs::JobIdGenerator do
6
+ let(:generator) do
7
+ Imitate.mock('Generator') do
8
+ include BackgroundJobs::JobIdGenerator
9
+ end
10
+ end
11
+
12
+ describe '.generate' do
13
+ let(:result1) { generator.generate_job_id }
14
+ let(:result2) { generator.generate_job_id }
15
+ let(:result3) { generator.generate_job_id }
16
+
17
+ subject { result1 }
18
+
19
+ it { should_not == result2 }
20
+ it { should_not == result3 }
21
+ it(:result2) { should_not == result3 }
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ require 'fast_helper'
2
+ require 'background_jobs/job_notifier'
3
+
4
+ describe BackgroundJobs::JobNotifier do
5
+
6
+ let(:job) { stub('job').tap {|j| j.extend BackgroundJobs::JobNotifier } }
7
+
8
+ it { should be }
9
+ end
@@ -0,0 +1,58 @@
1
+ require 'fast_helper'
2
+ require 'background_jobs/job_queue_factory'
3
+ require 'background_jobs/strategies/sidekiq/job_queue'
4
+ require 'background_jobs/strategies/direct_call/job_queue'
5
+
6
+ module BackgroundJobs
7
+ describe JobQueueFactory do
8
+ describe '.build' do
9
+ subject { JobQueueFactory.build }
10
+
11
+ context 'sidekiq' do
12
+ before { configure_strategy('sidekiq', options) }
13
+
14
+ let(:sidekiq_job_queue) { stub('sidekiq_job_queue') }
15
+
16
+ let(:options) { stub('options') }
17
+
18
+ it {
19
+ Sidekiq::JobQueue.expects(:new).returns(sidekiq_job_queue)
20
+ JobQueueFactory.build.should == sidekiq_job_queue
21
+ }
22
+
23
+ after { clear_job_queue_strategy }
24
+ end
25
+
26
+ context 'direct_call' do
27
+ before { configure_strategy('direct_call') }
28
+
29
+ it { should be_instance_of DirectCall::JobQueue }
30
+
31
+ after { clear_job_queue_strategy }
32
+ end
33
+
34
+ context 'with no strategy configured' do
35
+ before { clear_job_queue_strategy }
36
+
37
+ it "will blow up" do
38
+ expect {
39
+ JobQueueFactory.build
40
+ }.to raise_exception UnknownBackgroundJobsQueueStrategy
41
+ end
42
+
43
+ after { configure_strategy('delayed_job') }
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ def clear_job_queue_strategy
50
+ configure_strategy nil
51
+ end
52
+
53
+ def configure_strategy(name, options = {})
54
+ BackgroundJobs.configure do |config|
55
+ config.strategy = name
56
+ config.strategy_options = options
57
+ end
58
+ end
@@ -0,0 +1,36 @@
1
+ require 'fast_helper'
2
+ require 'background_jobs/queue_service'
3
+
4
+ module BackgroundJobs
5
+ describe QueueService do
6
+ let(:job_type) { :fix_all_bugs_while_we_sleep }
7
+
8
+ let(:priority) { 1 }
9
+
10
+ let(:data) { [1, 2, 3, 'foo', {1 => :uno}] }
11
+
12
+ let(:job_id) { 'job_id' }
13
+
14
+ let(:expected_queue_params) { data }
15
+
16
+ let(:strategy) { stub('strategy') }
17
+
18
+ describe '.enqueue' do
19
+ let(:delay) { stub('delay') }
20
+
21
+ before do
22
+ QueueService.stubs(:generate_job_id).returns(job_id)
23
+
24
+ JobQueueFactory.expects(:build).returns(strategy)
25
+
26
+ strategy.expects(:enqueue).with(
27
+ job_type, job_id, expected_queue_params, :priority => priority, :in => delay
28
+ )
29
+ end
30
+
31
+ subject { QueueService.enqueue(job_type, data, :priority => priority, :in => delay) }
32
+
33
+ it { should == job_id }
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,27 @@
1
+ require 'fast_helper'
2
+ require 'background_jobs/strategies/direct_call/job_queue'
3
+
4
+ module BackgroundJobs
5
+ module DirectCall
6
+ describe JobQueue.new do
7
+ let(:type) { :foo }
8
+
9
+ let(:priority) { 1 }
10
+
11
+ let(:attrs) { [10..10, :foo => :bar] }
12
+
13
+ let(:job) { stub('job') }
14
+
15
+ let(:job_id) { 123 }
16
+
17
+ describe '.enqueue' do
18
+ it 'should create a job and execute it' do
19
+ JobFactory.expects(:build).with(type, attrs).returns(job)
20
+ job.expects(:execute)
21
+
22
+ JobQueue.new.enqueue(type, job_id, attrs)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ require 'fast_helper'
2
+ require 'background_jobs/strategies/sidekiq/job_queue'
3
+
4
+ module BackgroundJobs
5
+ module Sidekiq
6
+ describe JobAdapter.new do
7
+ let(:job) { stub('job', :execute => true) }
8
+
9
+ describe '#peform' do
10
+ subject { JobAdapter.new.perform('foo', '1', :bar, :baz, 'range:[10,1000]') }
11
+
12
+ before { JobFactory.stubs(:build).returns(job) }
13
+
14
+ it 'should call job factory with appropriate job type and attributes' do
15
+ JobFactory.expects(:build).with(:foo, [:bar, :baz, 10..1000]).returns(job)
16
+ job.expects(:id=).with(1)
17
+
18
+ subject
19
+ end
20
+
21
+ it 'should call job perform with no attributes' do
22
+ job.expects(:execute)
23
+ job.expects(:id=).with(1)
24
+
25
+ subject
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,129 @@
1
+ require 'fast_helper'
2
+ require 'background_jobs/strategies/sidekiq/job_attributes_adapter'
3
+
4
+ module BackgroundJobs
5
+ module Sidekiq
6
+ describe JobAttributesAdapter do
7
+ context 'date' do
8
+ let(:attributes) { [Date.parse('1st Jan 2011')] }
9
+ let(:attributes_encoded) { ['date:2011-01-01'] }
10
+
11
+ it 'does decode to string with "date:"" prefix' do
12
+ JobAttributesAdapter.new(attributes_encoded).decode.should eq attributes
13
+ end
14
+
15
+ it 'does encode to string with "date:"" prefix' do
16
+ JobAttributesAdapter.new(attributes).encode.should eq attributes_encoded
17
+ end
18
+ end
19
+
20
+ context 'date range' do
21
+ let(:attributes) { [Date.parse('1st Jan 2011')..Date.parse('1st Feb 2011')] }
22
+ let(:attributes_encoded) { ['range:[date:2011-01-01,date:2011-02-01]'] }
23
+
24
+ it 'does decode to string with "range:" prefix and encode its boundary dates' do
25
+ JobAttributesAdapter.new(attributes_encoded).decode.should eq attributes
26
+ end
27
+
28
+ it 'does encode to string with "range:" prefix and encode its boundary dates' do
29
+ JobAttributesAdapter.new(attributes).encode.should eq attributes_encoded
30
+ end
31
+ end
32
+
33
+ context 'integers range' do
34
+ let(:attributes) { [1..10] }
35
+ let(:attributes_encoded) { ['range:[1,10]'] }
36
+
37
+ it 'does decode to string with prefix range:' do
38
+ JobAttributesAdapter.new(attributes_encoded).decode.should eq attributes
39
+ end
40
+
41
+ it 'does encode to string with prefix range:' do
42
+ JobAttributesAdapter.new(attributes).encode.should eq attributes_encoded
43
+ end
44
+ end
45
+
46
+ context 'integer' do
47
+ let(:attributes) { [12345] }
48
+ let(:attributes_encoded) { [12345] }
49
+
50
+ it 'is left decodehout any tranformations' do
51
+ JobAttributesAdapter.new(attributes_encoded).decode.should eq attributes
52
+ end
53
+
54
+ it 'does encode to string with prefix range:' do
55
+ JobAttributesAdapter.new(attributes).encode.should eq attributes_encoded
56
+ end
57
+ end
58
+
59
+ context 'hash' do
60
+ let(:attributes) { [{:foo => 'bar'}] }
61
+ let(:attributes_encoded) { [{'foo' => 'bar'}] }
62
+
63
+ it 'is left decodehout any tranformations' do
64
+ JobAttributesAdapter.new(attributes_encoded).decode.should eq attributes
65
+ end
66
+
67
+ it 'does encode to string with prefix range:' do
68
+ JobAttributesAdapter.new(attributes).encode.should eq attributes_encoded
69
+ end
70
+ end
71
+
72
+ context 'integer, hash and range' do
73
+ let(:attributes) { [12345, {:foo => 'bar'}, 10..1000] }
74
+ let(:attributes_encoded) { [12345, {'foo'=>"bar"}, "range:[10,1000]"] }
75
+
76
+ it 'does decode all together appropriately to their type' do
77
+ JobAttributesAdapter.new(attributes_encoded).decode.should eq attributes
78
+ end
79
+
80
+ it 'does encode to string with prefix range:' do
81
+ JobAttributesAdapter.new(attributes).encode.should eq attributes_encoded
82
+ end
83
+ end
84
+
85
+ context 'unknown group id bug' do
86
+ let(:attributes) {
87
+ [
88
+ "624ea403ad028e03045ba34c923bce0b2a812099bcd815bb1ddf2d92d8aaa560",
89
+ {
90
+ :group_id => 627,
91
+ :date_range => [Date.parse("2016-12-31")],
92
+ :week_schedule => {
93
+ :monday => 0,
94
+ :tuesday => 1,
95
+ :wednesday => 2,
96
+ :thursday => 3,
97
+ :friday => 2,
98
+ :saturday => 0,
99
+ :sunday => 0
100
+ }
101
+ }
102
+ ]
103
+ }
104
+
105
+ let(:attributes_encoded) {
106
+ [
107
+ "624ea403ad028e03045ba34c923bce0b2a812099bcd815bb1ddf2d92d8aaa560",
108
+ {
109
+ "group_id"=>627,
110
+ "date_range"=>["date:2016-12-31"],
111
+ "week_schedule"=>{"monday"=>0, "tuesday"=>1, "wednesday"=>2, "thursday"=>3, "friday"=>2, "saturday"=>0, "sunday"=>0}
112
+ }
113
+ ]
114
+ }
115
+
116
+ it 'does encode well' do
117
+ JobAttributesAdapter.new(attributes).encode.should eq attributes_encoded
118
+ end
119
+
120
+ it 'does decode well' do
121
+ decoded = JobAttributesAdapter.new(attributes_encoded).decode
122
+
123
+ decoded.should eq attributes
124
+ decoded.last[:group_id].should == 627
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end