chore-core 1.10.0 → 4.0.0

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.
Files changed (60) hide show
  1. checksums.yaml +5 -13
  2. data/LICENSE.txt +1 -1
  3. data/README.md +172 -153
  4. data/chore-core.gemspec +3 -3
  5. data/lib/chore.rb +29 -5
  6. data/lib/chore/cli.rb +22 -4
  7. data/lib/chore/configuration.rb +1 -1
  8. data/lib/chore/consumer.rb +54 -12
  9. data/lib/chore/fetcher.rb +12 -7
  10. data/lib/chore/hooks.rb +2 -1
  11. data/lib/chore/job.rb +19 -0
  12. data/lib/chore/manager.rb +17 -2
  13. data/lib/chore/publisher.rb +18 -2
  14. data/lib/chore/queues/filesystem/consumer.rb +126 -64
  15. data/lib/chore/queues/filesystem/filesystem_queue.rb +19 -0
  16. data/lib/chore/queues/filesystem/publisher.rb +10 -16
  17. data/lib/chore/queues/sqs.rb +22 -13
  18. data/lib/chore/queues/sqs/consumer.rb +64 -51
  19. data/lib/chore/queues/sqs/publisher.rb +26 -17
  20. data/lib/chore/strategies/consumer/batcher.rb +6 -6
  21. data/lib/chore/strategies/consumer/single_consumer_strategy.rb +5 -5
  22. data/lib/chore/strategies/consumer/threaded_consumer_strategy.rb +7 -6
  23. data/lib/chore/strategies/consumer/throttled_consumer_strategy.rb +120 -0
  24. data/lib/chore/strategies/worker/forked_worker_strategy.rb +5 -6
  25. data/lib/chore/strategies/worker/helpers/ipc.rb +87 -0
  26. data/lib/chore/strategies/worker/helpers/preforked_worker.rb +163 -0
  27. data/lib/chore/strategies/worker/helpers/work_distributor.rb +65 -0
  28. data/lib/chore/strategies/worker/helpers/worker_info.rb +13 -0
  29. data/lib/chore/strategies/worker/helpers/worker_killer.rb +40 -0
  30. data/lib/chore/strategies/worker/helpers/worker_manager.rb +183 -0
  31. data/lib/chore/strategies/worker/preforked_worker_strategy.rb +150 -0
  32. data/lib/chore/unit_of_work.rb +2 -1
  33. data/lib/chore/util.rb +5 -1
  34. data/lib/chore/version.rb +2 -2
  35. data/lib/chore/worker.rb +30 -3
  36. data/spec/chore/cli_spec.rb +2 -2
  37. data/spec/chore/consumer_spec.rb +1 -5
  38. data/spec/chore/duplicate_detector_spec.rb +17 -5
  39. data/spec/chore/fetcher_spec.rb +0 -11
  40. data/spec/chore/manager_spec.rb +7 -0
  41. data/spec/chore/queues/filesystem/filesystem_consumer_spec.rb +74 -16
  42. data/spec/chore/queues/sqs/consumer_spec.rb +117 -78
  43. data/spec/chore/queues/sqs/publisher_spec.rb +49 -60
  44. data/spec/chore/queues/sqs_spec.rb +32 -41
  45. data/spec/chore/strategies/consumer/single_consumer_strategy_spec.rb +3 -3
  46. data/spec/chore/strategies/consumer/threaded_consumer_strategy_spec.rb +6 -6
  47. data/spec/chore/strategies/consumer/throttled_consumer_strategy_spec.rb +165 -0
  48. data/spec/chore/strategies/worker/forked_worker_strategy_spec.rb +6 -1
  49. data/spec/chore/strategies/worker/helpers/ipc_spec.rb +127 -0
  50. data/spec/chore/strategies/worker/helpers/preforked_worker_spec.rb +236 -0
  51. data/spec/chore/strategies/worker/helpers/work_distributor_spec.rb +131 -0
  52. data/spec/chore/strategies/worker/helpers/worker_info_spec.rb +14 -0
  53. data/spec/chore/strategies/worker/helpers/worker_killer_spec.rb +97 -0
  54. data/spec/chore/strategies/worker/helpers/worker_manager_spec.rb +304 -0
  55. data/spec/chore/strategies/worker/preforked_worker_strategy_spec.rb +183 -0
  56. data/spec/chore/strategies/worker/single_worker_strategy_spec.rb +1 -1
  57. data/spec/chore/worker_spec.rb +70 -15
  58. data/spec/spec_helper.rb +1 -1
  59. data/spec/support/queues/sqs/fake_objects.rb +18 -0
  60. metadata +53 -29
@@ -1,74 +1,63 @@
1
1
  require 'spec_helper'
2
2
 
3
- module Chore
4
- describe Queues::SQS::Publisher do
5
- let(:job) { {'class' => 'TestJob', 'args'=>[1,2,'3']}}
6
- let(:queue_name) { 'test_queue' }
7
- let(:queue_url) {"http://www.queue_url.com/test_queue"}
8
- let(:queue) { double('queue', :send_message => nil) }
9
- let(:sqs) do
10
- double('sqs', :queues => double('queues', :named => queue, :url_for => queue_url, :[] => queue))
11
- end
12
- let(:publisher) { Queues::SQS::Publisher.new }
13
- let(:pool) { double("pool") }
3
+ describe Chore::Queues::SQS::Publisher do
4
+ include_context 'fake objects'
14
5
 
15
- before(:each) do
16
- AWS::SQS.stub(:new).and_return(sqs)
17
- end
6
+ let(:publisher) { Chore::Queues::SQS::Publisher.new }
7
+ let(:job) { {'class' => 'TestJob', 'args'=>[1,2,'3']}}
8
+ let(:send_message_result) { double(Aws::SQS::Types::SendMessageResult, :data => job) }
18
9
 
19
- it 'should configure sqs' do
20
- Chore.config.stub(:aws_access_key).and_return('key')
21
- Chore.config.stub(:aws_secret_key).and_return('secret')
10
+ before(:each) do
11
+ allow(Aws::SQS::Client).to receive(:new).and_return(sqs)
12
+ allow(sqs).to receive(:send_message).and_return(send_message_result)
13
+ end
22
14
 
23
- AWS::SQS.should_receive(:new).with(
24
- :access_key_id => 'key',
25
- :secret_access_key => 'secret',
26
- :logger => Chore.logger,
27
- :log_level => :debug
28
- )
29
- publisher.publish(queue_name,job)
30
- end
15
+ it 'should configure sqs' do
16
+ expect(Aws::SQS::Client).to receive(:new)
17
+ publisher.publish(queue_name,job)
18
+ end
31
19
 
32
- it 'should create send an encoded message to the specified queue' do
33
- queue.should_receive(:send_message).with(job.to_json)
34
- publisher.publish(queue_name,job)
35
- end
20
+ it 'should not create a new SQS client before every publish' do
21
+ expect(Aws::SQS::Client).to receive(:new).once
22
+ 2.times { publisher.send(:queue, queue_name) }
23
+ end
36
24
 
37
- it 'should lookup the queue when publishing' do
38
- sqs.queues.should_receive(:url_for).with('test_queue')
39
- publisher.publish('test_queue', job)
40
- end
25
+ it 'should lookup the queue when publishing' do
26
+ expect(sqs).to receive(:get_queue_url).with(queue_name: queue_name)
27
+ publisher.publish(queue_name, job)
28
+ end
41
29
 
42
- it 'should lookup multiple queues if specified' do
43
- sqs.queues.should_receive(:url_for).with('test_queue')
44
- sqs.queues.should_receive(:url_for).with('test_queue2')
45
- publisher.publish('test_queue', job)
46
- publisher.publish('test_queue2', job)
47
- end
30
+ it 'should create send an encoded message to the specified queue' do
31
+ expect(sqs).to receive(:send_message).with(queue_url: queue_url, message_body: job.to_json)
32
+ publisher.publish(queue_name,job)
33
+ end
34
+
35
+ it 'should lookup multiple queues if specified' do
36
+ second_queue_name = queue_name + '2'
37
+ expect(sqs).to receive(:get_queue_url).with(queue_name: queue_name)
38
+ expect(sqs).to receive(:get_queue_url).with(queue_name: second_queue_name)
39
+
40
+ publisher.publish(queue_name, job)
41
+ publisher.publish(second_queue_name, job)
42
+ end
43
+
44
+ it 'should only lookup a named queue once' do
45
+ expect(sqs).to receive(:get_queue_url).with(queue_name: queue_name).once
46
+ 4.times { publisher.publish(queue_name, job) }
47
+ end
48
48
 
49
- it 'should only lookup a named queue once' do
50
- sqs.queues.should_receive(:url_for).with('test_queue').once
51
- 2.times { publisher.publish('test_queue', job) }
49
+ describe '#reset_connection!' do
50
+ it 'should empty API client connection pool after a call to reset_connection!' do
51
+ expect(Aws).to receive(:empty_connection_pools!)
52
+ Chore::Queues::SQS::Publisher.reset_connection!
53
+ publisher.send(:queue, queue_name)
52
54
  end
53
55
 
54
- describe '#reset_connection!' do
55
- it 'should reset the connection after a call to reset_connection!' do
56
- AWS::Core::Http::ConnectionPool.stub(:pools).and_return([pool])
57
- pool.should_receive(:empty!)
58
- Chore::Queues::SQS::Publisher.reset_connection!
59
- publisher.queue(queue_name)
60
- end
61
-
62
- it 'should not reset the connection between calls' do
63
- sqs = publisher.queue(queue_name)
64
- sqs.should be publisher.queue(queue_name)
65
- end
66
-
67
- it 'should reconfigure sqs' do
68
- Chore::Queues::SQS::Publisher.reset_connection!
69
- AWS::SQS.should_receive(:new)
70
- publisher.queue(queue_name)
71
- end
56
+ # TODO: this test seems like basic identity (i.e. not even a real test)
57
+ it 'should not reset the connection between calls' do
58
+ expect(Aws).to receive(:empty_connection_pools!).once
59
+ Chore::Queues::SQS::Publisher.reset_connection!
60
+ 4.times { publisher.send(:queue, queue_name ) }
72
61
  end
73
62
  end
74
63
  end
@@ -1,53 +1,44 @@
1
1
  require 'spec_helper'
2
2
 
3
- module Chore
4
- describe Queues::SQS do
5
- context "when managing queues" do
6
- let(:fake_sqs) {double(Object)}
7
- let(:fake_queue_collection) {double(Object)}
8
- let(:queue_name) {"test"}
9
- let(:queue_url) {"http://amazon.sqs.url/queues/#{queue_name}"}
10
- let(:fake_queue) {double(Object)}
11
-
12
- before(:each) do
13
- allow(AWS::SQS).to receive(:new).and_return(fake_sqs)
14
- allow(Chore).to receive(:prefixed_queue_names).and_return([queue_name])
15
- allow(fake_queue).to receive(:delete)
3
+ describe Chore::Queues::SQS do
4
+ include_context 'fake objects'
5
+
6
+ context "when managing queues" do
7
+ before(:each) do
8
+ allow(Aws::SQS::Client).to receive(:new).and_return(sqs)
9
+ allow(sqs).to receive(:create_queue).and_return(queue)
10
+ allow(sqs).to receive(:delete_queue).and_return(Struct.new(nil))
11
+ allow(queue).to receive(:delete).and_return(sqs.delete_queue(queue))
12
+ allow(Chore).to receive(:prefixed_queue_names).and_return([queue_name])
13
+ allow(queue).to receive(:delete)
14
+ end
16
15
 
17
- allow(fake_queue_collection).to receive(:[]).and_return(fake_queue)
18
- allow(fake_queue_collection).to receive(:create)
19
- allow(fake_queue_collection).to receive(:url_for).with(queue_name).and_return(queue_url)
16
+ it 'should create queues that are defined in its internal job name list' do
17
+ #Only one job defined in the spec suite
18
+ expect(sqs).to receive(:create_queue).with(queue_name: queue_name)
19
+ Chore::Queues::SQS.create_queues!
20
+ end
20
21
 
21
- allow(fake_sqs).to receive(:queues).and_return(fake_queue_collection)
22
- end
22
+ it 'should delete queues that are defined in its internal job name list' do
23
+ #Only one job defined in the spec suite
24
+ expect(sqs).to receive(:delete_queue).with(queue_url: sqs.get_queue_url.queue_url)
25
+ Chore::Queues::SQS.delete_queues!
26
+ end
23
27
 
24
- it 'should create queues that are defined in its internal job name list' do
25
- #Only one job defined in the spec suite
26
- expect(fake_queue_collection).to receive(:create)
27
- Chore::Queues::SQS.create_queues!
28
+ context 'and checking for existing queues' do
29
+ it 'checks for existing queues' do
30
+ expect(described_class).to receive(:existing_queues).and_return([])
31
+ Chore::Queues::SQS.create_queues!(true)
28
32
  end
29
33
 
30
- it 'should delete queues that are defined in its internal job name list' do
31
- #Only one job defined in the spec suite
32
- expect(fake_queue).to receive(:delete)
33
- Chore::Queues::SQS.delete_queues!
34
+ it 'raises an error if a queue does exist' do
35
+ allow(described_class).to receive(:existing_queues).and_return([queue_name])
36
+ expect{Chore::Queues::SQS.create_queues!(true)}.to raise_error(RuntimeError)
34
37
  end
35
38
 
36
- context 'and checking for existing queues' do
37
- it 'checks for existing queues' do
38
- expect(described_class).to receive(:existing_queues).and_return([])
39
- Chore::Queues::SQS.create_queues!(true)
40
- end
41
-
42
- it 'raises an error if a queue does exist' do
43
- allow(described_class).to receive(:existing_queues).and_return([queue_name])
44
- expect{Chore::Queues::SQS.create_queues!(true)}.to raise_error(RuntimeError)
45
- end
46
-
47
- it 'does not raise an error if a queue does not exist' do
48
- allow(described_class).to receive(:existing_queues).and_return([])
49
- expect{Chore::Queues::SQS.create_queues!(true)}.not_to raise_error
50
- end
39
+ it 'does not raise an error if a queue does not exist' do
40
+ allow(described_class).to receive(:existing_queues).and_return([])
41
+ expect{Chore::Queues::SQS.create_queues!(true)}.not_to raise_error
51
42
  end
52
43
  end
53
44
  end
@@ -11,13 +11,13 @@ describe Chore::Strategy::SingleConsumerStrategy do
11
11
  fetcher.stub(:manager) { manager }
12
12
  Chore.config.stub(:queues).and_return(test_queues)
13
13
  Chore.config.stub(:consumer).and_return(consumer)
14
-
14
+
15
15
  end
16
16
 
17
17
  it "should consume and then assign a message" do
18
18
  consumer.should_receive(:new).with(test_queues.first).and_return(consumer)
19
- consumer.should_receive(:consume).and_yield(1, 'test-queue', 60, "test", 0)
20
- manager.should_receive(:assign).with(Chore::UnitOfWork.new(1, 'test-queue', 60, "test", 0, consumer))
19
+ consumer.should_receive(:consume).and_yield(1, nil, 'test-queue', 60, "test", 0)
20
+ manager.should_receive(:assign).with(Chore::UnitOfWork.new(1, nil, 'test-queue', 60, "test", 0, consumer))
21
21
  strategy.fetch
22
22
  end
23
23
  end
@@ -29,8 +29,8 @@ describe Chore::Strategy::ThreadedConsumerStrategy do
29
29
  before(:each) do
30
30
  fetcher.stub(:consumers) { [consumer] }
31
31
  fetcher.stub(:manager) { manager }
32
- Chore.configure do |c|
33
- c.queues = ['test']
32
+ Chore.configure do |c|
33
+ c.queues = ['test']
34
34
  c.consumer = consumer
35
35
  c.batch_size = batch_size
36
36
  end
@@ -40,7 +40,7 @@ describe Chore::Strategy::ThreadedConsumerStrategy do
40
40
  let(:batch_size) { 2 }
41
41
 
42
42
  it "should queue but not assign the message" do
43
- consumer.any_instance.should_receive(:consume).and_yield(1, 'test-queue', 60, "test", 0)
43
+ consumer.any_instance.should_receive(:consume).and_yield(1, nil, 'test-queue', 60, "test", 0)
44
44
  strategy.fetch
45
45
  strategy.batcher.batch.size.should == 1
46
46
 
@@ -60,7 +60,7 @@ describe Chore::Strategy::ThreadedConsumerStrategy do
60
60
 
61
61
  it "should assign the batch" do
62
62
  manager.should_receive(:assign)
63
- consumer.any_instance.should_receive(:consume).and_yield(1, 'test-queue', 60, "test", 0)
63
+ consumer.any_instance.should_receive(:consume).and_yield(1, nil, 'test-queue', 60, "test", 0)
64
64
  strategy.fetch
65
65
  strategy.batcher.batch.size.should == 0
66
66
  end
@@ -90,8 +90,8 @@ describe Chore::Strategy::ThreadedConsumerStrategy do
90
90
 
91
91
  before do
92
92
  fetcher.stub(:consumers) { [bad_consumer] }
93
- Chore.configure do |c|
94
- c.queues = ['test']
93
+ Chore.configure do |c|
94
+ c.queues = ['test']
95
95
  c.consumer = bad_consumer
96
96
  c.batch_size = batch_size
97
97
  c.threads_per_queue = 1
@@ -0,0 +1,165 @@
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
+ class NoQueueConsumer < Chore::Consumer
15
+ def initialize(queue_name, opts={})
16
+ raise Chore::TerribleMistake
17
+ end
18
+
19
+ def consume
20
+ end
21
+ end
22
+
23
+ describe Chore::Strategy::ThrottledConsumerStrategy do
24
+ let(:fetcher) { double("fetcher") }
25
+ let(:manager) { double("manager") }
26
+ let(:thread) {double("thread")}
27
+ let(:consume_queue) { "TestQueue" }
28
+ let(:consumer) { TestConsumer }
29
+ let(:consumer_object) { consumer.new(consume_queue) }
30
+ let(:strategy) { Chore::Strategy::ThrottledConsumerStrategy.new(fetcher) }
31
+ let(:config) { double("config") }
32
+ let(:sized_queue) {double("sized_queue")}
33
+ let(:return_queue) {double("return_queue")}
34
+ let(:work) { double("work") }
35
+ let(:msg) { OpenStruct.new( :id => 1, :body => "test" ) }
36
+
37
+
38
+ before(:each) do
39
+ allow(fetcher).to receive(:consumers).and_return([consumer])
40
+ allow(fetcher).to receive(:manager).and_return(manager)
41
+ Chore.configure do |c|
42
+ c.queues = [consume_queue]
43
+ c.consumer = consumer
44
+ end
45
+ end
46
+
47
+ context '#fetch' do
48
+ it 'should call consume, \'@number_of_consumers\' number of times' do
49
+ allow(strategy).to receive(:consume).and_return(thread)
50
+ allow(thread).to receive(:join).and_return(true)
51
+ allow(Chore).to receive(:config).and_return(config)
52
+ allow(config).to receive(:queues).and_return([consume_queue])
53
+ strategy.instance_variable_set(:@consumers_per_queue, 5)
54
+ expect(strategy).to receive(:consume).with(consume_queue).exactly(5).times
55
+ strategy.fetch
56
+ end
57
+ end
58
+
59
+ context '#stop!' do
60
+ it 'should should stop itself, and every other consumer' do
61
+ allow(strategy).to receive(:running?).and_return(true)
62
+ strategy.instance_eval('@running = true')
63
+ strategy.instance_variable_set(:@consumers, [consumer_object])
64
+ expect(consumer_object).to receive(:stop)
65
+ strategy.stop!
66
+ expect(strategy.instance_variable_get(:@running)).to eq(false)
67
+ end
68
+ end
69
+
70
+ context '#provide_work' do
71
+ it 'should return upto n units of work' do
72
+ n = 2
73
+ strategy.instance_variable_set(:@queue, sized_queue)
74
+ allow(sized_queue).to receive(:size).and_return(10)
75
+ allow(sized_queue).to receive(:pop).and_return(work)
76
+ expect(sized_queue).to receive(:pop).exactly(n).times
77
+ res = strategy.provide_work(n)
78
+ expect(res.size).to eq(n)
79
+ expect(res).to be_a_kind_of(Array)
80
+ end
81
+
82
+ it 'should return an empty array if no work is found in the queue' do
83
+ n = 2
84
+ strategy.instance_variable_set(:@queue, sized_queue)
85
+ allow(sized_queue).to receive(:size).and_return(0)
86
+ allow(sized_queue).to receive(:pop).and_return(work)
87
+ expect(sized_queue).to receive(:pop).exactly(0).times
88
+ res = strategy.provide_work(n)
89
+ expect(res.size).to eq(0)
90
+ expect(res).to be_a_kind_of(Array)
91
+ end
92
+
93
+ it 'should return units of work from the return queue first' do
94
+ n = 2
95
+ strategy.instance_variable_set(:@return_queue, return_queue)
96
+ allow(return_queue).to receive(:empty?).and_return(false)
97
+ allow(return_queue).to receive(:size).and_return(10)
98
+ allow(return_queue).to receive(:pop).and_return(work)
99
+ expect(return_queue).to receive(:pop).exactly(n).times
100
+ res = strategy.provide_work(n)
101
+ expect(res.size).to eq(n)
102
+ expect(res).to be_a_kind_of(Array)
103
+ end
104
+
105
+ it 'should return units of work from all queues if return queue is small' do
106
+ n = 2
107
+
108
+ strategy.instance_variable_set(:@return_queue, return_queue)
109
+ allow(return_queue).to receive(:empty?).and_return(false, true)
110
+ allow(return_queue).to receive(:size).and_return(1)
111
+ allow(return_queue).to receive(:pop).and_return(work)
112
+ expect(return_queue).to receive(:pop).once
113
+
114
+ strategy.instance_variable_set(:@queue, sized_queue)
115
+ allow(sized_queue).to receive(:size).and_return(1)
116
+ allow(sized_queue).to receive(:pop).and_return(work)
117
+ expect(sized_queue).to receive(:pop).once
118
+
119
+ res = strategy.provide_work(n)
120
+ expect(res.size).to eq(n)
121
+ expect(res).to be_a_kind_of(Array)
122
+ end
123
+ end
124
+
125
+ context 'return_work' do
126
+ it 'should add it to the internal return queue' do
127
+ strategy.instance_variable_set(:@return_queue, [])
128
+ strategy.send(:return_work, [work])
129
+ strategy.send(:return_work, [work])
130
+ return_queue = strategy.instance_variable_get(:@return_queue)
131
+ expect(return_queue).to eq([work, work])
132
+ end
133
+ end
134
+
135
+ context '#consume' do
136
+ it 'should create a consumer object, add it to the list of consumers and start a consumer thread' do
137
+ allow(strategy).to receive(:start_consumer_thread).and_return(true)
138
+ expect(strategy).to receive(:start_consumer_thread)
139
+ strategy.send(:consume, consume_queue)
140
+ expect(strategy.instance_variable_get(:@consumers)).to be_a_kind_of(Array)
141
+ expect(strategy.instance_variable_get(:@consumers).first).to be_a_kind_of(TestConsumer)
142
+ end
143
+ end
144
+
145
+ context '#start_consumer_thread' do
146
+ let(:thread) { double('thread') }
147
+
148
+ it 'should create a thread' do
149
+ allow(Thread).to receive(:new).and_return(thread)
150
+ res = strategy.send(:start_consumer_thread, consumer_object)
151
+ expect(res).to eq(thread)
152
+ end
153
+ end
154
+
155
+ context '#create_work_units' do
156
+ it 'should create an unit of work from what the consumer gets, and adds it to the internal queue' do
157
+ strategy.instance_variable_set(:@queue, [])
158
+ res = strategy.send(:create_work_units, consumer_object)
159
+ internal_queue = strategy.instance_variable_get(:@queue)
160
+ expect(internal_queue).to be_a_kind_of(Array)
161
+ expect(internal_queue.first).to be_a_kind_of(Chore::UnitOfWork)
162
+ expect(internal_queue.first.id).to eq(msg)
163
+ end
164
+ end
165
+ end
@@ -13,6 +13,7 @@ describe Chore::Strategy::ForkedWorkerStrategy do
13
13
  let(:job) do
14
14
  Chore::UnitOfWork.new(
15
15
  SecureRandom.uuid,
16
+ nil,
16
17
  'test',
17
18
  job_timeout,
18
19
  Chore::Encoder::JsonEncoder.encode(TestJob.job_hash([1,2,"3"])),
@@ -23,6 +24,10 @@ describe Chore::Strategy::ForkedWorkerStrategy do
23
24
  let!(:worker) { Chore::Worker.new(job) }
24
25
  let(:pid) { Random.rand(2048) }
25
26
 
27
+ before(:each) do
28
+ allow(consumer).to receive(:duplicate_message?).and_return(false)
29
+ end
30
+
26
31
  after(:each) do
27
32
  allow(Process).to receive(:kill) { nil }
28
33
  allow(Process).to receive(:wait) { pid }
@@ -174,6 +179,7 @@ describe Chore::Strategy::ForkedWorkerStrategy do
174
179
  end
175
180
 
176
181
  it 'should run the on_failure callback hook' do
182
+ allow(Chore).to receive(:run_hooks_for)
177
183
  forker.assign(job)
178
184
  expect(Chore).to receive(:run_hooks_for).with(:on_failure, anything, instance_of(Chore::TimeoutError))
179
185
  sleep 2
@@ -291,4 +297,3 @@ describe Chore::Strategy::ForkedWorkerStrategy do
291
297
  end
292
298
  end
293
299
  end
294
-