worker_roulette 0.1.7 → 0.1.9

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.
@@ -1,248 +1,227 @@
1
1
  require "spec_helper"
2
+ module WorkerRoulette
3
+ describe WorkerRoulette do
4
+ include EventedSpec::EMSpec
5
+
6
+ let(:sender) {'katie_80'}
7
+ let(:work_orders) {["hello", "foreman"]}
8
+ let(:default_headers) {Hash['headers' => {'sender' => sender}]}
9
+ let(:hello_work_order) {Hash['payload' => "hello"]}
10
+ let(:foreman_work_order) {Hash['payload' => "foreman"]}
11
+ let(:work_orders_with_headers) {default_headers.merge({'payload' => work_orders})}
12
+ let(:jsonized_work_orders_with_headers) {[WorkerRoulette.dump(work_orders_with_headers)]}
13
+ let(:worker_roulette) { WorkerRoulette.start(evented: true) }
14
+ let(:redis) {Redis.new(worker_roulette.redis_config)}
15
+
16
+ context "Evented Foreman" do
17
+ let(:subject) {worker_roulette.foreman(sender)}
18
+
19
+ it "enqueues work" do
20
+ called = false
21
+ foreman = worker_roulette.foreman('foreman')
22
+ foreman.enqueue_work_order('some old fashion work') do |redis_response, stuff|
23
+ called = true
24
+ end
25
+ done(0.1) { expect(called).to be_truthy }
26
+ end
2
27
 
3
- describe WorkerRoulette do
4
- include EventedSpec::EMSpec
5
-
6
- let(:sender) {'katie_80'}
7
- let(:work_orders) {["hello", "foreman"]}
8
- let(:default_headers) {Hash['headers' => {'sender' => sender}]}
9
- let(:hello_work_order) {Hash['payload' => "hello"]}
10
- let(:foreman_work_order) {Hash['payload' => "foreman"]}
11
- let(:work_orders_with_headers) {default_headers.merge({'payload' => work_orders})}
12
- let(:jsonized_work_orders_with_headers) {[WorkerRoulette.dump(work_orders_with_headers)]}
13
-
14
- let(:redis) {Redis.new(WorkerRoulette.redis_config)}
15
-
16
- em_before do
17
- WorkerRoulette.start(evented: true)
18
- end
19
-
20
- context "Evented Foreman" do
21
- let(:subject) {WorkerRoulette.a_foreman(sender)}
22
-
23
- it "enqueues work" do
24
- called = false
25
- foreman = WorkerRoulette.a_foreman('foreman')
26
- foreman.enqueue_work_order('some old fashion work') do |redis_response, stuff|
27
- called = true
28
+ it "should enqueue_work_order two work_orders in the sender's slot in the job board" do
29
+ subject.enqueue_work_order(work_orders.first) do
30
+ subject.enqueue_work_order(work_orders.last) do
31
+ expected = work_orders.map { |m| WorkerRoulette.dump(default_headers.merge({'payload' => m})) }
32
+ expect(redis.lrange(sender, 0, -1)).to eq(expected)
33
+ done
34
+ end
35
+ end
28
36
  end
29
- done(0.1) { expect(called).to be_truthy }
30
- end
31
37
 
32
- it "should enqueue_work_order two work_orders in the sender's slot in the job board" do
33
- subject.enqueue_work_order(work_orders.first) do
34
- subject.enqueue_work_order(work_orders.last) do
35
- expected = work_orders.map { |m| WorkerRoulette.dump(default_headers.merge({'payload' => m})) }
36
- expect(redis.lrange(sender, 0, -1)).to eq(expected)
38
+ it "should enqueue_work_order an array of work_orders without headers in the sender's slot in the job board" do
39
+ subject.enqueue_work_order_without_headers(work_orders) do
40
+ expect(redis.lrange(sender, 0, -1)).to eq([WorkerRoulette.dump(work_orders)])
37
41
  done
38
42
  end
39
43
  end
40
- end
41
44
 
42
- it "should enqueue_work_order an array of work_orders without headers in the sender's slot in the job board" do
43
- subject.enqueue_work_order_without_headers(work_orders) do
44
- expect(redis.lrange(sender, 0, -1)).to eq([WorkerRoulette.dump(work_orders)])
45
- done
46
- end
47
- end
48
-
49
- it "should enqueue_work_order an array of work_orders with default headers in the sender's slot in the job board" do
50
- subject.enqueue_work_order(work_orders) do
51
- expect(redis.lrange(sender, 0, -1)).to eq(jsonized_work_orders_with_headers)
52
- done
45
+ it "should enqueue_work_order an array of work_orders with default headers in the sender's slot in the job board" do
46
+ subject.enqueue_work_order(work_orders) do
47
+ expect(redis.lrange(sender, 0, -1)).to eq(jsonized_work_orders_with_headers)
48
+ done
49
+ end
53
50
  end
54
- end
55
51
 
56
- it "should enqueue_work_order an array of work_orders with additional headers in the sender's slot in the job board" do
57
- extra_headers = {'foo' => 'bars'}
58
- subject.enqueue_work_order(work_orders, extra_headers) do
59
- work_orders_with_headers['headers'].merge!(extra_headers)
60
- expect(redis.lrange(sender, 0, -1)).to eq([WorkerRoulette.dump(work_orders_with_headers)])
61
- done
52
+ it "should enqueue_work_order an array of work_orders with additional headers in the sender's slot in the job board" do
53
+ extra_headers = {'foo' => 'bars'}
54
+ subject.enqueue_work_order(work_orders, extra_headers) do
55
+ work_orders_with_headers['headers'].merge!(extra_headers)
56
+ expect(redis.lrange(sender, 0, -1)).to eq([WorkerRoulette.dump(work_orders_with_headers)])
57
+ done
58
+ end
62
59
  end
63
- end
64
60
 
65
- it "should post the sender's id to the job board with an order number" do
66
- first_foreman = WorkerRoulette.a_foreman('first_foreman')
67
- first_foreman.enqueue_work_order('foo') do
68
- subject.enqueue_work_order(work_orders.first) do
69
- subject.enqueue_work_order(work_orders.last) do
70
- expect(redis.zrange(subject.job_board_key, 0, -1, with_scores: true)).to eq([["first_foreman", 1.0], ["katie_80", 2.0]])
71
- done
61
+ it "should post the sender's id to the job board with an order number" do
62
+ first_foreman = worker_roulette.foreman('first_foreman')
63
+ first_foreman.enqueue_work_order('foo') do
64
+ subject.enqueue_work_order(work_orders.first) do
65
+ subject.enqueue_work_order(work_orders.last) do
66
+ expect(redis.zrange(subject.job_board_key, 0, -1, with_scores: true)).to eq([["first_foreman", 1.0], ["katie_80", 2.0]])
67
+ done
68
+ end
72
69
  end
73
70
  end
74
71
  end
75
- end
76
72
 
77
- it "should generate a monotically increasing score for senders not on the job board, but not for senders already there" do
78
- first_foreman = WorkerRoulette.a_foreman('first_foreman')
79
- expect(redis.get(subject.counter_key)).to be_nil
80
- first_foreman.enqueue_work_order(work_orders.first) do
81
- expect(redis.get(subject.counter_key)).to eq("1")
82
- first_foreman.enqueue_work_order(work_orders.last) do
73
+ it "should generate a monotically increasing score for senders not on the job board, but not for senders already there" do
74
+ first_foreman = worker_roulette.foreman('first_foreman')
75
+ expect(redis.get(subject.counter_key)).to be_nil
76
+ first_foreman.enqueue_work_order(work_orders.first) do
83
77
  expect(redis.get(subject.counter_key)).to eq("1")
84
- subject.enqueue_work_order(work_orders.first) do
85
- expect(redis.get(subject.counter_key)).to eq("2")
86
- done
78
+ first_foreman.enqueue_work_order(work_orders.last) do
79
+ expect(redis.get(subject.counter_key)).to eq("1")
80
+ subject.enqueue_work_order(work_orders.first) do
81
+ expect(redis.get(subject.counter_key)).to eq("2")
82
+ done
83
+ end
87
84
  end
88
85
  end
89
86
  end
90
87
  end
91
88
 
92
- it "should publish a notification that a new job is ready" do
93
- result = nil
94
- subscriber = WorkerRoulette.new_redis_pubsub
95
- subscriber.subscribe(WorkerRoulette::JOB_NOTIFICATIONS) do |message|
96
- subscriber.unsubscribe(WorkerRoulette::JOB_NOTIFICATIONS)
97
- expect(message).to eq(WorkerRoulette::JOB_NOTIFICATIONS)
98
- done
99
- end.callback { subject.enqueue_work_order(work_orders) }
100
- end
101
- end
102
-
103
- context "Evented Tradesman" do
104
- let(:foreman) {WorkerRoulette.a_foreman(sender)}
105
- let(:subject) {WorkerRoulette.a_tradesman}
89
+ context "Evented Tradesman" do
90
+ let(:foreman) {worker_roulette.foreman(sender)}
91
+ let(:subject) {worker_roulette.tradesman(nil, 0.01) }
106
92
 
107
- it "should be working on behalf of a sender" do
108
- foreman.enqueue_work_order(work_orders) do
109
- subject.work_orders! do |r|
110
- expect(subject.last_sender).to eq(sender)
111
- done
93
+ it "should be working on behalf of a sender" do
94
+ foreman.enqueue_work_order(work_orders) do
95
+ subject.work_orders! do |r|
96
+ expect(subject.last_sender).to eq(sender)
97
+ done
98
+ end
112
99
  end
113
100
  end
114
- end
115
101
 
116
102
 
117
- it "should drain one set of work_orders from the sender's slot in the job board" do
118
- foreman.enqueue_work_order(work_orders) do
119
- subject.work_orders! do |r|
120
- expect(r).to eq([work_orders_with_headers])
121
- subject.work_orders! do |r| expect(r).to be_empty
122
- subject.work_orders! {|r| expect(r).to be_empty; done} #does not throw an error if queue is alreay empty
103
+ it "should drain one set of work_orders from the sender's slot in the job board" do
104
+ foreman.enqueue_work_order(work_orders) do
105
+ subject.work_orders! do |r|
106
+ expect(r).to eq([work_orders_with_headers])
107
+ subject.work_orders! do |r| expect(r).to be_empty
108
+ subject.work_orders! {|r| expect(r).to be_empty; done} #does not throw an error if queue is alreay empty
109
+ end
123
110
  end
124
111
  end
125
112
  end
126
- end
127
113
 
128
- it "should take the oldest sender off the job board (FIFO)" do
129
- foreman.enqueue_work_order(work_orders) do
130
- oldest_sender = sender.to_s
131
- most_recent_sender = 'most_recent_sender'
132
- most_recent_foreman = WorkerRoulette.a_foreman(most_recent_sender)
133
- most_recent_foreman.enqueue_work_order(work_orders) do
134
- expect(redis.zrange(subject.job_board_key, 0, -1)).to eq([oldest_sender, most_recent_sender])
135
- subject.work_orders! { expect(redis.zrange(subject.job_board_key, 0, -1)).to eq([most_recent_sender]); done }
114
+ it "should take the oldest sender off the job board (FIFO)" do
115
+ foreman.enqueue_work_order(work_orders) do
116
+ oldest_sender = sender.to_s
117
+ most_recent_sender = 'most_recent_sender'
118
+ most_recent_foreman = worker_roulette.foreman(most_recent_sender)
119
+ most_recent_foreman.enqueue_work_order(work_orders) do
120
+ expect(redis.zrange(subject.job_board_key, 0, -1)).to eq([oldest_sender, most_recent_sender])
121
+ subject.work_orders! { expect(redis.zrange(subject.job_board_key, 0, -1)).to eq([most_recent_sender]); done }
122
+ end
136
123
  end
137
124
  end
138
- end
139
125
 
140
- it "should get the work_orders from the next queue when a new job is ready" do
141
- expect(subject).to receive(:work_orders!).twice.and_call_original
142
- publish = proc {foreman.enqueue_work_order(work_orders)}
126
+ it "should get the work_orders from the next queue when a new job is ready" do
127
+ #tradesman polls every so often, we care that it is called at least twice, but did not use
128
+ #the built in rspec syntax for that bc if the test ends while we're talking to redis, redis
129
+ #throws an Error. This way we ensure we call work_orders! at least twice and just stub the second
130
+ #call so as not to hurt redis' feelings.
143
131
 
144
- subject.wait_for_work_orders(publish) do |redis_work_orders, message, channel|
145
- expect(redis_work_orders).to eq([work_orders_with_headers])
146
- expect(subject.last_sender).to be_nil
147
- done
148
- end
149
- end
132
+ expect(subject).to receive(:work_orders!).and_call_original
133
+ expect(subject).to receive(:work_orders!)
150
134
 
151
- it "should publish and subscribe on custom channels" do
152
- good_subscribed = false
153
- bad_subscribed = false
135
+ foreman.enqueue_work_order(work_orders) do
136
+ subject.wait_for_work_orders do |redis_work_orders|
137
+ expect(redis_work_orders).to eq([work_orders_with_headers])
138
+ expect(subject.last_sender).to match(/katie_80/)
139
+ done(0.1)
140
+ end
141
+ end
142
+ end
154
143
 
155
- tradesman = WorkerRoulette.a_tradesman('good_channel')
156
- evil_tradesman = WorkerRoulette.a_tradesman('bad_channel')
144
+ it "should publish and subscribe on custom channels" do
145
+ good_subscribed = false
146
+ bad_subscribed = false
157
147
 
158
- good_foreman = WorkerRoulette.a_foreman('foreman', 'good_channel')
159
- bad_foreman = WorkerRoulette.a_foreman('foreman', 'bad_channel')
148
+ tradesman = worker_roulette.tradesman('good_channel', 0.001)
149
+ evil_tradesman = worker_roulette.tradesman('bad_channel', 0.001)
160
150
 
161
- good_publish = proc {good_foreman.enqueue_work_order('some old fashion work')}
162
- bad_publish = proc {bad_foreman.enqueue_work_order('evil biddings you should not carry out')}
151
+ good_foreman = worker_roulette.foreman('foreman', 'good_channel')
152
+ bad_foreman = worker_roulette.foreman('foreman', 'bad_channel')
163
153
 
164
- expect(tradesman).to receive(:work_orders!).twice.and_call_original
165
- expect(evil_tradesman).to receive(:work_orders!).twice.and_call_original
154
+ #tradesman polls every so often, we care that it is called at least twice, but did not use
155
+ #the built in rspec syntax for that bc if the test ends while we're talking to redis, redis
156
+ #throws an Error. This way we ensure we call work_orders! at least twice and just stub the second
157
+ #call so as not to hurt redis' feelings.
158
+ expect(tradesman).to receive(:work_orders!).and_call_original
159
+ expect(tradesman).to receive(:work_orders!)
166
160
 
167
- #They are double subscribing; is it possible that it is the connection pool?
161
+ expect(evil_tradesman).to receive(:work_orders!).and_call_original
162
+ expect(evil_tradesman).to receive(:work_orders!)
168
163
 
169
- tradesman.wait_for_work_orders(good_publish) do |good_work|
170
- expect(good_work.to_s).to match("old fashion")
171
- expect(good_work.to_s).not_to match("evil")
172
- end
164
+ good_foreman.enqueue_work_order('some old fashion work') do
165
+ bad_foreman.enqueue_work_order('evil biddings you should not carry out') do
173
166
 
174
- evil_tradesman.wait_for_work_orders(bad_publish) do |bad_work|
175
- expect(bad_work.to_s).not_to match("old fashion")
176
- expect(bad_work.to_s).to match("evil")
177
- end
167
+ tradesman.wait_for_work_orders do |good_work|
168
+ expect(good_work.to_s).to match("old fashion")
169
+ expect(good_work.to_s).not_to match("evil")
170
+ end
178
171
 
179
- done(0.2)
180
- end
172
+ evil_tradesman.wait_for_work_orders do |bad_work|
173
+ expect(bad_work.to_s).not_to match("old fashion")
174
+ expect(bad_work.to_s).to match("evil")
175
+ end
176
+ done(0.1)
181
177
 
182
- it "should unsubscribe from the job board" do
183
- publish = proc {foreman.enqueue_work_order(work_orders)}
184
- subject.wait_for_work_orders(publish) do |redis_work_orders, message, channel|
185
- subject.unsubscribe {done}
186
- end
187
- expect_any_instance_of(EM::Hiredis::PubsubClient).to receive(:close_connection).and_call_original
188
- end
189
-
190
- it "should periodically (random time between 20 and 25 seconds?) poll the job board for new work, in case it missed a notification" do
191
- expect(EM::PeriodicTimer).to receive(:new) {|time| expect(time).to be_within(2.5).of(22.5)}
192
- publish = proc {foreman.enqueue_work_order('foo')}
193
- subject.wait_for_work_orders(publish) {done}
194
- end
195
-
196
- pending "cancels the old timer when the on_message callback is called" do
197
- publish = proc {foreman.enqueue_work_order('foo')}
198
- subject.wait_for_work_orders(publish) do
199
- expect(subject.send(:timer)).to receive(:cancel).and_call_original
200
- done
178
+ end
179
+ end
201
180
  end
202
- end
203
181
 
204
- it "should pull off work orders for more than one sender" do
205
- tradesman = WorkerRoulette.a_tradesman('good_channel')
182
+ it "should pull off work orders for more than one sender" do
183
+ tradesman = worker_roulette.tradesman('good_channel')
206
184
 
207
- good_foreman = WorkerRoulette.a_foreman('good_foreman', 'good_channel')
208
- lazy_foreman = WorkerRoulette.a_foreman('lazy_foreman', 'good_channel')
185
+ good_foreman = worker_roulette.foreman('good_foreman', 'good_channel')
186
+ lazy_foreman = worker_roulette.foreman('lazy_foreman', 'good_channel')
209
187
 
210
- got_good = false
211
- got_lazy = false
212
- good_foreman.enqueue_work_order('do good work') do
213
- tradesman.work_orders! do |r|
214
- got_good = true
215
- expect(r.first['payload']).to eq('do good work')
188
+ got_good = false
189
+ got_lazy = false
190
+ good_foreman.enqueue_work_order('do good work') do
191
+ tradesman.work_orders! do |r|
192
+ got_good = true
193
+ expect(r.first['payload']).to eq('do good work')
194
+ end
216
195
  end
217
- end
218
- lazy_foreman.enqueue_work_order('just get it done') do
219
- tradesman.work_orders! do |r|
220
- got_lazy = true
221
- expect(r.first['payload']).to eq('just get it done')
196
+ lazy_foreman.enqueue_work_order('just get it done') do
197
+ tradesman.work_orders! do |r|
198
+ got_lazy = true
199
+ expect(r.first['payload']).to eq('just get it done')
200
+ end
222
201
  end
223
- end
224
202
 
225
- done(0.2) {expect(got_good && got_lazy).to eq(true)}
203
+ done(0.2) {expect(got_good && got_lazy).to eq(true)}
204
+ end
226
205
  end
227
- end
228
206
 
229
- pending "should return a hash with a string in the payload if OJ cannot parse the json"
207
+ pending "should return a hash with a string in the payload if OJ cannot parse the json"
230
208
 
231
- context "Failure" do
232
- it "should not put the sender_id and work_orders back if processing fails bc new work_orders may have been processed while that process failed" do; done; end
233
- end
209
+ context "Failure" do
210
+ it "should not put the sender_id and work_orders back if processing fails bc new work_orders may have been processed while that process failed" do; done; end
211
+ end
234
212
 
235
- context "Concurrent Access" do
236
- it "should not leak connections"
213
+ context "Concurrent Access" do
214
+ it "should not leak connections"
237
215
 
238
- it "should be fork() proof" do
239
- @subject = WorkerRoulette.a_tradesman
240
- @subject.work_orders! do
241
- fork do
242
- @subject.work_orders!
216
+ it "should be fork() proof" do
217
+ @subject = worker_roulette.tradesman
218
+ @subject.work_orders! do
219
+ fork do
220
+ @subject.work_orders!
221
+ end
243
222
  end
223
+ done(1)
244
224
  end
245
- done(1)
246
225
  end
247
226
  end
248
227
  end