worker_roulette 0.1.7 → 0.1.9

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