chore-core 1.10.0 → 4.0.0

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