sneakers 0.0.1 → 0.0.2

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