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.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +24 -0
  3. data/.gitignore +12 -0
  4. data/ChangeLog.md +142 -0
  5. data/Dockerfile +24 -0
  6. data/Dockerfile.slim +20 -0
  7. data/Gemfile +8 -0
  8. data/Guardfile +8 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +209 -0
  11. data/Rakefile +12 -0
  12. data/bin/sneakers +6 -0
  13. data/docker-compose.yml +24 -0
  14. data/examples/benchmark_worker.rb +22 -0
  15. data/examples/max_retry_handler.rb +68 -0
  16. data/examples/metrics_worker.rb +34 -0
  17. data/examples/middleware_worker.rb +36 -0
  18. data/examples/newrelic_metrics_worker.rb +40 -0
  19. data/examples/profiling_worker.rb +69 -0
  20. data/examples/sneakers.conf.rb.example +11 -0
  21. data/examples/title_scraper.rb +36 -0
  22. data/examples/workflow_worker.rb +23 -0
  23. data/kicks.gemspec +44 -0
  24. data/lib/sneakers/cli.rb +122 -0
  25. data/lib/sneakers/concerns/logging.rb +34 -0
  26. data/lib/sneakers/concerns/metrics.rb +34 -0
  27. data/lib/sneakers/configuration.rb +125 -0
  28. data/lib/sneakers/content_encoding.rb +47 -0
  29. data/lib/sneakers/content_type.rb +47 -0
  30. data/lib/sneakers/error_reporter.rb +33 -0
  31. data/lib/sneakers/errors.rb +2 -0
  32. data/lib/sneakers/handlers/maxretry.rb +219 -0
  33. data/lib/sneakers/handlers/oneshot.rb +26 -0
  34. data/lib/sneakers/metrics/logging_metrics.rb +16 -0
  35. data/lib/sneakers/metrics/newrelic_metrics.rb +32 -0
  36. data/lib/sneakers/metrics/null_metrics.rb +13 -0
  37. data/lib/sneakers/metrics/statsd_metrics.rb +21 -0
  38. data/lib/sneakers/middleware/config.rb +23 -0
  39. data/lib/sneakers/publisher.rb +49 -0
  40. data/lib/sneakers/queue.rb +87 -0
  41. data/lib/sneakers/runner.rb +91 -0
  42. data/lib/sneakers/spawner.rb +30 -0
  43. data/lib/sneakers/support/production_formatter.rb +11 -0
  44. data/lib/sneakers/support/utils.rb +18 -0
  45. data/lib/sneakers/tasks.rb +66 -0
  46. data/lib/sneakers/version.rb +3 -0
  47. data/lib/sneakers/worker.rb +162 -0
  48. data/lib/sneakers/workergroup.rb +60 -0
  49. data/lib/sneakers.rb +125 -0
  50. data/log/.gitkeep +0 -0
  51. data/scripts/local_integration +2 -0
  52. data/scripts/local_worker +3 -0
  53. data/spec/fixtures/integration_worker.rb +18 -0
  54. data/spec/fixtures/require_worker.rb +23 -0
  55. data/spec/gzip_helper.rb +15 -0
  56. data/spec/sneakers/cli_spec.rb +75 -0
  57. data/spec/sneakers/concerns/logging_spec.rb +39 -0
  58. data/spec/sneakers/concerns/metrics_spec.rb +38 -0
  59. data/spec/sneakers/configuration_spec.rb +97 -0
  60. data/spec/sneakers/content_encoding_spec.rb +81 -0
  61. data/spec/sneakers/content_type_spec.rb +81 -0
  62. data/spec/sneakers/integration_spec.rb +158 -0
  63. data/spec/sneakers/publisher_spec.rb +179 -0
  64. data/spec/sneakers/queue_spec.rb +169 -0
  65. data/spec/sneakers/runner_spec.rb +70 -0
  66. data/spec/sneakers/sneakers_spec.rb +77 -0
  67. data/spec/sneakers/support/utils_spec.rb +44 -0
  68. data/spec/sneakers/tasks/sneakers_run_spec.rb +115 -0
  69. data/spec/sneakers/worker_handlers_spec.rb +469 -0
  70. data/spec/sneakers/worker_spec.rb +712 -0
  71. data/spec/sneakers/workergroup_spec.rb +83 -0
  72. data/spec/spec_helper.rb +21 -0
  73. 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