sneakers 0.0.1 → 0.0.2

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 (43) hide show
  1. data/.gitignore +6 -17
  2. data/Gemfile +0 -1
  3. data/Gemfile.lock +152 -0
  4. data/Guardfile +8 -0
  5. data/LICENSE.txt +2 -2
  6. data/README.md +140 -9
  7. data/Rakefile +9 -0
  8. data/bin/sneakers +5 -0
  9. data/examples/benchmark_worker.rb +21 -0
  10. data/examples/metrics_worker.rb +28 -0
  11. data/examples/profiling_worker.rb +55 -0
  12. data/examples/sneakers.conf.rb.example +10 -0
  13. data/examples/title_scraper.rb +20 -0
  14. data/examples/workflow_worker.rb +24 -0
  15. data/lib/sneakers.rb +80 -1
  16. data/lib/sneakers/cli.rb +107 -0
  17. data/lib/sneakers/concerns/logging.rb +34 -0
  18. data/lib/sneakers/concerns/metrics.rb +34 -0
  19. data/lib/sneakers/handlers/oneshot.rb +25 -0
  20. data/lib/sneakers/metrics/logging_metrics.rb +16 -0
  21. data/lib/sneakers/metrics/null_metrics.rb +13 -0
  22. data/lib/sneakers/metrics/statsd_metrics.rb +21 -0
  23. data/lib/sneakers/publisher.rb +35 -0
  24. data/lib/sneakers/queue.rb +42 -0
  25. data/lib/sneakers/runner.rb +20 -0
  26. data/lib/sneakers/runner_config.rb +55 -0
  27. data/lib/sneakers/support/production_formatter.rb +11 -0
  28. data/lib/sneakers/support/queue_name.rb +14 -0
  29. data/lib/sneakers/support/utils.rb +18 -0
  30. data/lib/sneakers/tasks.rb +34 -0
  31. data/lib/sneakers/version.rb +1 -1
  32. data/lib/sneakers/worker.rb +120 -0
  33. data/lib/sneakers/workergroup.rb +47 -0
  34. data/sneakers.gemspec +26 -16
  35. data/spec/fixtures/require_worker.rb +17 -0
  36. data/spec/sneakers/cli_spec.rb +53 -0
  37. data/spec/sneakers/concerns/logging.rb +39 -0
  38. data/spec/sneakers/concerns/metrics.rb +38 -0
  39. data/spec/sneakers/publisher_spec.rb +37 -0
  40. data/spec/sneakers/queue_spec.rb +42 -0
  41. data/spec/sneakers/worker_spec.rb +348 -0
  42. data/spec/spec_helper.rb +10 -0
  43. metadata +216 -14
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+ require 'sneakers'
3
+ require 'logger'
4
+
5
+
6
+ class Foobar
7
+ include Sneakers::Concerns::Logging
8
+ end
9
+
10
+ describe Sneakers::Concerns::Logging do
11
+ describe ".configure" do
12
+ before do
13
+ Foobar.logger = nil
14
+ end
15
+
16
+ it "should configure a default logger when included" do
17
+ Foobar.logger.must_be_nil
18
+ Foobar.configure_logger
19
+ Foobar.logger.wont_be_nil
20
+ Foobar.logger.formatter.must_equal Sneakers::Concerns::Logging::ProductionFormatter
21
+ end
22
+
23
+ it "should supply accessible instance logger" do
24
+ Foobar.logger.must_be_nil
25
+ Foobar.configure_logger
26
+ f = Foobar.new
27
+ f.logger.must_equal Foobar.logger
28
+ f.logger.wont_be_nil
29
+ end
30
+
31
+ it "should configure a given logger when specified" do
32
+ Foobar.logger.must_be_nil
33
+ log = Logger.new(STDOUT)
34
+ Foobar.configure_logger(log)
35
+ Foobar.logger.must_equal log
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+ require 'sneakers'
3
+ require 'logger'
4
+
5
+
6
+ class Foometrics
7
+ include Sneakers::Concerns::Metrics
8
+ end
9
+
10
+ describe Sneakers::Concerns::Metrics do
11
+ describe ".configure" do
12
+ before do
13
+ Foometrics.metrics = nil
14
+ end
15
+
16
+ it "should configure a default logger when included" do
17
+ Foometrics.metrics.must_be_nil
18
+ Foometrics.configure_metrics
19
+ Foometrics.metrics.wont_be_nil
20
+ end
21
+
22
+ it "should supply accessible instance logger" do
23
+ Foometrics.metrics.must_be_nil
24
+ Foometrics.configure_metrics
25
+ f = Foometrics.new
26
+ f.metrics.must_equal Foometrics.metrics
27
+ f.metrics.wont_be_nil
28
+ end
29
+
30
+ it "should configure a given metrics when specified" do
31
+ Foometrics.metrics.must_be_nil
32
+ o = Object.new
33
+ Foometrics.configure_metrics(o)
34
+ Foometrics.metrics.must_equal o
35
+ end
36
+ end
37
+ end
38
+
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+ require 'sneakers'
3
+
4
+
5
+ describe Sneakers::Publisher do
6
+ before do
7
+ Sneakers.configure(:env => 'test')
8
+ end
9
+
10
+ describe "#publish" do
11
+ it "should publish a message to an exchange" do
12
+ xchg = Object.new
13
+ mock(xchg).publish("test msg", :routing_key => "downloads_test")
14
+
15
+ p = Sneakers::Publisher.new
16
+ p.exchange = xchg
17
+
18
+ mock(p).ensure_connection!{}
19
+ p.publish("test msg", :to_queue => 'downloads')
20
+ end
21
+
22
+ it "should not reconnect if already connected" do
23
+ xchg = Object.new
24
+ mock(xchg).publish("test msg", :routing_key => "downloads_test")
25
+
26
+ p = Sneakers::Publisher.new
27
+ p.exchange = xchg
28
+ mock(p).connected?{true}
29
+ mock(p).ensure_connection!.times(0)
30
+
31
+ p.publish("test msg", :to_queue => 'downloads')
32
+ end
33
+ end
34
+
35
+
36
+ end
37
+
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+ require 'sneakers'
3
+
4
+
5
+
6
+ describe Sneakers::Queue do
7
+ before do
8
+ Sneakers.configure(:env => 'test')
9
+ end
10
+
11
+ describe "#subscribe" do
12
+ it "should setup a bunny queue according to configuration values" do
13
+ q = Sneakers::Queue.new("downloads",
14
+ :prefetch => 25,
15
+ :durable => true,
16
+ :ack => true,
17
+ :heartbeat_interval => 2,
18
+ :exchange => "sneakers"
19
+ )
20
+ mkbunny = Object.new
21
+ mkchan = Object.new
22
+ mkex = Object.new
23
+ mkqueue = Object.new
24
+
25
+ mock(mkbunny).start {}
26
+ mock(mkbunny).create_channel{ mkchan }
27
+ mock(Bunny).new(:heartbeat_interval => 2){ mkbunny }
28
+
29
+ mock(mkchan).prefetch(25)
30
+ mock(mkchan).exchange("sneakers", :type => :direct, :durable => true){ mkex }
31
+ mock(mkchan).queue("downloads", :durable => true){ mkqueue }
32
+
33
+ mock(mkqueue).bind(mkex, :routing_key => "downloads")
34
+ mock(mkqueue).subscribe(:block => false, :ack => true)
35
+
36
+ q.subscribe(Object.new)
37
+ end
38
+ end
39
+
40
+
41
+ end
42
+
@@ -0,0 +1,348 @@
1
+ require 'spec_helper'
2
+ require 'sneakers'
3
+ require 'timeout'
4
+
5
+
6
+ class DummyWorker
7
+ include Sneakers::Worker
8
+ from_queue 'downloads',
9
+ :env => 'test',
10
+ :durable => false,
11
+ :ack => false,
12
+ :threads => 50,
13
+ :prefetch => 40,
14
+ :timeout_job_after => 1,
15
+ :exchange => 'dummy',
16
+ :heartbeat_interval => 5
17
+
18
+ def work(msg)
19
+ end
20
+ end
21
+
22
+ class DefaultsWorker
23
+ include Sneakers::Worker
24
+ from_queue 'defaults'
25
+
26
+ def work(msg)
27
+ end
28
+ end
29
+
30
+ class TimeoutWorker
31
+ include Sneakers::Worker
32
+ from_queue 'defaults',
33
+ :timeout_job_after => 0.5,
34
+ :ack => true
35
+
36
+ def work(msg)
37
+ end
38
+ end
39
+
40
+ class AcksWorker
41
+ include Sneakers::Worker
42
+ from_queue 'defaults',
43
+ :ack => true
44
+
45
+ def work(msg)
46
+ if msg == :ack
47
+ ack!
48
+ elsif msg == :nack
49
+ nack!
50
+ elsif msg == :reject
51
+ reject!
52
+ else
53
+ msg
54
+ end
55
+ end
56
+ end
57
+
58
+ class PublishingWorker
59
+ include Sneakers::Worker
60
+ from_queue 'defaults',
61
+ :ack => false,
62
+ :exchange => 'foochange'
63
+
64
+ def work(msg)
65
+ publish msg, :to_queue => 'target'
66
+ end
67
+ end
68
+
69
+
70
+
71
+ class LoggingWorker
72
+ include Sneakers::Worker
73
+ from_queue 'defaults',
74
+ :ack => false
75
+
76
+ def work(msg)
77
+ logger.info "hello"
78
+ end
79
+ end
80
+
81
+
82
+ class MetricsWorker
83
+ include Sneakers::Worker
84
+ from_queue 'defaults',
85
+ :ack => true,
86
+ :timeout_job_after => 0.5
87
+
88
+ def work(msg)
89
+ metrics.increment "foobar"
90
+ msg
91
+ end
92
+ end
93
+
94
+
95
+
96
+ class TestPool
97
+ def process(*args,&block)
98
+ block.call
99
+ end
100
+ end
101
+
102
+ class TestHandler
103
+ def acknowledge(tag); end
104
+ def reject(tag); end
105
+ def error(tag, err); end
106
+ def timeout(tag); end
107
+ end
108
+
109
+ def with_test_queuefactory(ctx, ack=true, msg=nil, nowork=false)
110
+ qf = Object.new
111
+ q = Object.new
112
+ s = Object.new
113
+ hdr = Object.new
114
+ mock(qf).build_queue(anything, anything, anything) { q }
115
+ mock(q).subscribe(anything){ s }
116
+
117
+ mock(s).each(anything) { |h,b| b.call(hdr, msg) unless nowork }
118
+ mock(hdr).ack{true} if !nowork && ack
119
+ mock(hdr).reject{true} if !nowork && !ack
120
+
121
+ mock(ctx).queue_factory { qf } # should return our own
122
+ end
123
+
124
+ describe Sneakers::Worker do
125
+ before do
126
+ @queue = Object.new
127
+ stub(@queue).name { 'test-queue' }
128
+ stub(@queue).opts { {} }
129
+
130
+ Sneakers.configure(:env => 'test')
131
+ Sneakers::Worker.configure_logger(Logger.new('/dev/null'))
132
+ Sneakers::Worker.configure_metrics
133
+ end
134
+
135
+ describe "#initialize" do
136
+ describe "builds an internal queue" do
137
+ before do
138
+ @dummy_q = DummyWorker.new.queue
139
+ @defaults_q = DefaultsWorker.new.queue
140
+ end
141
+
142
+ it "should build a queue with correct configuration given defaults" do
143
+ @defaults_q.name.must_equal('defaults_test')
144
+ @defaults_q.opts.must_equal(
145
+ :durable => true,
146
+ :ack => true,
147
+ :prefetch => 10,
148
+ :heartbeat_interval => 2,
149
+ :exchange => 'sneakers'
150
+ )
151
+ end
152
+
153
+ it "should build a queue with given configuration" do
154
+ @dummy_q.name.must_equal('downloads_test')
155
+ @dummy_q.opts.must_equal(
156
+ :durable => false,
157
+ :ack => false,
158
+ :prefetch => 40,
159
+ :heartbeat_interval => 5,
160
+ :exchange => 'dummy'
161
+ )
162
+ end
163
+ end
164
+
165
+ describe "initializes worker" do
166
+ it "should generate a worker id" do
167
+ DummyWorker.new.id.must_match(/^worker-/)
168
+ end
169
+ end
170
+ end
171
+
172
+
173
+ describe "#run" do
174
+ it "should subscribe on internal queue" do
175
+ q = Object.new
176
+ w = DummyWorker.new(q)
177
+ mock(q).subscribe(w).once #XXX once?
178
+ stub(q).name{ "test" }
179
+ stub(q).opts { nil }
180
+ w.run
181
+ end
182
+ end
183
+
184
+ describe "#stop" do
185
+ it "should unsubscribe from internal queue" do
186
+ q = Object.new
187
+ mock(q).unsubscribe.once #XXX once?
188
+ stub(q).name { 'test-queue' }
189
+ stub(q).opts {nil}
190
+ w = DummyWorker.new(q)
191
+ w.stop
192
+ end
193
+ end
194
+
195
+
196
+ describe "#do_work" do
197
+ it "should perform worker's work" do
198
+ w = DummyWorker.new(@queue, TestPool.new)
199
+ mock(w).work("msg").once
200
+ w.do_work(nil, nil, "msg", nil)
201
+ end
202
+
203
+ it "should catch runtime exceptions from a bad work" do
204
+ w = AcksWorker.new(@queue, TestPool.new)
205
+ mock(w).work("msg").once{ raise "foo" }
206
+ handler = Object.new
207
+ mock(handler).error("tag", anything)
208
+ header = Object.new
209
+ stub(header).delivery_tag { "tag" }
210
+ w.do_work(header, nil, "msg", handler)
211
+ end
212
+
213
+ it "should timeout if a work takes too long" do
214
+ w = TimeoutWorker.new(@queue, TestPool.new)
215
+ stub(w).work("msg"){ sleep 10 }
216
+
217
+ handler = Object.new
218
+ mock(handler).timeout("tag")
219
+
220
+ header = Object.new
221
+ stub(header).delivery_tag { "tag" }
222
+
223
+ w.do_work(header, nil, "msg", handler)
224
+ end
225
+
226
+ describe "with ack" do
227
+ before do
228
+ @header = Object.new
229
+ stub(@header).delivery_tag{ "tag" }
230
+
231
+ @worker = AcksWorker.new(@queue, TestPool.new)
232
+ end
233
+
234
+ it "should work and handle acks" do
235
+ handler = Object.new
236
+ mock(handler).acknowledge("tag")
237
+
238
+ @worker.do_work(@header, nil, :ack, handler)
239
+ end
240
+
241
+ it "should work and handle nacks" do
242
+ handler = Object.new
243
+ mock(handler).reject("tag")
244
+
245
+ @worker.do_work(@header, nil, :nack, handler)
246
+ end
247
+
248
+ it "should work and handle rejects" do
249
+ handler = Object.new
250
+ mock(handler).reject("tag")
251
+
252
+ @worker.do_work(@header, nil, :reject, handler)
253
+ end
254
+
255
+ it "should work and handle user-land timeouts" do
256
+ handler = Object.new
257
+ mock(handler).timeout("tag")
258
+
259
+ @worker.do_work(@header, nil, :timeout, handler)
260
+ end
261
+
262
+ it "should work and handle user-land error" do
263
+ handler = Object.new
264
+ mock(handler).error("tag",anything)
265
+
266
+ @worker.do_work(@header, nil, :error, handler)
267
+ end
268
+ end
269
+
270
+ describe "without ack" do
271
+ it "should work and not care about acking if not ack" do
272
+ handler = Object.new
273
+ mock(handler).reject(anything).never
274
+ mock(handler).acknowledge(anything).never
275
+
276
+ w = DummyWorker.new(@queue, TestPool.new)
277
+ w.do_work(nil, nil, 'msg', handler)
278
+ end
279
+ end
280
+ end
281
+
282
+
283
+ describe 'publish' do
284
+ it 'should be able to publish a message from working context' do
285
+ w = PublishingWorker.new(@queue, TestPool.new)
286
+ mock(w).publish('msg', :to_queue => 'target').once
287
+ w.do_work(nil, nil, 'msg', nil)
288
+
289
+ end
290
+ end
291
+
292
+
293
+ describe 'Logging' do
294
+ it 'should be able to use the logging facilities' do
295
+ log = Logger.new('/dev/null')
296
+ mock(log).debug(anything).once
297
+ mock(log).info("hello").once
298
+ Sneakers::Worker.configure_logger(log)
299
+
300
+ w = LoggingWorker.new(@queue, TestPool.new)
301
+ w.do_work(nil,nil,'msg',nil)
302
+ end
303
+ end
304
+
305
+
306
+ describe 'Metrics' do
307
+ before do
308
+ @handler = Object.new
309
+ stub(@handler).acknowledge("tag")
310
+ stub(@handler).reject("tag")
311
+ stub(@handler).timeout("tag")
312
+ stub(@handler).error("tag", anything)
313
+
314
+ @header = Object.new
315
+ stub(@header).delivery_tag { "tag" }
316
+
317
+ @w = MetricsWorker.new(@queue, TestPool.new)
318
+ mock(@w.metrics).increment("work.MetricsWorker.started").once
319
+ mock(@w.metrics).increment("work.MetricsWorker.ended").once
320
+ mock(@w.metrics).timing("work.MetricsWorker.time").yields.once
321
+ end
322
+
323
+ it 'should be able to meter acks' do
324
+ mock(@w.metrics).increment("foobar").once
325
+ mock(@w.metrics).increment("work.MetricsWorker.handled.ack").once
326
+ @w.do_work(@header, nil, :ack, @handler)
327
+ end
328
+
329
+ it 'should be able to meter rejects' do
330
+ mock(@w.metrics).increment("foobar").once
331
+ mock(@w.metrics).increment("work.MetricsWorker.handled.reject").once
332
+ @w.do_work(@header, nil, nil, @handler)
333
+ end
334
+
335
+ it 'should be able to meter errors' do
336
+ mock(@w.metrics).increment("work.MetricsWorker.handled.error").once
337
+ mock(@w).work('msg'){ raise :error }
338
+ @w.do_work(@header, nil, 'msg', @handler)
339
+ end
340
+
341
+ it 'should be able to meter timeouts' do
342
+ mock(@w.metrics).increment("work.MetricsWorker.handled.timeout").once
343
+ mock(@w).work('msg'){ sleep 10 }
344
+ @w.do_work(@header, nil, 'msg', @handler)
345
+ end
346
+ end
347
+
348
+ end