kicks 3.0.0.pre
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.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +24 -0
- data/.gitignore +12 -0
- data/ChangeLog.md +142 -0
- data/Dockerfile +24 -0
- data/Dockerfile.slim +20 -0
- data/Gemfile +8 -0
- data/Guardfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +209 -0
- data/Rakefile +12 -0
- data/bin/sneakers +6 -0
- data/docker-compose.yml +24 -0
- data/examples/benchmark_worker.rb +22 -0
- data/examples/max_retry_handler.rb +68 -0
- data/examples/metrics_worker.rb +34 -0
- data/examples/middleware_worker.rb +36 -0
- data/examples/newrelic_metrics_worker.rb +40 -0
- data/examples/profiling_worker.rb +69 -0
- data/examples/sneakers.conf.rb.example +11 -0
- data/examples/title_scraper.rb +36 -0
- data/examples/workflow_worker.rb +23 -0
- data/kicks.gemspec +44 -0
- data/lib/sneakers/cli.rb +122 -0
- data/lib/sneakers/concerns/logging.rb +34 -0
- data/lib/sneakers/concerns/metrics.rb +34 -0
- data/lib/sneakers/configuration.rb +125 -0
- data/lib/sneakers/content_encoding.rb +47 -0
- data/lib/sneakers/content_type.rb +47 -0
- data/lib/sneakers/error_reporter.rb +33 -0
- data/lib/sneakers/errors.rb +2 -0
- data/lib/sneakers/handlers/maxretry.rb +219 -0
- data/lib/sneakers/handlers/oneshot.rb +26 -0
- data/lib/sneakers/metrics/logging_metrics.rb +16 -0
- data/lib/sneakers/metrics/newrelic_metrics.rb +32 -0
- data/lib/sneakers/metrics/null_metrics.rb +13 -0
- data/lib/sneakers/metrics/statsd_metrics.rb +21 -0
- data/lib/sneakers/middleware/config.rb +23 -0
- data/lib/sneakers/publisher.rb +49 -0
- data/lib/sneakers/queue.rb +87 -0
- data/lib/sneakers/runner.rb +91 -0
- data/lib/sneakers/spawner.rb +30 -0
- data/lib/sneakers/support/production_formatter.rb +11 -0
- data/lib/sneakers/support/utils.rb +18 -0
- data/lib/sneakers/tasks.rb +66 -0
- data/lib/sneakers/version.rb +3 -0
- data/lib/sneakers/worker.rb +162 -0
- data/lib/sneakers/workergroup.rb +60 -0
- data/lib/sneakers.rb +125 -0
- data/log/.gitkeep +0 -0
- data/scripts/local_integration +2 -0
- data/scripts/local_worker +3 -0
- data/spec/fixtures/integration_worker.rb +18 -0
- data/spec/fixtures/require_worker.rb +23 -0
- data/spec/gzip_helper.rb +15 -0
- data/spec/sneakers/cli_spec.rb +75 -0
- data/spec/sneakers/concerns/logging_spec.rb +39 -0
- data/spec/sneakers/concerns/metrics_spec.rb +38 -0
- data/spec/sneakers/configuration_spec.rb +97 -0
- data/spec/sneakers/content_encoding_spec.rb +81 -0
- data/spec/sneakers/content_type_spec.rb +81 -0
- data/spec/sneakers/integration_spec.rb +158 -0
- data/spec/sneakers/publisher_spec.rb +179 -0
- data/spec/sneakers/queue_spec.rb +169 -0
- data/spec/sneakers/runner_spec.rb +70 -0
- data/spec/sneakers/sneakers_spec.rb +77 -0
- data/spec/sneakers/support/utils_spec.rb +44 -0
- data/spec/sneakers/tasks/sneakers_run_spec.rb +115 -0
- data/spec/sneakers/worker_handlers_spec.rb +469 -0
- data/spec/sneakers/worker_spec.rb +712 -0
- data/spec/sneakers/workergroup_spec.rb +83 -0
- data/spec/spec_helper.rb +21 -0
- metadata +352 -0
@@ -0,0 +1,712 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'gzip_helper'
|
3
|
+
require 'sneakers'
|
4
|
+
require 'serverengine'
|
5
|
+
|
6
|
+
class DummyWorker
|
7
|
+
include Sneakers::Worker
|
8
|
+
from_queue 'downloads',
|
9
|
+
:exchange_options => {
|
10
|
+
:type => :topic,
|
11
|
+
:durable => false,
|
12
|
+
:auto_delete => true,
|
13
|
+
:arguments => { 'x-arg' => 'value' }
|
14
|
+
},
|
15
|
+
:queue_options => {
|
16
|
+
:durable => false,
|
17
|
+
:auto_delete => true,
|
18
|
+
:exclusive => true,
|
19
|
+
:arguments => { 'x-arg' => 'value' }
|
20
|
+
},
|
21
|
+
:ack => false,
|
22
|
+
:threads => 50,
|
23
|
+
:prefetch => 40,
|
24
|
+
:exchange => 'dummy',
|
25
|
+
:heartbeat => 5
|
26
|
+
|
27
|
+
def work(msg)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class DefaultsWorker
|
32
|
+
include Sneakers::Worker
|
33
|
+
from_queue 'defaults'
|
34
|
+
|
35
|
+
def work(msg)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class AcksWorker
|
40
|
+
include Sneakers::Worker
|
41
|
+
from_queue 'defaults',
|
42
|
+
:ack => true
|
43
|
+
|
44
|
+
def work(msg)
|
45
|
+
if msg == :ack
|
46
|
+
ack!
|
47
|
+
elsif msg == :nack
|
48
|
+
nack!
|
49
|
+
elsif msg == :reject
|
50
|
+
reject!
|
51
|
+
else
|
52
|
+
msg
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class PublishingWorker
|
58
|
+
include Sneakers::Worker
|
59
|
+
from_queue 'defaults',
|
60
|
+
:ack => false,
|
61
|
+
:exchange => 'foochange'
|
62
|
+
|
63
|
+
def work(msg)
|
64
|
+
publish msg, :to_queue => 'target'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class JSONPublishingWorker
|
69
|
+
include Sneakers::Worker
|
70
|
+
from_queue 'defaults',
|
71
|
+
:ack => false,
|
72
|
+
:exchange => 'foochange'
|
73
|
+
|
74
|
+
def work(msg)
|
75
|
+
publish msg, :to_queue => 'target', :content_type => 'application/json'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class GzipPublishingWorker
|
80
|
+
include Sneakers::Worker
|
81
|
+
from_queue 'defaults',
|
82
|
+
:ack => false,
|
83
|
+
:exchange => 'foochange'
|
84
|
+
|
85
|
+
def work(msg)
|
86
|
+
publish msg, :to_queue => 'target', :content_encoding => 'gzip'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class LoggingWorker
|
91
|
+
include Sneakers::Worker
|
92
|
+
from_queue 'defaults',
|
93
|
+
:ack => false
|
94
|
+
|
95
|
+
def work(msg)
|
96
|
+
logger.info "hello"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class JSONWorker
|
101
|
+
include Sneakers::Worker
|
102
|
+
from_queue 'defaults',
|
103
|
+
:ack => false,
|
104
|
+
:content_type => 'application/json'
|
105
|
+
|
106
|
+
def work(msg)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class GzipWorker
|
111
|
+
include Sneakers::Worker
|
112
|
+
from_queue 'defaults',
|
113
|
+
:ack => false,
|
114
|
+
:content_encoding => 'gzip'
|
115
|
+
|
116
|
+
def work(msg)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class MetricsWorker
|
121
|
+
include Sneakers::Worker
|
122
|
+
from_queue 'defaults',
|
123
|
+
:ack => true
|
124
|
+
|
125
|
+
def work(msg)
|
126
|
+
metrics.increment "foobar"
|
127
|
+
msg
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class WithParamsWorker
|
132
|
+
include Sneakers::Worker
|
133
|
+
from_queue 'defaults',
|
134
|
+
:ack => true
|
135
|
+
|
136
|
+
def work_with_params(msg, delivery_info, metadata)
|
137
|
+
msg
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class WithDeprecatedExchangeOptionsWorker
|
142
|
+
include Sneakers::Worker
|
143
|
+
from_queue 'defaults',
|
144
|
+
:durable => false,
|
145
|
+
:exchange_type => :topic,
|
146
|
+
:exchange_arguments => { 'x-arg' => 'value' },
|
147
|
+
:arguments => { 'x-arg2' => 'value2' }
|
148
|
+
|
149
|
+
def work(msg)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
TestPool ||= Concurrent::ImmediateExecutor
|
154
|
+
|
155
|
+
describe Sneakers::Worker do
|
156
|
+
before do
|
157
|
+
@queue = Object.new
|
158
|
+
@exchange = Object.new
|
159
|
+
stub(@queue).name { 'test-queue' }
|
160
|
+
stub(@queue).opts { {} }
|
161
|
+
stub(@queue).exchange { @exchange }
|
162
|
+
|
163
|
+
Sneakers.clear!
|
164
|
+
Sneakers.configure(daemonize: true, log: 'sneakers.log')
|
165
|
+
Sneakers::Worker.configure_metrics
|
166
|
+
end
|
167
|
+
|
168
|
+
describe ".enqueue" do
|
169
|
+
it "publishes a message to the class queue" do
|
170
|
+
message = 'test message'
|
171
|
+
|
172
|
+
mock(Sneakers::Publisher).new(DummyWorker.queue_opts) do
|
173
|
+
mock(Object.new).publish(message, {
|
174
|
+
:routing_key => 'test.routing.key',
|
175
|
+
:to_queue => 'downloads',
|
176
|
+
:content_type => nil,
|
177
|
+
:content_encoding => nil,
|
178
|
+
})
|
179
|
+
end
|
180
|
+
|
181
|
+
DummyWorker.enqueue(message, :routing_key => 'test.routing.key')
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe "#initialize" do
|
186
|
+
describe "builds an internal queue" do
|
187
|
+
it "should build a queue with correct configuration given defaults" do
|
188
|
+
@defaults_q = DefaultsWorker.new.queue
|
189
|
+
_(@defaults_q.name).must_equal('defaults')
|
190
|
+
_(@defaults_q.opts.to_hash).must_equal(
|
191
|
+
:error_reporters => [Sneakers.error_reporters.last],
|
192
|
+
:runner_config_file => nil,
|
193
|
+
:metrics => nil,
|
194
|
+
:daemonize => true,
|
195
|
+
:start_worker_delay => 0.2,
|
196
|
+
:workers => 4,
|
197
|
+
:log => "sneakers.log",
|
198
|
+
:pid_path => "sneakers.pid",
|
199
|
+
:prefetch => 10,
|
200
|
+
:threads => 10,
|
201
|
+
:share_threads => false,
|
202
|
+
:ack => true,
|
203
|
+
:amqp => "amqp://guest:guest@localhost:5672",
|
204
|
+
:vhost => "/",
|
205
|
+
:exchange => "sneakers",
|
206
|
+
:exchange_options => {
|
207
|
+
:type => :direct,
|
208
|
+
:durable => true,
|
209
|
+
:auto_delete => false,
|
210
|
+
:arguments => {}
|
211
|
+
},
|
212
|
+
:queue_options => {
|
213
|
+
:durable => true,
|
214
|
+
:auto_delete => false,
|
215
|
+
:exclusive => false,
|
216
|
+
:arguments => {}
|
217
|
+
},
|
218
|
+
:hooks => {},
|
219
|
+
:handler => Sneakers::Handlers::Oneshot,
|
220
|
+
:heartbeat => 30,
|
221
|
+
:amqp_heartbeat => 30
|
222
|
+
)
|
223
|
+
end
|
224
|
+
|
225
|
+
it "should build a queue with given configuration" do
|
226
|
+
@dummy_q = DummyWorker.new.queue
|
227
|
+
_(@dummy_q.name).must_equal('downloads')
|
228
|
+
_(@dummy_q.opts.to_hash).must_equal(
|
229
|
+
:error_reporters => [Sneakers.error_reporters.last],
|
230
|
+
:runner_config_file => nil,
|
231
|
+
:metrics => nil,
|
232
|
+
:daemonize => true,
|
233
|
+
:start_worker_delay => 0.2,
|
234
|
+
:workers => 4,
|
235
|
+
:log => "sneakers.log",
|
236
|
+
:pid_path => "sneakers.pid",
|
237
|
+
:prefetch => 40,
|
238
|
+
:threads => 50,
|
239
|
+
:share_threads => false,
|
240
|
+
:ack => false,
|
241
|
+
:amqp => "amqp://guest:guest@localhost:5672",
|
242
|
+
:vhost => "/",
|
243
|
+
:exchange => "dummy",
|
244
|
+
:exchange_options => {
|
245
|
+
:type => :topic,
|
246
|
+
:durable => false,
|
247
|
+
:auto_delete => true,
|
248
|
+
:arguments => { 'x-arg' => 'value' }
|
249
|
+
},
|
250
|
+
:queue_options => {
|
251
|
+
:durable => false,
|
252
|
+
:auto_delete => true,
|
253
|
+
:exclusive => true,
|
254
|
+
:arguments => { 'x-arg' => 'value' }
|
255
|
+
},
|
256
|
+
:hooks => {},
|
257
|
+
:handler => Sneakers::Handlers::Oneshot,
|
258
|
+
:heartbeat => 5,
|
259
|
+
:amqp_heartbeat => 30
|
260
|
+
)
|
261
|
+
end
|
262
|
+
|
263
|
+
it "should build a queue with correct configuration given deprecated exchange options" do
|
264
|
+
@deprecated_exchange_opts_q = WithDeprecatedExchangeOptionsWorker.new.queue
|
265
|
+
_(@deprecated_exchange_opts_q.name).must_equal('defaults')
|
266
|
+
_(@deprecated_exchange_opts_q.opts.to_hash).must_equal(
|
267
|
+
:error_reporters => [Sneakers.error_reporters.last],
|
268
|
+
:runner_config_file => nil,
|
269
|
+
:metrics => nil,
|
270
|
+
:daemonize => true,
|
271
|
+
:start_worker_delay => 0.2,
|
272
|
+
:workers => 4,
|
273
|
+
:log => "sneakers.log",
|
274
|
+
:pid_path => "sneakers.pid",
|
275
|
+
:prefetch => 10,
|
276
|
+
:threads => 10,
|
277
|
+
:share_threads => false,
|
278
|
+
:ack => true,
|
279
|
+
:amqp => "amqp://guest:guest@localhost:5672",
|
280
|
+
:vhost => "/",
|
281
|
+
:exchange => "sneakers",
|
282
|
+
:exchange_options => {
|
283
|
+
:type => :topic,
|
284
|
+
:durable => false,
|
285
|
+
:auto_delete => false,
|
286
|
+
:arguments => { 'x-arg' => 'value' }
|
287
|
+
},
|
288
|
+
:queue_options => {
|
289
|
+
:durable => false,
|
290
|
+
:auto_delete => false,
|
291
|
+
:exclusive => false,
|
292
|
+
:arguments => { 'x-arg2' => 'value2' }
|
293
|
+
},
|
294
|
+
:hooks => {},
|
295
|
+
:handler => Sneakers::Handlers::Oneshot,
|
296
|
+
:heartbeat => 30,
|
297
|
+
:amqp_heartbeat => 30
|
298
|
+
)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
describe "initializes worker" do
|
303
|
+
it "should generate a worker id" do
|
304
|
+
_(DummyWorker.new.id).must_match(/^worker-/)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
describe 'when connection provided' do
|
309
|
+
before do
|
310
|
+
@connection = Bunny.new(host: 'any-host.local')
|
311
|
+
Sneakers.configure(
|
312
|
+
exchange: 'some-exch',
|
313
|
+
exchange_options: { type: :direct },
|
314
|
+
connection: @connection,
|
315
|
+
)
|
316
|
+
end
|
317
|
+
|
318
|
+
it "should build a queue with given connection" do
|
319
|
+
@dummy_q = DummyWorker.new.queue
|
320
|
+
_(@dummy_q.opts[:connection]).must_equal(@connection)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
|
326
|
+
describe "#run" do
|
327
|
+
it "should subscribe on internal queue" do
|
328
|
+
q = Object.new
|
329
|
+
w = DummyWorker.new(q)
|
330
|
+
mock(q).subscribe(w).once #XXX once?
|
331
|
+
stub(q).name{ "test" }
|
332
|
+
stub(q).opts { nil }
|
333
|
+
w.run
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
describe "#stop" do
|
338
|
+
it "should unsubscribe from internal queue" do
|
339
|
+
q = Object.new
|
340
|
+
mock(q).unsubscribe.once #XXX once?
|
341
|
+
stub(q).name { 'test-queue' }
|
342
|
+
stub(q).opts {nil}
|
343
|
+
w = DummyWorker.new(q)
|
344
|
+
w.stop
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
|
349
|
+
describe "#do_work" do
|
350
|
+
it "should perform worker's work" do
|
351
|
+
w = DummyWorker.new(@queue, TestPool.new)
|
352
|
+
mock(w).work("msg").once
|
353
|
+
w.do_work(nil, nil, "msg", nil)
|
354
|
+
end
|
355
|
+
|
356
|
+
describe 'content type based deserialization' do
|
357
|
+
before do
|
358
|
+
Sneakers::ContentType.register(
|
359
|
+
content_type: 'application/json',
|
360
|
+
serializer: ->(_) {},
|
361
|
+
deserializer: ->(payload) { JSON.parse(payload) },
|
362
|
+
)
|
363
|
+
end
|
364
|
+
|
365
|
+
after do
|
366
|
+
Sneakers::ContentType.reset!
|
367
|
+
end
|
368
|
+
|
369
|
+
it 'should use the registered deserializer if the content type is in the metadata' do
|
370
|
+
w = DummyWorker.new(@queue, TestPool.new)
|
371
|
+
mock(w).work({'foo' => 'bar'}).once
|
372
|
+
w.do_work(nil, { content_type: 'application/json' }, '{"foo":"bar"}', nil)
|
373
|
+
end
|
374
|
+
|
375
|
+
it 'should use the registered deserializer if the content type is in the queue options' do
|
376
|
+
w = JSONWorker.new(@queue, TestPool.new)
|
377
|
+
mock(w).work({'foo' => 'bar'}).once
|
378
|
+
w.do_work(nil, {}, '{"foo":"bar"}', nil)
|
379
|
+
end
|
380
|
+
|
381
|
+
it 'should use the deserializer from the queue options even if the metadata has a different content type' do
|
382
|
+
w = JSONWorker.new(@queue, TestPool.new)
|
383
|
+
mock(w).work({'foo' => 'bar'}).once
|
384
|
+
w.do_work(nil, { content_type: 'not/real' }, '{"foo":"bar"}', nil)
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
describe 'content encoding based decoding' do
|
389
|
+
before do
|
390
|
+
Sneakers::ContentEncoding.register(
|
391
|
+
content_encoding: 'gzip',
|
392
|
+
encoder: ->(_) {},
|
393
|
+
decoder: ->(payload) { gzip_decompress(payload) },
|
394
|
+
)
|
395
|
+
end
|
396
|
+
|
397
|
+
after do
|
398
|
+
Sneakers::ContentEncoding.reset!
|
399
|
+
end
|
400
|
+
|
401
|
+
it 'should use the registered decoder if the content encoding is in the metadata' do
|
402
|
+
w = DummyWorker.new(@queue, TestPool.new)
|
403
|
+
mock(w).work('foobar').once
|
404
|
+
w.do_work(nil, { content_encoding: 'gzip' }, gzip_compress('foobar'), nil)
|
405
|
+
end
|
406
|
+
|
407
|
+
it 'should use the registered decoder if the content encoding is in the queue options' do
|
408
|
+
w = GzipWorker.new(@queue, TestPool.new)
|
409
|
+
mock(w).work('foobar').once
|
410
|
+
w.do_work(nil, {}, gzip_compress('foobar'), nil)
|
411
|
+
end
|
412
|
+
|
413
|
+
it 'should use the decoder from the queue options even if the metadata has a different content encoding' do
|
414
|
+
w = GzipWorker.new(@queue, TestPool.new)
|
415
|
+
mock(w).work('foobar').once
|
416
|
+
w.do_work(nil, { content_encoding: 'not/real' }, gzip_compress('foobar'), nil)
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
it "should catch runtime exceptions from a bad work" do
|
421
|
+
w = AcksWorker.new(@queue, TestPool.new)
|
422
|
+
mock(w).work("msg").once{ raise "foo" }
|
423
|
+
handler = Object.new
|
424
|
+
header = Object.new
|
425
|
+
mock(handler).error(header, nil, "msg", anything)
|
426
|
+
mock(w.logger).error(/\[Exception error="foo" error_class=RuntimeError worker_class=AcksWorker backtrace=.*/)
|
427
|
+
w.do_work(header, nil, "msg", handler)
|
428
|
+
end
|
429
|
+
|
430
|
+
it "should catch script exceptions from a bad work" do
|
431
|
+
w = AcksWorker.new(@queue, TestPool.new)
|
432
|
+
mock(w).work("msg").once{ raise ScriptError }
|
433
|
+
handler = Object.new
|
434
|
+
header = Object.new
|
435
|
+
mock(handler).error(header, nil, "msg", anything)
|
436
|
+
mock(w.logger).error(/\[Exception error="ScriptError" error_class=ScriptError worker_class=AcksWorker backtrace=.*/)
|
437
|
+
w.do_work(header, nil, "msg", handler)
|
438
|
+
end
|
439
|
+
|
440
|
+
it "should log exceptions from workers" do
|
441
|
+
handler = Object.new
|
442
|
+
header = Object.new
|
443
|
+
w = AcksWorker.new(@queue, TestPool.new)
|
444
|
+
mock(w).work("msg").once{ raise "foo" }
|
445
|
+
mock(w.logger).error(/error="foo" error_class=RuntimeError worker_class=AcksWorker backtrace=/)
|
446
|
+
mock(handler).error(header, nil, "msg", anything)
|
447
|
+
w.do_work(header, nil, "msg", handler)
|
448
|
+
end
|
449
|
+
|
450
|
+
describe 'middleware' do
|
451
|
+
let(:middleware) do
|
452
|
+
Class.new do
|
453
|
+
def initialize(app, *args)
|
454
|
+
@app = app
|
455
|
+
end
|
456
|
+
|
457
|
+
def call(deserialized_msg, delivery_info, metadata, handler)
|
458
|
+
@app.call(deserialized_msg, delivery_info, metadata, handler)
|
459
|
+
end
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
let(:worker) do
|
464
|
+
Class.new do
|
465
|
+
include Sneakers::Worker
|
466
|
+
from_queue 'defaults', ack: false
|
467
|
+
|
468
|
+
def work_with_params(msg, delivery_info, metadata)
|
469
|
+
msg
|
470
|
+
end
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
before do
|
475
|
+
Sneakers.middleware.use(middleware, 'args')
|
476
|
+
|
477
|
+
@delivery_info = Object.new
|
478
|
+
@metadata = Object.new
|
479
|
+
stub(@metadata).[](:content_type) { 'some/fake' }
|
480
|
+
stub(@metadata).[](:content_encoding) { 'some/fake' }
|
481
|
+
@message = Object.new
|
482
|
+
@handler = Object.new
|
483
|
+
end
|
484
|
+
|
485
|
+
after do
|
486
|
+
Sneakers.middleware.delete(middleware)
|
487
|
+
end
|
488
|
+
|
489
|
+
it 'should process job and call #work_with_params/#work' do
|
490
|
+
w = worker.new(@queue, TestPool.new)
|
491
|
+
mock(w).work_with_params(@message, @delivery_info, @metadata).once
|
492
|
+
|
493
|
+
w.do_work(@delivery_info, @metadata, @message, @handler)
|
494
|
+
end
|
495
|
+
|
496
|
+
it "should call registered middleware" do
|
497
|
+
mock.proxy(middleware).new(instance_of(Proc), 'args').once do |res|
|
498
|
+
mock.proxy(res).call(@message, @delivery_info, @metadata, @handler).once
|
499
|
+
end
|
500
|
+
|
501
|
+
w = worker.new(@queue, TestPool.new)
|
502
|
+
w.do_work(@delivery_info, @metadata, @message, @handler)
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
describe "with ack" do
|
507
|
+
before do
|
508
|
+
@delivery_info = Object.new
|
509
|
+
stub(@delivery_info).delivery_tag{ "tag" }
|
510
|
+
|
511
|
+
@worker = AcksWorker.new(@queue, TestPool.new)
|
512
|
+
end
|
513
|
+
|
514
|
+
it "should work and handle acks" do
|
515
|
+
handler = Object.new
|
516
|
+
mock(handler).acknowledge(@delivery_info, nil, :ack)
|
517
|
+
|
518
|
+
@worker.do_work(@delivery_info, nil, :ack, handler)
|
519
|
+
end
|
520
|
+
|
521
|
+
it "should work and handle rejects" do
|
522
|
+
handler = Object.new
|
523
|
+
mock(handler).reject(@delivery_info, nil, :reject)
|
524
|
+
|
525
|
+
@worker.do_work(@delivery_info, nil, :reject, handler)
|
526
|
+
end
|
527
|
+
|
528
|
+
it "should work and handle requeues" do
|
529
|
+
handler = Object.new
|
530
|
+
mock(handler).reject(@delivery_info, nil, :requeue, true)
|
531
|
+
|
532
|
+
@worker.do_work(@delivery_info, nil, :requeue, handler)
|
533
|
+
end
|
534
|
+
|
535
|
+
it "should work and handle user code errors" do
|
536
|
+
handler = Object.new
|
537
|
+
mock(handler).error(@delivery_info, nil, :error, anything)
|
538
|
+
|
539
|
+
@worker.do_work(@delivery_info, nil, :error, handler)
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
describe "without ack" do
|
544
|
+
it "should work and not care about acking if not ack" do
|
545
|
+
handler = Object.new
|
546
|
+
mock(handler).reject(anything).never
|
547
|
+
mock(handler).acknowledge(anything).never
|
548
|
+
|
549
|
+
w = DummyWorker.new(@queue, TestPool.new)
|
550
|
+
w.do_work(nil, nil, 'msg', handler)
|
551
|
+
end
|
552
|
+
end
|
553
|
+
end
|
554
|
+
|
555
|
+
|
556
|
+
describe 'publish' do
|
557
|
+
it 'should be able to publish a message from working context' do
|
558
|
+
w = PublishingWorker.new(@queue, TestPool.new)
|
559
|
+
mock(@exchange).publish('msg', :routing_key => 'target').once
|
560
|
+
w.do_work(nil, nil, 'msg', nil)
|
561
|
+
end
|
562
|
+
|
563
|
+
it 'should be able to publish arbitrary metadata' do
|
564
|
+
w = PublishingWorker.new(@queue, TestPool.new)
|
565
|
+
mock(@exchange).publish('msg', :routing_key => 'target', :expiration => 1).once
|
566
|
+
w.publish 'msg', :to_queue => 'target', :expiration => 1
|
567
|
+
end
|
568
|
+
|
569
|
+
describe 'content_type based serialization' do
|
570
|
+
before do
|
571
|
+
Sneakers::ContentType.register(
|
572
|
+
content_type: 'application/json',
|
573
|
+
serializer: ->(payload) { JSON.dump(payload) },
|
574
|
+
deserializer: ->(_) {},
|
575
|
+
)
|
576
|
+
end
|
577
|
+
|
578
|
+
after do
|
579
|
+
Sneakers::ContentType.reset!
|
580
|
+
end
|
581
|
+
|
582
|
+
it 'should be able to publish a message from working context' do
|
583
|
+
w = JSONPublishingWorker.new(@queue, TestPool.new)
|
584
|
+
mock(@exchange).publish('{"foo":"bar"}', :routing_key => 'target', :content_type => 'application/json').once
|
585
|
+
w.do_work(nil, {}, {'foo' => 'bar'}, nil)
|
586
|
+
end
|
587
|
+
end
|
588
|
+
|
589
|
+
describe 'content_encoding based encoding' do
|
590
|
+
before do
|
591
|
+
Sneakers::ContentEncoding.register(
|
592
|
+
content_encoding: 'gzip',
|
593
|
+
encoder: ->(payload) { gzip_compress(payload) },
|
594
|
+
decoder: ->(_) {},
|
595
|
+
)
|
596
|
+
end
|
597
|
+
|
598
|
+
after do
|
599
|
+
Sneakers::ContentEncoding.reset!
|
600
|
+
end
|
601
|
+
|
602
|
+
it 'should be able to publish a message from working context' do
|
603
|
+
w = GzipPublishingWorker.new(@queue, TestPool.new)
|
604
|
+
mock(@exchange).publish(gzip_compress('foobar'), :routing_key => 'target', :content_encoding => 'gzip').once
|
605
|
+
w.do_work(nil, {}, 'foobar', nil)
|
606
|
+
end
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
|
611
|
+
describe 'Logging' do
|
612
|
+
it 'should be able to use the logging facilities' do
|
613
|
+
log = Logger.new('/dev/null')
|
614
|
+
mock(log).debug(anything).once
|
615
|
+
mock(log).info("hello").once
|
616
|
+
Sneakers::Worker.configure_logger(log)
|
617
|
+
|
618
|
+
w = LoggingWorker.new(@queue, TestPool.new)
|
619
|
+
w.do_work(nil,nil,'msg',nil)
|
620
|
+
end
|
621
|
+
|
622
|
+
it 'has a helper to constuct log prefix values' do
|
623
|
+
w = DummyWorker.new(@queue, TestPool.new)
|
624
|
+
w.instance_variable_set(:@id, 'worker-id')
|
625
|
+
m = w.log_msg('foo')
|
626
|
+
_(w.log_msg('foo')).must_match(/\[worker-id\]\[#<Thread:.*>\]\[test-queue\]\[\{\}\] foo/)
|
627
|
+
end
|
628
|
+
|
629
|
+
describe '#worker_error' do
|
630
|
+
it 'only logs backtraces if present' do
|
631
|
+
w = DummyWorker.new(@queue, TestPool.new)
|
632
|
+
mock(w.logger).warn('cuz')
|
633
|
+
mock(w.logger).error(/\[Exception error="boom!" error_class=RuntimeError worker_class=DummyWorker\]/)
|
634
|
+
w.worker_error(RuntimeError.new('boom!'), 'cuz')
|
635
|
+
end
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
|
640
|
+
describe 'Metrics' do
|
641
|
+
before do
|
642
|
+
@handler = Object.new
|
643
|
+
@header = Object.new
|
644
|
+
|
645
|
+
# We don't care how these are called, we're focusing on metrics here.
|
646
|
+
stub(@handler).acknowledge
|
647
|
+
stub(@handler).reject
|
648
|
+
stub(@handler).error
|
649
|
+
stub(@handler).noop
|
650
|
+
|
651
|
+
@delivery_info = Object.new
|
652
|
+
stub(@delivery_info).delivery_tag { "tag" }
|
653
|
+
|
654
|
+
@w = MetricsWorker.new(@queue, TestPool.new)
|
655
|
+
mock(@w.metrics).increment("work.MetricsWorker.started").once
|
656
|
+
mock(@w.metrics).increment("work.MetricsWorker.ended").once
|
657
|
+
mock(@w.metrics).timing("work.MetricsWorker.time").yields.once
|
658
|
+
end
|
659
|
+
|
660
|
+
it 'should be able to meter acks' do
|
661
|
+
mock(@w.metrics).increment("foobar").once
|
662
|
+
mock(@w.metrics).increment("work.MetricsWorker.handled.ack").once
|
663
|
+
@w.do_work(@delivery_info, nil, :ack, @handler)
|
664
|
+
end
|
665
|
+
|
666
|
+
it 'should be able to meter rejects' do
|
667
|
+
mock(@w.metrics).increment("foobar").once
|
668
|
+
mock(@w.metrics).increment("work.MetricsWorker.handled.reject").once
|
669
|
+
@w.do_work(@header, nil, :reject, @handler)
|
670
|
+
end
|
671
|
+
|
672
|
+
it 'should be able to meter requeue' do
|
673
|
+
mock(@w.metrics).increment("foobar").once
|
674
|
+
mock(@w.metrics).increment("work.MetricsWorker.handled.requeue").once
|
675
|
+
@w.do_work(@header, nil, :requeue, @handler)
|
676
|
+
end
|
677
|
+
|
678
|
+
it 'should be able to meter errors' do
|
679
|
+
mock(@w.metrics).increment("work.MetricsWorker.handled.error").once
|
680
|
+
mock(@w).work('msg'){ raise :error }
|
681
|
+
@w.do_work(@delivery_info, nil, 'msg', @handler)
|
682
|
+
end
|
683
|
+
|
684
|
+
it 'defaults to noop when no response is specified' do
|
685
|
+
mock(@w.metrics).increment("foobar").once
|
686
|
+
mock(@w.metrics).increment("work.MetricsWorker.handled.noop").once
|
687
|
+
@w.do_work(@header, nil, nil, @handler)
|
688
|
+
end
|
689
|
+
end
|
690
|
+
|
691
|
+
|
692
|
+
|
693
|
+
describe 'With Params' do
|
694
|
+
before do
|
695
|
+
@props = { :foo => 1 }
|
696
|
+
@handler = Object.new
|
697
|
+
@header = Object.new
|
698
|
+
|
699
|
+
@delivery_info = Object.new
|
700
|
+
|
701
|
+
stub(@handler).noop(@delivery_info, {:foo => 1}, :ack)
|
702
|
+
|
703
|
+
@w = WithParamsWorker.new(@queue, TestPool.new)
|
704
|
+
mock(@w.metrics).timing("work.WithParamsWorker.time").yields.once
|
705
|
+
end
|
706
|
+
|
707
|
+
it 'should call work_with_params and not work' do
|
708
|
+
mock(@w).work_with_params(:ack, @delivery_info, {:foo => 1}).once
|
709
|
+
@w.do_work(@delivery_info, {:foo => 1 }, :ack, @handler)
|
710
|
+
end
|
711
|
+
end
|
712
|
+
end
|