stooge 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY.md CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- ### 0.1.0 (2012-03-09)
2
+ ### 0.1.0 (2012-03-14)
3
3
 
4
4
  * Implemented same basic feature set as Minion (i.e. work queues)
5
5
  * Added ability to easily test job handlers
data/README.md CHANGED
@@ -44,10 +44,10 @@ end
44
44
  Error handling
45
45
  --------------
46
46
 
47
- When an error is thrown in a job handler, the job is requeued to be done later and the Stooge process exits. If you define an error handler, however, the error handler is run and the job is removed from the queue.
47
+ When an error is thrown in a job handler, the job is re-queued to be done later and the Stooge process exits. If you define an error handler, however, the error handler is run and the job is removed from the queue.
48
48
 
49
49
  ```ruby
50
- Stooge.error do |e|
50
+ Stooge.error do |exception, handler, payload, headers|
51
51
  puts "got an error! #{e}"
52
52
  end
53
53
  ```
@@ -30,23 +30,43 @@ module Stooge
30
30
  channel.queue(@queue_name, @queue_options) do |queue|
31
31
  queue.subscribe(:ack => true) do |metadata, payload|
32
32
  Stooge.log "recv: #{@queue_name}"
33
- begin
34
- case metadata.content_type
35
- when 'application/json'
36
- args = MultiJson.decode(payload)
37
- else
38
- args = payload
39
- end
40
- @block.call(args, metadata.headers)
41
- rescue Object => e
42
- if Stooge.error_handler
43
- Stooge.error_handler.call(e,self,payload,metadata)
44
- end
45
- end
33
+ run(payload, metadata.content_type, metadata.headers)
46
34
  metadata.ack
47
35
  end
48
36
  end
49
37
  end
50
38
 
39
+ #
40
+ # Call the handler block. This method will rescue any exceptions the
41
+ # handler block raises and pass them on to the global error handler.
42
+ #
43
+ # @param [String] payload the message payload
44
+ # @param [String] content_type the MIME content type of the message
45
+ # payload
46
+ # @param [Hash] headers the message headers
47
+ #
48
+ # @return [Object] the return value of the handler block
49
+ #
50
+ def run(payload, content_type, headers)
51
+ @block.call(
52
+ decode_payload(payload, content_type),
53
+ headers)
54
+ rescue Object => e
55
+ if Stooge.error_handler
56
+ Stooge.error_handler.call(e,self,payload,headers)
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def decode_payload(payload, content_type)
63
+ case content_type
64
+ when 'application/json'
65
+ MultiJson.decode(payload)
66
+ else
67
+ payload.to_s
68
+ end
69
+ end
70
+
51
71
  end
52
72
  end
@@ -1,3 +1,3 @@
1
1
  module Stooge
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
data/lib/stooge/worker.rb CHANGED
@@ -19,7 +19,7 @@ module Stooge
19
19
  # @return [Boolean] true or false
20
20
  #
21
21
  def self.run?
22
- Stooge.handlers? &&
22
+ Stooge.has_handlers? &&
23
23
  File.expand_path($0) == File.expand_path(app_file)
24
24
  end
25
25
 
data/lib/stooge.rb CHANGED
@@ -14,7 +14,7 @@ module Stooge
14
14
  @@connection = nil
15
15
  @@channel = nil
16
16
  @@handlers = []
17
- @@error_handler = Proc.new do |exception, handler, payload, metadata|
17
+ @@error_handler = Proc.new do |exception, handler, payload, headers|
18
18
  Stooge.log "#{handler.queue_name} failed: #{exception.inspect}"
19
19
  raise exception
20
20
  end
@@ -71,12 +71,10 @@ module Stooge
71
71
  #
72
72
  # If you don't raise an exception in the error handler the job will be
73
73
  # acked with the broker and the broker will consider the job done and remove
74
- # it from the queue. If you for some reason want to force the job to be
75
- # acked even when you raise an error you can manually ack it before you
76
- # raise the error, like this:
74
+ # it from the queue. To make sure the job is not lost you can simply
75
+ # re-raise the same exception in your custom handler:
77
76
  #
78
- # Stooge.error do |exception, handler, payload, metadata|
79
- # metadata.ack
77
+ # Stooge.error do |exception, handler, payload, headers|
80
78
  # raise exception
81
79
  # end
82
80
  #
@@ -85,7 +83,7 @@ module Stooge
85
83
  # @yieldparam [Stooge::Handler] handler the handler that failed.
86
84
  # @yieldparam [Object] payload the message payload that was processed when
87
85
  # the handler failed.
88
- # @yieldparam [Hash] metadata the message metadata (headers, etc.)
86
+ # @yieldparam [Hash] headers the message headers
89
87
  #
90
88
  def error(&blk)
91
89
  @@error_handler = blk
@@ -124,10 +122,18 @@ module Stooge
124
122
  #
125
123
  # @return [Boolean] true or false
126
124
  #
127
- def handlers?
125
+ def has_handlers?
128
126
  @@handlers.empty? == false
129
127
  end
130
128
 
129
+ #
130
+ # Remove all job handlers. Mainly useful in tests to create a clean slate
131
+ # for each test case.
132
+ #
133
+ def clear_handlers
134
+ @@handlers = []
135
+ end
136
+
131
137
  #
132
138
  # Execute a handler block without going through AMQP at all. This is a
133
139
  # helper method for use in tests. It allows you to test the business logic
@@ -148,7 +154,7 @@ module Stooge
148
154
  def run_handler(queue_name, data, headers = {})
149
155
  @@handlers.each do |handler|
150
156
  if handler.queue_name == queue_name
151
- return handler.block.call(data, headers)
157
+ return handler.run(MultiJson.encode(data), 'application/json', headers)
152
158
  end
153
159
  end
154
160
  end
data/spec/stooge_spec.rb CHANGED
@@ -6,4 +6,142 @@ require 'rspec'
6
6
 
7
7
  describe Stooge do
8
8
 
9
+ before do
10
+ Stooge.amqp_url = 'amqp://guest:guest@localhost/'
11
+ Stooge.clear_handlers
12
+ end
13
+
14
+ it 'should use a default URL to the AMQP broker' do
15
+ Stooge.amqp_url.should == 'amqp://guest:guest@localhost/'
16
+ Stooge.send(:amqp_config).should == {
17
+ :vhost => '/',
18
+ :host => 'localhost',
19
+ :user => 'guest',
20
+ :port => 5672,
21
+ :pass => 'guest'
22
+ }
23
+ end
24
+
25
+ it 'should be possible to configure the URL to the AMQP broker' do
26
+ Stooge.amqp_url = 'amqp://john:doe@example.com:5000/vhost'
27
+ Stooge.amqp_url.should == 'amqp://john:doe@example.com:5000/vhost'
28
+ Stooge.send(:amqp_config).should == {
29
+ :vhost => '/vhost',
30
+ :host => 'example.com',
31
+ :user => 'john',
32
+ :port => 5000,
33
+ :pass => 'doe'
34
+ }
35
+ end
36
+
37
+ it 'can add a job handler (ruby block) for a specified work queue' do
38
+ Stooge.job('test.work') {}
39
+ Stooge.should have_handlers
40
+ end
41
+
42
+ it 'can enqueue jobs on a named queue with data and headers if inside an EM synchrony block' do
43
+ EM.synchrony do
44
+ # Here we stub the call to #publish on the default exchange and
45
+ # intercept the published message instead of sending the message to the
46
+ # broker and then testing if the broker receieved a new message, which
47
+ # is very hard to do reliably because of timing issues and because the
48
+ # message is silently dropped by the broker if no queue is bound to the
49
+ # exchange. It also give us the ability to easily test that the message
50
+ # is published with the correct options.
51
+ Stooge.with_channel do |channel|
52
+ channel.default_exchange.should_receive(:publish).with(
53
+ '{"test":"test"}',
54
+ {
55
+ :routing_key => 'test.work',
56
+ :mandatory => true,
57
+ :content_type => 'application/json',
58
+ :headers => { :foo => 'bar' }
59
+ }).and_yield
60
+ end
61
+ Stooge.enqueue('test.work', { :test => 'test' }, :foo => 'bar')
62
+ Stooge.stop!
63
+ end
64
+ end
65
+
66
+ it 'can put a job on a queue and that job is the later picked up by a handler and processed' do
67
+ # This test is a bit tricky and might require some explanation. To
68
+ # actually test that this works we need to first create a job handler that
69
+ # sets a global ($work_performed) variable that we can later check to see
70
+ # wether the job has actually been performed. We then open an EM synchrony
71
+ # block in which we start the job handler (i.e. makes it listen to its
72
+ # queue for job messages). We then enqeue a job and later check if the job
73
+ # was performed as expected.
74
+ #
75
+ # To make all this work we need to deal with some nasty timing issues:
76
+ #
77
+ # 1. We need make sure the handler has had enough time to create a queue
78
+ # and binding before we can publish a message, or the message will
79
+ # just be silently dropped. This needs to happen in different EM tick.
80
+ # We therefor add an EM timer to publish the message 1 second later.
81
+ # The timer requires us to open up yet another synchrony block, because
82
+ # the timer block does not execute inside the fiber created by
83
+ # synchrony which it needs to do for Stooge#enqueue to work (because of
84
+ # the deferable magic happening behind the scenes).
85
+ #
86
+ # 2. When we have published the message we then need to wait a while for
87
+ # the job handler to pick it up and process it. We need to keep the
88
+ # EM loop alive for this to happen, so we simply sleep for 1 second
89
+ # before we shut the EM loop down and check if the job was processed.
90
+ #
91
+ # NOTE: This all depend on the communication with the broker taking less
92
+ # than 1 second, which should be more than fine in most cases. If this
93
+ # test stops working for some reason the first thing to try should be to
94
+ # increase the wait time and see if it works then.
95
+ #
96
+ $work_performed = false
97
+ Stooge.job('test.work') { $work_performed = true }
98
+ EM.synchrony do
99
+ Stooge.with_channel do |channel|
100
+ Stooge.start_handlers(channel)
101
+ end
102
+ EM.add_timer(1) do
103
+ EM.synchrony do
104
+ Stooge.enqueue('test.work', { :test => 'test' }, :foo => 'bar')
105
+ end
106
+ sleep(1)
107
+ Stooge.stop!
108
+ end
109
+ end
110
+ $work_performed.should == true
111
+ end
112
+
113
+ it 'should be possible to specify a custom global error handler' do
114
+ $error = false
115
+ old_handler = Stooge.error_handler
116
+ Stooge.error do |exception, handler, payload, metadata|
117
+ $error = true
118
+ end
119
+ Stooge.job('test.work') { raise }
120
+ Stooge.run_handler('test.work', '')
121
+ Stooge.error(&old_handler)
122
+ $error.should == true
123
+ end
124
+
125
+ it 'should be possible to specify a custom logger' do
126
+ $logged = false
127
+ old_logger = Stooge.logger
128
+ Stooge.logger do |msg|
129
+ $logged = true
130
+ end
131
+ Stooge.log('test')
132
+ Stooge.logger(&old_logger)
133
+ $logged.should == true
134
+ end
135
+
136
+ it 'should be possible to test a handler' do
137
+ $work_performed = false
138
+ Stooge.job('test.work') do |args, headers|
139
+ $work_performed = true
140
+ args.should == { 'test' => 'test' }
141
+ headers.should == { :foo => 'bar' }
142
+ end
143
+ Stooge.run_handler('test.work', { :test => 'test' }, :foo => 'bar')
144
+ $work_performed.should == true
145
+ end
146
+
9
147
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stooge
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-14 00:00:00.000000000 Z
12
+ date: 2012-03-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: amqp
16
- requirement: &70330131821100 !ruby/object:Gem::Requirement
16
+ requirement: &70114352031440 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70330131821100
24
+ version_requirements: *70114352031440
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: em-synchrony
27
- requirement: &70330131820660 !ruby/object:Gem::Requirement
27
+ requirement: &70114352031000 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70330131820660
35
+ version_requirements: *70114352031000
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: multi_json
38
- requirement: &70330131820200 !ruby/object:Gem::Requirement
38
+ requirement: &70114352030540 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70330131820200
46
+ version_requirements: *70114352030540
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rspec
49
- requirement: &70330131819740 !ruby/object:Gem::Requirement
49
+ requirement: &70114352030080 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,7 +54,7 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70330131819740
57
+ version_requirements: *70114352030080
58
58
  description: Super advanced job queue over AMQP
59
59
  email:
60
60
  - niklas@sutajio.se