chore-core 1.5.2
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 +15 -0
- data/LICENSE.txt +20 -0
- data/README.md +260 -0
- data/Rakefile +32 -0
- data/bin/chore +34 -0
- data/chore-core.gemspec +46 -0
- data/lib/chore/cli.rb +232 -0
- data/lib/chore/configuration.rb +13 -0
- data/lib/chore/consumer.rb +52 -0
- data/lib/chore/duplicate_detector.rb +56 -0
- data/lib/chore/fetcher.rb +31 -0
- data/lib/chore/hooks.rb +25 -0
- data/lib/chore/job.rb +103 -0
- data/lib/chore/json_encoder.rb +18 -0
- data/lib/chore/manager.rb +47 -0
- data/lib/chore/publisher.rb +29 -0
- data/lib/chore/queues/filesystem/consumer.rb +128 -0
- data/lib/chore/queues/filesystem/filesystem_queue.rb +49 -0
- data/lib/chore/queues/filesystem/publisher.rb +45 -0
- data/lib/chore/queues/sqs/consumer.rb +121 -0
- data/lib/chore/queues/sqs/publisher.rb +55 -0
- data/lib/chore/queues/sqs.rb +38 -0
- data/lib/chore/railtie.rb +18 -0
- data/lib/chore/signal.rb +175 -0
- data/lib/chore/strategies/consumer/batcher.rb +76 -0
- data/lib/chore/strategies/consumer/single_consumer_strategy.rb +34 -0
- data/lib/chore/strategies/consumer/threaded_consumer_strategy.rb +81 -0
- data/lib/chore/strategies/worker/forked_worker_strategy.rb +221 -0
- data/lib/chore/strategies/worker/single_worker_strategy.rb +39 -0
- data/lib/chore/tasks/queues.task +11 -0
- data/lib/chore/unit_of_work.rb +17 -0
- data/lib/chore/util.rb +18 -0
- data/lib/chore/version.rb +9 -0
- data/lib/chore/worker.rb +117 -0
- data/lib/chore-core.rb +1 -0
- data/lib/chore.rb +218 -0
- data/spec/chore/cli_spec.rb +182 -0
- data/spec/chore/consumer_spec.rb +36 -0
- data/spec/chore/duplicate_detector_spec.rb +62 -0
- data/spec/chore/fetcher_spec.rb +38 -0
- data/spec/chore/hooks_spec.rb +44 -0
- data/spec/chore/job_spec.rb +80 -0
- data/spec/chore/json_encoder_spec.rb +11 -0
- data/spec/chore/manager_spec.rb +39 -0
- data/spec/chore/queues/filesystem/filesystem_consumer_spec.rb +71 -0
- data/spec/chore/queues/sqs/consumer_spec.rb +136 -0
- data/spec/chore/queues/sqs/publisher_spec.rb +74 -0
- data/spec/chore/queues/sqs_spec.rb +37 -0
- data/spec/chore/signal_spec.rb +244 -0
- data/spec/chore/strategies/consumer/batcher_spec.rb +93 -0
- data/spec/chore/strategies/consumer/single_consumer_strategy_spec.rb +23 -0
- data/spec/chore/strategies/consumer/threaded_consumer_strategy_spec.rb +105 -0
- data/spec/chore/strategies/worker/forked_worker_strategy_spec.rb +281 -0
- data/spec/chore/strategies/worker/single_worker_strategy_spec.rb +36 -0
- data/spec/chore/worker_spec.rb +134 -0
- data/spec/chore_spec.rb +108 -0
- data/spec/spec_helper.rb +58 -0
- data/spec/test_job.rb +7 -0
- metadata +194 -0
@@ -0,0 +1,182 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class TestJob2
|
4
|
+
include Chore::Job
|
5
|
+
end
|
6
|
+
|
7
|
+
describe Chore::CLI do
|
8
|
+
let(:cli) { Chore::CLI.send(:new) }
|
9
|
+
|
10
|
+
describe ".register_option" do
|
11
|
+
let(:cli) { Chore::CLI.instance }
|
12
|
+
|
13
|
+
it 'should allow configuration options to be registered externally' do
|
14
|
+
args = ['some','args']
|
15
|
+
Chore::CLI.register_option('option_name',*args)
|
16
|
+
cli.registered_opts['option_name'].should == {:args => args}
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should allow configuration options to come from a file' do
|
20
|
+
file = StringIO.new("--key-name=some_value")
|
21
|
+
File.stub(:read).and_return(file.read)
|
22
|
+
|
23
|
+
args = ['-k', '--key-name SOME_VALUE', "Some description"]
|
24
|
+
Chore::CLI.register_option "key_name", *args
|
25
|
+
options = cli.parse_config_file(file)
|
26
|
+
cli.registered_opts['key_name'].should == {:args => args}
|
27
|
+
options[:key_name].should == 'some_value'
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should handle ERB tags in a config file' do
|
31
|
+
file = StringIO.new("--key-name=<%= 'erb_inserted_value' %>\n--other-key=<%= 'second_val' %>")
|
32
|
+
File.stub(:read).and_return(file.read)
|
33
|
+
|
34
|
+
Chore::CLI.register_option "key_name", '-k', '--key-name SOME_VALUE', "Some description"
|
35
|
+
Chore::CLI.register_option "other_key", '-o', '--other-key SOME_VALUE', "Some description"
|
36
|
+
options = cli.parse_config_file(file)
|
37
|
+
options[:key_name].should == 'erb_inserted_value'
|
38
|
+
options[:other_key].should == 'second_val'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'queue mananagement' do
|
43
|
+
before(:each) do
|
44
|
+
TestJob.queue_options :name => 'test_queue', :publisher => Chore::Publisher
|
45
|
+
TestJob2.queue_options :name => 'test2', :publisher => Chore::Publisher
|
46
|
+
cli.send(:options).delete(:queues)
|
47
|
+
cli.stub(:validate!)
|
48
|
+
cli.stub(:boot_system)
|
49
|
+
end
|
50
|
+
|
51
|
+
after :all do
|
52
|
+
#Removing the prefix due to spec idempotency issues in job spec
|
53
|
+
Chore.config.queue_prefix = nil
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should detect queues based on included jobs' do
|
57
|
+
cli.parse([])
|
58
|
+
Chore.config.queues.should include('test_queue')
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should honor --except when processing all queues' do
|
62
|
+
cli.parse(['--except=test_queue'])
|
63
|
+
Chore.config.queues.should_not include('test_queue')
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should honor --queue-prefix when processing all queues' do
|
67
|
+
cli.parse(['--queue-prefix=prefixey'])
|
68
|
+
Chore.config.queues.should include('prefixey_test2')
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'when provided duplicate queues' do
|
72
|
+
let(:queue_options) {['--queues=test2,test2']}
|
73
|
+
before :each do
|
74
|
+
cli.parse(queue_options)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should not have duplicate queues' do
|
78
|
+
Chore.config.queues.count.should == 1
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'when both --queue_prefix and --queues have been provided' do
|
83
|
+
let(:queue_options) {['--queue-prefix=prefixy', '--queues=test2']}
|
84
|
+
before :each do
|
85
|
+
cli.parse(queue_options)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'should honor --queue_prefix' do
|
89
|
+
total_queues = Chore.config.queues.count
|
90
|
+
prefixed_queues = Chore.config.queues.count {|item| item.start_with?("prefixy_")}
|
91
|
+
prefixed_queues.should == total_queues
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'should prefix the names of the specified queues' do
|
95
|
+
Chore.config.queues.should include('prefixy_test2')
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should not prefix the names of queues that were not specified' do
|
99
|
+
Chore.config.queues.should_not include('prefixy_test_queue')
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'should not have a queue without the prefix' do
|
103
|
+
Chore.config.queues.should_not include('test2')
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should not have a queue that was not specified' do
|
107
|
+
Chore.config.queues.should_not include('test_queue')
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should raise an exception if both --queues and --except are specified' do
|
112
|
+
expect { cli.parse(['--except=something','--queues=something,else']) }.to raise_error(ArgumentError)
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'should raise an exception if no queues are found' do
|
116
|
+
Chore::Job.job_classes.clear
|
117
|
+
expect { cli.parse([]) }.to raise_error(ArgumentError)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "#parse" do
|
122
|
+
let(:cli) do
|
123
|
+
Chore::CLI.send(:new).tap do |cli|
|
124
|
+
cli.send(:options).clear
|
125
|
+
cli.stub(:validate!)
|
126
|
+
cli.stub(:boot_system)
|
127
|
+
cli.stub(:detect_queues)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
let(:config) { cli.parse(command); Chore.config }
|
132
|
+
|
133
|
+
context "--consumer-strategy" do
|
134
|
+
let(:command) { ["--consumer-strategy=Chore::Strategy::SingleConsumerStrategy"] }
|
135
|
+
|
136
|
+
it "should set the consumer class" do
|
137
|
+
config.consumer_strategy.should == Chore::Strategy::SingleConsumerStrategy
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe '--shutdown-timeout' do
|
142
|
+
let(:command) { ["--shutdown-timeout=#{amount}"] }
|
143
|
+
subject { config.shutdown_timeout }
|
144
|
+
|
145
|
+
context 'given a numeric value' do
|
146
|
+
let(:amount) { '10.0' }
|
147
|
+
|
148
|
+
it 'is that amount' do
|
149
|
+
subject.should == amount.to_f
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
context 'given no value' do
|
154
|
+
let(:command) { [] }
|
155
|
+
it 'is the default value, 120 seconds' do
|
156
|
+
subject.should == 120.0
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
describe '--max-attempts' do
|
162
|
+
let(:command) { ["--max-attempts=#{amount}"] }
|
163
|
+
subject { config.max_attempts }
|
164
|
+
|
165
|
+
context 'given a numeric value' do
|
166
|
+
let(:amount) { '10' }
|
167
|
+
|
168
|
+
it 'is that amount' do
|
169
|
+
subject.should == amount.to_i
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context 'given no value' do
|
174
|
+
let(:command) { [] }
|
175
|
+
it 'is the default value, infinity' do
|
176
|
+
subject.should == 1.0 / 0.0
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Chore::Consumer do
|
4
|
+
let(:queue) { "test" }
|
5
|
+
let(:options) { {} }
|
6
|
+
let(:consumer) { Chore::Consumer.new(queue) }
|
7
|
+
let(:message) { "message" }
|
8
|
+
|
9
|
+
it 'should have a consume method' do
|
10
|
+
consumer.should respond_to :consume
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should have a reject method' do
|
14
|
+
consumer.should respond_to :reject
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should have a complete method' do
|
18
|
+
consumer.should respond_to :complete
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should have a class level reset_connection method' do
|
22
|
+
Chore::Consumer.should respond_to :reset_connection!
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should not have an implemented consume method' do
|
26
|
+
expect { consumer.consume }.to raise_error(NotImplementedError)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should not have an implemented reject method' do
|
30
|
+
expect { consumer.reject(message) }.to raise_error(NotImplementedError)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should not have an implemented complete method' do
|
34
|
+
expect { consumer.complete(message) }.to raise_error(NotImplementedError)
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
describe Chore::DuplicateDetector do
|
5
|
+
let(:memcache) { double("memcache") }
|
6
|
+
let(:dupe_on_cache_failure) { false }
|
7
|
+
let(:dedupe_params) { { :memcached_client => memcache, :dupe_on_cache_failure => dupe_on_cache_failure } }
|
8
|
+
let(:dedupe) { Chore::DuplicateDetector.new(dedupe_params)}
|
9
|
+
let(:message) { double('message') }
|
10
|
+
let(:timeout) { 2 }
|
11
|
+
let(:queue_url) {"queue://bogus/url"}
|
12
|
+
let(:queue) { (q = double('queue')).stub(:visibility_timeout).and_return(timeout); q.stub(:url).and_return(queue_url); q }
|
13
|
+
let(:id) { SecureRandom.uuid }
|
14
|
+
|
15
|
+
before(:each) do
|
16
|
+
message.stub(:id).and_return(id)
|
17
|
+
message.stub(:queue).and_return(queue)
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#found_duplicate" do
|
21
|
+
it 'should not return true if the message has not already been seen' do
|
22
|
+
memcache.should_receive(:add).and_return(true)
|
23
|
+
dedupe.found_duplicate?(message).should_not be_true
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should return true if the message has already been seen' do
|
27
|
+
memcache.should_receive(:add).and_return(false)
|
28
|
+
dedupe.found_duplicate?(message).should be_true
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should return false if given an invalid message' do
|
32
|
+
dedupe.found_duplicate?(double()).should be_false
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should return false when identity store errors" do
|
36
|
+
memcache.should_receive(:add).and_raise("no")
|
37
|
+
dedupe.found_duplicate?(message).should be_false
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should set the timeout to be the queue's " do
|
41
|
+
memcache.should_receive(:add).with(id,"1",timeout).and_return(true)
|
42
|
+
dedupe.found_duplicate?(message).should be_false
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should call #visibility_timeout once and only once" do
|
46
|
+
queue.should_receive(:visibility_timeout).once
|
47
|
+
memcache.should_receive(:add).at_least(3).times.and_return(true)
|
48
|
+
3.times { dedupe.found_duplicate?(message) }
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'when a memecached connection error occurs' do
|
52
|
+
context 'and when Chore.config.dedupe_strategy is set to :strict' do
|
53
|
+
let(:dupe_on_cache_failure) { true }
|
54
|
+
|
55
|
+
it "returns true" do
|
56
|
+
memcache.should_receive(:add).and_raise
|
57
|
+
dedupe.found_duplicate?(message).should be_true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class TestConsumer < Chore::Consumer
|
4
|
+
def initialize(queue_name, opts={})
|
5
|
+
end
|
6
|
+
|
7
|
+
def consume
|
8
|
+
# just something that looks like an SQS message
|
9
|
+
msg = OpenStruct.new( :id => 1, :body => "test" )
|
10
|
+
yield msg if block_given?
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe Chore::Fetcher do
|
15
|
+
let(:manager) { double("manager") }
|
16
|
+
let(:consumer) { TestConsumer }
|
17
|
+
let(:fetcher) { Chore::Fetcher.new(manager) }
|
18
|
+
|
19
|
+
before(:each) do
|
20
|
+
Chore.configure do |c|
|
21
|
+
c.queues = ['test']
|
22
|
+
c.consumer = consumer
|
23
|
+
c.batch_size = 1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
it "should have a start function" do
|
29
|
+
fetcher.should respond_to :start
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "fetching messages" do
|
33
|
+
it "should assign its message" do
|
34
|
+
manager.should_receive(:assign)
|
35
|
+
fetcher.start
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
class TestHooks
|
4
|
+
include Chore::Hooks
|
5
|
+
end
|
6
|
+
|
7
|
+
describe Chore::Hooks do
|
8
|
+
let(:test_instance){ TestHooks.new }
|
9
|
+
it 'should respond_to run_hooks_for' do
|
10
|
+
test_instance.should respond_to(:run_hooks_for)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should call a defined hook' do
|
14
|
+
test_instance.should_receive(:before_perform_test).and_return(true)
|
15
|
+
test_instance.run_hooks_for(:before_perform)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should call multiple defined hooks' do
|
19
|
+
3.times do |i|
|
20
|
+
test_instance.should_receive(:"before_perform_test#{i}").and_return(true)
|
21
|
+
end
|
22
|
+
test_instance.run_hooks_for(:before_perform)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should bubble up raised exceptions' do
|
26
|
+
test_instance.should_receive(:"before_perform_raise").and_raise(RuntimeError)
|
27
|
+
expect { test_instance.run_hooks_for(:before_perform) }.to raise_error(RuntimeError)
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'global hooks' do
|
31
|
+
before(:each) do
|
32
|
+
Chore.clear_hooks!
|
33
|
+
Chore.add_hook(:test_hook,&callback)
|
34
|
+
end
|
35
|
+
|
36
|
+
let(:callback) { proc { true } }
|
37
|
+
|
38
|
+
it 'should call a global hook' do
|
39
|
+
callback.should_receive(:call).once
|
40
|
+
test_instance.run_hooks_for(:test_hook)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Chore::Job do
|
4
|
+
let(:args) { [1,2, { :a => :hash }] }
|
5
|
+
let(:config) { { :name => 'test_queue', :publisher => Chore::Publisher } }
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
TestJob.queue_options config
|
9
|
+
end
|
10
|
+
|
11
|
+
after(:each) do
|
12
|
+
TestJob.queue_options config
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should have an perform_async method' do
|
16
|
+
TestJob.should respond_to :perform_async
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should have a perform method' do
|
20
|
+
TestJob.should respond_to :perform
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should require a queue when configuring' do
|
24
|
+
expect { TestJob.queue_options(:name => nil) }.to raise_error(ArgumentError)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should require a publisher when configuring' do
|
28
|
+
expect { TestJob.queue_options(:publisher => nil) }.to raise_error(ArgumentError)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should take params via perform' do
|
32
|
+
TestJob.any_instance.should_receive(:perform).with(*args)
|
33
|
+
TestJob.perform(*args)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should store class level configuration' do
|
37
|
+
TestJob.queue_options(:name => 'test_queue')
|
38
|
+
TestJob.options[:name].should == 'test_queue'
|
39
|
+
end
|
40
|
+
|
41
|
+
describe(:perform_async) do
|
42
|
+
it 'should call an instance of the queue_options publisher' do
|
43
|
+
args = [1,2,{:h => 'ash'}]
|
44
|
+
TestJob.queue_options(:publisher => Chore::Publisher)
|
45
|
+
Chore::Publisher.any_instance.should_receive(:publish).with('test_queue',{:class => 'TestJob',:args => args}).and_return(true)
|
46
|
+
TestJob.perform_async(*args)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe 'publisher configured via Chore.configure' do
|
51
|
+
before do
|
52
|
+
Chore.configure do |c|
|
53
|
+
c.publisher = Chore::Publisher
|
54
|
+
end
|
55
|
+
|
56
|
+
class NoPublisherJob
|
57
|
+
include Chore::Job
|
58
|
+
queue_options :name => "test_queue"
|
59
|
+
|
60
|
+
def perform
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should have the default publisher' do
|
66
|
+
NoPublisherJob.options[:publisher].should == Chore::Publisher
|
67
|
+
end
|
68
|
+
|
69
|
+
describe 'global publisher can be overridden' do
|
70
|
+
before do
|
71
|
+
TestJob.queue_options config.merge(:publisher => FakePublisher)
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'should override publisher' do
|
75
|
+
TestJob.options[:publisher].should == FakePublisher
|
76
|
+
TestJob.options[:publisher].should_not == Chore::Publisher
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Chore::JsonEncoder do
|
4
|
+
it 'should have an encode method' do
|
5
|
+
subject.should respond_to :encode
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'should have a decode method' do
|
9
|
+
subject.should respond_to :decode
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
describe Chore::Manager do
|
5
|
+
|
6
|
+
let(:fetcher) { mock(:start => nil) }
|
7
|
+
let(:opts) { { :num_workers => 4, :other_opt => 'hi', :fetcher => fetcher } }
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
Chore.configure {|c| c.fetcher = fetcher; c.worker_strategy = Chore::Strategy::SingleWorkerStrategy }
|
11
|
+
fetcher.should_receive(:new).and_return(fetcher)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should call create an instance of the defined fetcher' do
|
15
|
+
manager = Chore::Manager.new
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'running the manager' do
|
19
|
+
|
20
|
+
let(:manager) { Chore::Manager.new}
|
21
|
+
let(:work) { Chore::UnitOfWork.new(Chore::JsonEncoder.encode({:class => 'MyClass',:args => []}),mock()) }
|
22
|
+
|
23
|
+
it 'should start the fetcher when starting the manager' do
|
24
|
+
fetcher.should_receive(:start)
|
25
|
+
manager.start
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'assigning messages' do
|
29
|
+
it 'should create a worker if one is available' do
|
30
|
+
worker = mock()
|
31
|
+
Chore::Worker.should_receive(:new).with(work).and_return(worker)
|
32
|
+
worker.should_receive(:start).with()
|
33
|
+
manager.assign(work)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# This test is actually testing both the publisher and the consumer behavior but what we
|
4
|
+
# really want to validate is that they can pass messages off to each other. Hard coding in
|
5
|
+
# the behavior of each in two separate tests was becoming a mess and would be hard to maintain.
|
6
|
+
describe Chore::Queues::Filesystem::Consumer do
|
7
|
+
let(:consumer) { Chore::Queues::Filesystem::Consumer.new(test_queue) }
|
8
|
+
let(:publisher) { Chore::Queues::Filesystem::Publisher.new }
|
9
|
+
let(:test_queues_dir) { "test-queues" }
|
10
|
+
let(:test_queue) { "test-queue" }
|
11
|
+
|
12
|
+
before do
|
13
|
+
Chore.config.fs_queue_root = test_queues_dir
|
14
|
+
Chore.config.stub(:default_queue_timeout).and_return(60)
|
15
|
+
consumer.stub(:sleep)
|
16
|
+
end
|
17
|
+
|
18
|
+
after do
|
19
|
+
FileUtils.rm_rf(test_queues_dir)
|
20
|
+
end
|
21
|
+
|
22
|
+
let!(:consumer_run_for_one_message) { consumer.stub(:running?).and_return(true, false) }
|
23
|
+
let(:test_job_hash) {{:class => "TestClass", :args => "test-args"}}
|
24
|
+
|
25
|
+
context "founding a published job" do
|
26
|
+
before do
|
27
|
+
publisher.publish(test_queue, test_job_hash)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should consume a published job and yield the job to the handler block" do
|
31
|
+
expect { |b| consumer.consume(&b) }.to yield_with_args(anything, 'test-queue', 60, test_job_hash.to_json, 0)
|
32
|
+
end
|
33
|
+
|
34
|
+
context "rejecting a job" do
|
35
|
+
let!(:consumer_run_for_two_messages) { consumer.stub(:running?).and_return(true, false,true,false) }
|
36
|
+
|
37
|
+
it "should requeue a job that gets rejected" do
|
38
|
+
rejected = false
|
39
|
+
consumer.consume do |job_id, queue_name, job_hash|
|
40
|
+
consumer.reject(job_id)
|
41
|
+
rejected = true
|
42
|
+
end
|
43
|
+
rejected.should be_true
|
44
|
+
|
45
|
+
expect { |b| consumer.consume(&b) }.to yield_with_args(anything, 'test-queue', 60, test_job_hash.to_json, 1)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "completing a job" do
|
50
|
+
let!(:consumer_run_for_two_messages) { consumer.stub(:running?).and_return(true, false,true,false) }
|
51
|
+
|
52
|
+
it "should remove job on completion" do
|
53
|
+
completed = false
|
54
|
+
consumer.consume do |job_id, queue_name, job_hash|
|
55
|
+
consumer.complete(job_id)
|
56
|
+
completed = true
|
57
|
+
end
|
58
|
+
completed.should be_true
|
59
|
+
|
60
|
+
expect { |b| consumer.consume(&b) }.to_not yield_control
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "not finding a published job" do
|
66
|
+
it "should consume a published job and yield the job to the handler block" do
|
67
|
+
expect { |b| consumer.consume(&b) }.to_not yield_control
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|