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
@@ -0,0 +1,127 @@
1
+ require 'spec_helper'
2
+ require 'socket'
3
+
4
+ describe Chore::Strategy::Ipc do
5
+ class DummyClass
6
+ include Chore::Strategy::Ipc
7
+ end
8
+
9
+ before(:each) do
10
+ @dummy_instance = DummyClass.new
11
+ end
12
+
13
+ let(:socket) { double('socket') }
14
+ let(:connection) { double('socket') }
15
+ let(:message) { "test message" }
16
+ let(:encoded_message) { "#{[Marshal.dump(message).size].pack('L>')}#{Marshal.dump(message)}" }
17
+ let(:socket_base) { './prefork_worker_sock-' }
18
+ let(:pid) { '1' }
19
+ let(:socket_file) { socket_base + pid }
20
+
21
+ describe '#create_master_socket' do
22
+ before(:each) do
23
+ allow(Process).to receive(:pid).and_return(pid)
24
+ end
25
+
26
+ it 'deletes socket file, if it already exists' do
27
+ allow(File).to receive(:exist?).with(socket_file).and_return(true)
28
+ allow(UNIXServer).to receive(:new).and_return(socket)
29
+ allow(socket).to receive(:setsockopt).and_return(true)
30
+ expect(File).to receive(:delete).with(socket_file)
31
+ @dummy_instance.create_master_socket
32
+ end
33
+
34
+ it 'should create and return a new UnixServer object' do
35
+ allow(File).to receive(:exist?).with(socket_file).and_return(false)
36
+ allow(UNIXServer).to receive(:new).and_return(socket)
37
+ allow(socket).to receive(:setsockopt).and_return(true)
38
+ expect(@dummy_instance.create_master_socket).to eq(socket)
39
+ end
40
+
41
+ it 'should set the required socket options' do
42
+ allow(File).to receive(:exist?).with(socket_file).and_return(false)
43
+ allow(UNIXServer).to receive(:new).and_return(socket)
44
+ expect(socket).to receive(:setsockopt).with(:SOCKET, :REUSEADDR, true)
45
+ @dummy_instance.create_master_socket
46
+ end
47
+ end
48
+
49
+ context '#child_connection' do
50
+ it 'should accept a connection and return the connection socket' do
51
+ expect(socket).to receive(:accept).and_return(connection)
52
+ expect(@dummy_instance.child_connection(socket)).to eq connection
53
+ end
54
+ end
55
+
56
+ context '#send_msg' do
57
+ it 'should raise an exception if the message is empty' do
58
+ expect { @dummy_instance.send_msg(socket, nil) }.to raise_error('send_msg cannot send empty messages')
59
+ end
60
+
61
+ it 'should send a message with the predefined protocol (size of message + marshalled message)' do
62
+ expect(socket).to receive(:send).with(encoded_message, 0)
63
+ @dummy_instance.send_msg(socket, message)
64
+ end
65
+ end
66
+
67
+ context '#read_msg' do
68
+ before(:each) do
69
+ allow(IO).to receive(:select).with([socket], nil, nil, 0.5).and_return([[socket], [], []])
70
+ end
71
+
72
+ it 'should return nil if the message size is missing' do
73
+ allow(socket).to receive(:recv).and_return(nil)
74
+ expect(@dummy_instance.read_msg(socket)).to eq(nil)
75
+ end
76
+
77
+ it 'should read a message with the predefined protocol (size of message + marshalled message)' do
78
+ allow(socket).to receive(:recv).and_return(encoded_message)
79
+ expect(@dummy_instance.read_msg(socket)).to eq(message)
80
+ end
81
+
82
+ it 'should raise an exception if the connection was dropped' do
83
+ allow(socket).to receive(:recv).and_raise(Errno::ECONNRESET)
84
+ expect { @dummy_instance.read_msg(socket) }.to raise_error(Errno::ECONNRESET)
85
+ end
86
+ end
87
+
88
+ context '#add_worker_socket' do
89
+ it 'should create and return a new UnixSocket object' do
90
+ allow(UNIXSocket).to receive(:new).and_return(socket)
91
+ allow(socket).to receive(:setsockopt).and_return(true)
92
+ expect(@dummy_instance.add_worker_socket).to eq(socket)
93
+ end
94
+
95
+ it 'should set the required socket options' do
96
+ allow(UNIXSocket).to receive(:new).and_return(socket)
97
+ expect(socket).to receive(:setsockopt).with(:SOCKET, :REUSEADDR, true)
98
+ @dummy_instance.add_worker_socket
99
+ end
100
+ end
101
+
102
+ context '#clear_ready' do
103
+ it 'should remove the ready signal from the the socket' do
104
+ expect(socket).to receive(:gets)
105
+ @dummy_instance.clear_ready(socket)
106
+ end
107
+ end
108
+
109
+ context '#signal_ready' do
110
+ it 'should set a ready signal on the socket' do
111
+ expect(socket).to receive(:puts).with('R')
112
+ @dummy_instance.signal_ready(socket)
113
+ end
114
+ end
115
+
116
+ context '#select_sockets' do
117
+ it 'should return a readable socket if one is found' do
118
+ allow(IO).to receive(:select).with([socket], nil, [socket], 0.5).and_return([[socket], [], []])
119
+ expect(@dummy_instance.select_sockets([socket], nil, 0.5)).to eq([[socket], [], []])
120
+ end
121
+
122
+ it 'should timeout and return no sockets if none are found within the timeout window' do
123
+ expect(@dummy_instance.select_sockets(nil, nil, 0.1)).to eq(nil)
124
+ end
125
+ end
126
+ end
127
+
@@ -0,0 +1,236 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chore::Strategy::PreforkedWorker do
4
+ before(:each) do
5
+ allow_any_instance_of(Chore::Strategy::PreforkedWorker).to receive(:post_fork_setup)
6
+ end
7
+
8
+ let(:preforkedworker) { Chore::Strategy::PreforkedWorker.new }
9
+ let(:socket) { double("socket") }
10
+ let(:work) { double("work") }
11
+ let(:consumer) { double("consumer") }
12
+ let(:worker) { double("worker") }
13
+ let(:config) { double("config") }
14
+ let(:queues) { double("queues") }
15
+ let(:consumer_object) { double("consumer_object") }
16
+
17
+ context '#start_worker' do
18
+ it 'should connect to the master to signal that it is ready, and process messages with the worker' do
19
+ allow(preforkedworker).to receive(:connect_to_master).and_return(socket)
20
+ expect(preforkedworker).to receive(:connect_to_master).with(socket)
21
+ allow(preforkedworker).to receive(:worker).and_return(true)
22
+ expect(preforkedworker).to receive(:worker).with(socket)
23
+ preforkedworker.start_worker(socket)
24
+ end
25
+ end
26
+
27
+ describe '#worker' do
28
+ before(:each) do
29
+ preforkedworker.instance_variable_set(:@self_read, socket)
30
+ allow(preforkedworker).to receive(:select_sockets).and_return([[socket],nil,nil])
31
+ allow(preforkedworker).to receive(:read_msg).and_return(nil)
32
+ allow(preforkedworker).to receive(:is_orphan?).and_return(false)
33
+ allow(preforkedworker).to receive(:process_work).and_return(true)
34
+ allow(preforkedworker).to receive(:signal_ready).and_return(true)
35
+ allow(work).to receive(:queue_timeout).and_return(20*60)
36
+ end
37
+
38
+
39
+ it 'should not run while @running is false' do
40
+ allow(preforkedworker).to receive(:running?).and_return(false)
41
+ expect(preforkedworker).to receive(:select_sockets).exactly(0).times
42
+ begin
43
+ preforkedworker.send(:worker, nil)
44
+ rescue SystemExit=>e
45
+ expect(e.status).to eq(0)
46
+ end
47
+ end
48
+
49
+ it 'should be able to handle timeouts on readable sockets' do
50
+ allow(preforkedworker).to receive(:running?).and_return(true, true, false)
51
+ allow(preforkedworker).to receive(:select_sockets).and_return(nil)
52
+ expect(preforkedworker).to receive(:select_sockets).exactly(2).times
53
+ begin
54
+ preforkedworker.send(:worker, nil)
55
+ rescue SystemExit=>e
56
+ expect(e.status).to eq(0)
57
+ end
58
+ end
59
+
60
+ it 'should read a message if the connection is readable' do
61
+ allow(preforkedworker).to receive(:running?).and_return(true, false)
62
+ expect(preforkedworker).to receive(:read_msg).once
63
+ begin
64
+ preforkedworker.send(:worker, nil)
65
+ rescue SystemExit=>e
66
+ expect(e.status).to eq(0)
67
+ end
68
+ end
69
+
70
+ it 'should check if the master is alive, and if not, it should end' do
71
+ allow(preforkedworker).to receive(:running).and_return(true)
72
+ allow(preforkedworker).to receive(:select_sockets).and_return([[socket],nil,nil])
73
+ allow(preforkedworker).to receive(:read_msg).and_return(nil)
74
+ allow(preforkedworker).to receive(:is_orphan?).and_return(true)
75
+ begin
76
+ preforkedworker.send(:worker, socket)
77
+ rescue SystemExit=>e
78
+ expect(e.status).to eq(0)
79
+ end
80
+ end
81
+
82
+ it 'should process work if it is read from the master and signal ready' do
83
+ allow(preforkedworker).to receive(:running?).and_return(true, false)
84
+ allow(preforkedworker).to receive(:read_msg).and_return(work)
85
+ expect(preforkedworker).to receive(:process_work).once
86
+ expect(preforkedworker).to receive(:signal_ready).once
87
+ begin
88
+ preforkedworker.send(:worker, socket)
89
+ rescue SystemExit=>e
90
+ expect(e.status).to eq(0)
91
+ end
92
+ end
93
+
94
+ it 'should exit the process if the connection to master is closed' do
95
+ allow(preforkedworker).to receive(:running?).and_return(true)
96
+ allow(preforkedworker).to receive(:read_msg).and_raise(Errno::ECONNRESET)
97
+ begin
98
+ preforkedworker.send(:worker, socket)
99
+ rescue SystemExit=>e
100
+ expect(e.status).to eq(0)
101
+ end
102
+ end
103
+ end
104
+
105
+ context '#connect_to_master' do
106
+ it 'should create a connection to the master, and send it its PID and a ready message' do
107
+ allow(preforkedworker).to receive(:child_connection).and_return(socket)
108
+ allow(preforkedworker).to receive(:send_msg).and_return(true)
109
+ allow(preforkedworker).to receive(:signal_ready).and_return(true)
110
+
111
+ expect(preforkedworker).to receive(:child_connection).once
112
+ expect(preforkedworker).to receive(:send_msg).once
113
+ expect(preforkedworker).to receive(:signal_ready).once
114
+
115
+ res = preforkedworker.send(:connect_to_master,socket)
116
+
117
+ expect(res).to eq(socket)
118
+ end
119
+ end
120
+
121
+ context '#post_fork_setup' do
122
+ before(:each) do
123
+ allow(preforkedworker).to receive(:procline).and_return(true)
124
+ allow(preforkedworker).to receive(:trap_signals)
125
+ allow_any_instance_of(Chore::Strategy::PreforkedWorker).to receive(:post_fork_setup).and_call_original
126
+ end
127
+
128
+ it 'should change the process name' do
129
+ expect(preforkedworker).to receive(:procline)
130
+ preforkedworker.send(:post_fork_setup)
131
+ end
132
+
133
+ it 'should trap new relevant signals' do
134
+ expect(preforkedworker).to receive(:trap_signals)
135
+ preforkedworker.send(:post_fork_setup)
136
+ end
137
+
138
+ it "should run after_fork hooks" do
139
+ hook_called = false
140
+ Chore.add_hook(:after_fork) { hook_called = true }
141
+ preforkedworker.send(:post_fork_setup)
142
+ expect(hook_called).to be true
143
+ end
144
+ end
145
+
146
+ context '#process_work' do
147
+ before(:each) do
148
+ allow(preforkedworker).to receive(:consumer).and_return(consumer)
149
+ allow(work).to receive(:queue_name).and_return("test_queue")
150
+ allow(work).to receive(:consumer=)
151
+ allow(work).to receive(:queue_timeout).and_return(10)
152
+ allow(Chore::Worker).to receive(:new).and_return(worker)
153
+ allow(worker).to receive(:start)
154
+ end
155
+
156
+ it 'should fetch the consumer object associated with the queue' do
157
+ expect(preforkedworker).to receive(:consumer)
158
+ preforkedworker.send(:process_work, [work])
159
+ end
160
+
161
+ it 'should create and start a worker object with the job sent to it' do
162
+ expect(worker).to receive(:start)
163
+ preforkedworker.send(:process_work, [work])
164
+ end
165
+
166
+ it 'should timeout if the work runs for longer than the queue timeout' do
167
+ allow(work).to receive(:queue_timeout).and_return(1)
168
+ allow(worker).to receive(:start) { sleep 5 }
169
+ begin
170
+ preforkedworker.send(:process_work, [work])
171
+ rescue => e
172
+ expect(e.class).to eq(Timeout::Error)
173
+ end
174
+ end
175
+ end
176
+
177
+ context '#consumer' do
178
+ before(:each) do
179
+ preforkedworker.instance_variable_set(:@consumer_cache, {key_1: :value_1})
180
+ allow(Chore).to receive(:config).and_return(config)
181
+ allow(config).to receive(:queues).and_return(queues)
182
+ allow(queues).to receive(:size).and_return(2)
183
+ allow(config).to receive(:consumer).and_return(consumer)
184
+ allow(consumer).to receive(:new).and_return(:value_2)
185
+ end
186
+
187
+ it 'should fetch a consumer object if it was created previously for this queue' do
188
+ preforkedworker.instance_variable_set(:@consumer_cache, {key_1: :value_1})
189
+ res = preforkedworker.send(:consumer,:key_1)
190
+ expect(res).to eq(:value_1)
191
+ end
192
+
193
+ it 'should create and return a new consumer object if one does not exist for this queue' do
194
+ preforkedworker.instance_variable_set(:@consumer_cache, {})
195
+ expect(consumer).to receive(:new)
196
+ res = preforkedworker.send(:consumer,:key_2)
197
+ expect(res).to eq(:value_2)
198
+ end
199
+ end
200
+
201
+ context '#trap_signals' do
202
+ it 'should reset signals' do
203
+ allow(Chore::Signal).to receive(:reset)
204
+ allow(Chore::Signal).to receive(:trap)
205
+ expect(Chore::Signal).to receive(:reset)
206
+ preforkedworker.send(:trap_signals)
207
+ end
208
+
209
+ it 'should trap the signals passed to it' do
210
+ allow(Chore::Signal).to receive(:reset)
211
+ allow(Chore::Signal).to receive(:trap)
212
+ expect(Chore::Signal).to receive(:trap).with(:INT).once
213
+ expect(Chore::Signal).to receive(:trap).with(:QUIT).once
214
+ expect(Chore::Signal).to receive(:trap).with(:TERM).once
215
+ expect(Chore::Signal).to receive(:trap).with(:USR1).once
216
+ preforkedworker.send(:trap_signals)
217
+ end
218
+ end
219
+
220
+ context '#is_orphan?' do
221
+ it 'should return true if the parent is dead' do
222
+ allow(Process).to receive(:ppid).and_return(10)
223
+ preforkedworker.instance_variable_set(:@manager_pid, 9)
224
+ res = preforkedworker.send(:is_orphan?)
225
+ expect(res).to eq(true)
226
+ end
227
+
228
+ it 'should return false if the parent is alive' do
229
+ allow(Process).to receive(:ppid).and_return(10)
230
+ preforkedworker.instance_variable_set(:@manager_pid, 10)
231
+ res = preforkedworker.send(:is_orphan?)
232
+ expect(res).to eq(false)
233
+ end
234
+ end
235
+ end
236
+
@@ -0,0 +1,131 @@
1
+ require 'spec_helper'
2
+ require 'chore/strategies/worker/helpers/work_distributor'
3
+
4
+ describe Chore::Strategy::WorkDistributor do
5
+ let(:timestamp) { Time.now }
6
+ let(:manager) { double('manager') }
7
+ let(:worker) { Chore::Strategy::WorkerInfo.new(1) }
8
+ let(:consumer) { double('consumer') }
9
+ let(:job) do
10
+ Chore::UnitOfWork.new(
11
+ SecureRandom.uuid,
12
+ 'test',
13
+ 60,
14
+ Chore::Encoder::JsonEncoder.encode(TestJob.job_hash([1,2,"3"])),
15
+ 0
16
+ )
17
+ end
18
+ let(:socket) { double('socket') }
19
+
20
+ context '#include_ipc' do
21
+ it 'should include the Ipc module' do
22
+ expect(described_class.ipc_help).to eq(:available)
23
+ end
24
+ end
25
+
26
+ context '#fetch_and_assign_jobs' do
27
+ it 'should fetch jobs from the consumer' do
28
+ allow(described_class).to receive(:assign_jobs).and_return(true)
29
+ allow(manager).to receive(:fetch_work).with(1).and_return([job])
30
+ allow(manager).to receive(:return_work)
31
+ expect(manager).to receive(:fetch_work).with(1)
32
+ described_class.fetch_and_assign_jobs([worker], manager)
33
+ end
34
+
35
+ it 'should assign the fetched jobs to the workers' do
36
+ allow(manager).to receive(:fetch_work).with(1).and_return([job])
37
+ allow(manager).to receive(:return_work)
38
+ expect(described_class).to receive(:assign_jobs).with([job], [worker])
39
+ described_class.fetch_and_assign_jobs([worker], manager)
40
+ end
41
+
42
+ it 'should not return any work' do
43
+ allow(described_class).to receive(:assign_jobs).and_return([])
44
+ allow(manager).to receive(:fetch_work).with(1).and_return([job])
45
+ expect(manager).to receive(:return_work).with([])
46
+ described_class.fetch_and_assign_jobs([worker], manager)
47
+ end
48
+
49
+ it 'should raise and exception if it does not get an array from the manager' do
50
+ allow(manager).to receive(:fetch_work).with(1).and_return(nil)
51
+ allow(manager).to receive(:return_work)
52
+ expect { described_class.fetch_and_assign_jobs([worker], manager) }.to raise_error("DW: jobs needs to be a list got NilClass")
53
+ end
54
+
55
+ it 'should sleep if no jobs are available' do
56
+ expect(described_class).to receive(:sleep).with(0.1)
57
+ allow(manager).to receive(:fetch_work).with(1).and_return([])
58
+ allow(manager).to receive(:return_work)
59
+ described_class.fetch_and_assign_jobs([worker], manager)
60
+ end
61
+ end
62
+
63
+ context '#assign_jobs' do
64
+ it 'should raise an exception if we have no free workers' do
65
+ expect { described_class.send(:assign_jobs, [job], []) }.to raise_error('DW: assign_jobs got 0 workers')
66
+ end
67
+
68
+ it 'should remove the consumer object from the job object' do
69
+ allow(described_class).to receive(:push_job_to_worker).and_return(true)
70
+
71
+ described_class.send(:assign_jobs, [job], [worker])
72
+ end
73
+
74
+ it 'should raise an exception if more jobs than workers are provided to it' do
75
+ allow(described_class).to receive(:push_job_to_worker).and_return(true)
76
+
77
+ expect { described_class.send(:assign_jobs, [job, job], [worker]) }.to raise_error('DW: More Jobs than Sockets')
78
+ end
79
+
80
+ it 'should send the job object to the free worker' do
81
+ allow(described_class).to receive(:push_job_to_worker).and_return(true)
82
+
83
+ expect(described_class).to receive(:push_job_to_worker).with(job, worker)
84
+ described_class.send(:assign_jobs, [job], [worker])
85
+ end
86
+
87
+ it 'should return jobs that failed to be assigned' do
88
+ job2 = Chore::UnitOfWork.new(
89
+ SecureRandom.uuid,
90
+ 'test',
91
+ 60,
92
+ Chore::Encoder::JsonEncoder.encode(TestJob.job_hash([1,2,"3"])),
93
+ 0
94
+ )
95
+ worker2 = Chore::Strategy::WorkerInfo.new(2)
96
+
97
+ allow(described_class).to receive(:push_job_to_worker).and_return(true, false)
98
+
99
+ expect(described_class).to receive(:push_job_to_worker).with(job, worker)
100
+ expect(described_class).to receive(:push_job_to_worker).with(job2, worker2)
101
+ unassigned_jobs = described_class.send(:assign_jobs, [job, job2], [worker, worker2])
102
+ expect(unassigned_jobs).to eq([job2])
103
+ end
104
+ end
105
+
106
+ context '#push_job_to_worker' do
107
+ before(:each) do
108
+ allow(described_class).to receive(:clear_ready).with(worker.socket).and_return(true)
109
+ allow(described_class).to receive(:send_msg).with(worker.socket, job).and_return(true)
110
+ end
111
+
112
+ it 'should clear all signals from the worker' do
113
+ expect(described_class).to receive(:clear_ready).with(worker.socket)
114
+ described_class.send(:push_job_to_worker, job, worker)
115
+ end
116
+
117
+ it 'should send the job as a message on the worker' do
118
+ expect(described_class).to receive(:send_msg).with(worker.socket, job)
119
+ described_class.send(:push_job_to_worker, job, worker)
120
+ end
121
+
122
+ it 'should return true if job successfully sent' do
123
+ expect(described_class.send(:push_job_to_worker, job, worker)).to eq(true)
124
+ end
125
+
126
+ it 'should return false if job failed to send' do
127
+ expect(described_class).to receive(:send_msg).and_raise(StandardError)
128
+ expect(described_class.send(:push_job_to_worker, job, worker)).to eq(false)
129
+ end
130
+ end
131
+ end