queueing_rabbit 0.1.3 → 0.2.0

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 (33) hide show
  1. data/lib/queueing_rabbit/client/amqp.rb +46 -40
  2. data/lib/queueing_rabbit/client/bunny.rb +20 -23
  3. data/lib/queueing_rabbit/extensions/direct_exchange.rb +26 -0
  4. data/lib/queueing_rabbit/extensions/new_relic.rb +19 -4
  5. data/lib/queueing_rabbit/extensions/retryable.rb +22 -0
  6. data/lib/queueing_rabbit/job.rb +56 -11
  7. data/lib/queueing_rabbit/jobs/abstract_job.rb +27 -0
  8. data/lib/queueing_rabbit/jobs/json_job.rb +19 -0
  9. data/lib/queueing_rabbit/version.rb +1 -1
  10. data/lib/queueing_rabbit/worker.rb +14 -4
  11. data/lib/queueing_rabbit.rb +21 -6
  12. data/queueing_rabbit.gemspec +6 -17
  13. data/spec/integration/asynchronous_publishing_and_consuming_spec.rb +36 -16
  14. data/spec/integration/asynchronous_publishing_and_consuming_with_retries_spec.rb +103 -0
  15. data/spec/integration/direct_exchange_asynchronous_publishing_and_consuming_spec.rb +93 -0
  16. data/spec/integration/jobs/print_line_job.rb +5 -11
  17. data/spec/integration/json_job_asynchronous_publishing_and_consuming_spec.rb +99 -0
  18. data/spec/integration/persistent_asynchronous_publishing_and_consuming_spec.rb +96 -0
  19. data/spec/integration/synchronous_publishing_and_asynchronous_consuming_spec.rb +13 -17
  20. data/spec/integration/synchronous_publishing_spec.rb +6 -2
  21. data/spec/spec_helper.rb +4 -1
  22. data/spec/unit/queueing_rabbit/client/amqp_spec.rb +80 -51
  23. data/spec/unit/queueing_rabbit/client/bunny_spec.rb +53 -8
  24. data/spec/unit/queueing_rabbit/extensions/direct_exchange_spec.rb +20 -0
  25. data/spec/unit/queueing_rabbit/extensions/new_relic_spec.rb +36 -0
  26. data/spec/unit/queueing_rabbit/extensions/retryable_spec.rb +36 -0
  27. data/spec/unit/queueing_rabbit/job_spec.rb +76 -4
  28. data/spec/unit/queueing_rabbit/jobs/abstract_job_spec.rb +73 -0
  29. data/spec/unit/queueing_rabbit/jobs/json_job_spec.rb +22 -0
  30. data/spec/unit/queueing_rabbit/worker_spec.rb +33 -23
  31. data/spec/unit/queueing_rabbit_spec.rb +55 -25
  32. metadata +111 -90
  33. data/spec/support/shared_examples.rb +0 -37
@@ -0,0 +1,96 @@
1
+ require 'spec_helper'
2
+ require 'integration/jobs/print_line_job'
3
+
4
+ describe "Persistent asynchronous publishing and consuming" do
5
+ include_context "Evented spec"
6
+ include_context "StringIO logger"
7
+
8
+ before(:all) { QueueingRabbit.client = QueueingRabbit::Client::AMQP }
9
+ after(:all) { QueueingRabbit.client = QueueingRabbit.default_client }
10
+
11
+ context "basic consuming" do
12
+ let(:line) { "Hello, world!" }
13
+ let(:connection) { QueueingRabbit.connection }
14
+ let(:job) {
15
+ Class.new(PrintLineJob) do
16
+ queue 'persistent_print_line_job'
17
+ listen :ack => true
18
+ publishing_defaults :persistent => true
19
+ channel :use_publisher_confirms => true
20
+
21
+ def perform
22
+ super
23
+ acknowledge
24
+ end
25
+ end
26
+ }
27
+ let(:job_name) { 'PrintLineWithAcknowledgmentsJob' }
28
+ let(:io) { StringIO.new }
29
+ let(:worker) { QueueingRabbit::Worker.new(job_name) }
30
+
31
+ before(:each) do
32
+ QueueingRabbit.drop_connection
33
+ end
34
+
35
+ before do
36
+ stub_const(job_name, job)
37
+ job.io = io
38
+ end
39
+
40
+ it "processes enqueued jobs" do
41
+ em {
42
+ QueueingRabbit.connect
43
+ @queue_size = nil
44
+
45
+ def request_queue_size
46
+ connection.open_channel do |c, _|
47
+ connection.define_queue(c, job.queue_name, job.queue_options).status do |s, _|
48
+ @queue_size = s
49
+ end
50
+ end
51
+ end
52
+
53
+ delayed(0.5) {
54
+ 3.times { job.enqueue(line) }
55
+ }
56
+
57
+ delayed(1.5) {
58
+ request_queue_size
59
+ }
60
+
61
+ delayed(2.0) {
62
+ @queue_size.should == 3
63
+ }
64
+
65
+ delayed(2.5) {
66
+ worker.work
67
+ }
68
+
69
+ delayed(3.5) {
70
+ request_queue_size
71
+ }
72
+
73
+ done(4.0) {
74
+ @queue_size.should be_zero
75
+ }
76
+ }
77
+ end
78
+
79
+ it "actually outputs the line" do
80
+ em {
81
+ QueueingRabbit.connect
82
+
83
+ delayed(0.5) { job.enqueue(line) }
84
+
85
+ delayed(1.0) {
86
+ worker.work
87
+ }
88
+
89
+ done(2.0) {
90
+ io.string.should include(line)
91
+ }
92
+ }
93
+ end
94
+ end
95
+ end
96
+
@@ -10,29 +10,25 @@ describe "Synchronous publishing and asynchronous consuming example" do
10
10
 
11
11
  after(:all) { QueueingRabbit.client = QueueingRabbit.default_client }
12
12
 
13
- context "when a message is published synchronously" do
13
+ context "when a message is published synchronously and being consumed " \
14
+ "asynchornously" do
15
+ let(:worker) { QueueingRabbit::Worker.new(job.to_s) }
16
+ let(:io) { StringIO.new }
17
+
14
18
  before do
15
- QueueingRabbit.publish(job, :line => line)
19
+ job.io = io
20
+ job.enqueue(line)
16
21
  QueueingRabbit.drop_connection
17
22
  end
18
23
 
19
- context "and being consumed asynchornously" do
20
- let(:worker) { QueueingRabbit::Worker.new(job.to_s) }
21
- let(:io) { StringIO.new }
22
-
23
- before do
24
- job.io = io
25
- end
26
-
27
- it "works" do
28
- em {
29
- worker.work
24
+ it "works" do
25
+ em {
26
+ worker.work
30
27
 
31
- done(1.0) {
32
- io.string.should include(line)
33
- }
28
+ done(1.0) {
29
+ io.string.should include(line)
34
30
  }
35
- end
31
+ }
36
32
  end
37
33
  end
38
34
 
@@ -6,17 +6,21 @@ describe "Synchronous publishing example" do
6
6
 
7
7
  let(:job) { PrintLineJob }
8
8
 
9
+ before do
10
+ job.io = StringIO.new
11
+ end
12
+
9
13
  context "when publishing a message" do
10
14
  after do
11
15
  QueueingRabbit.purge_queue(job)
12
16
  end
13
17
 
14
18
  let(:publishing) {
15
- lambda { QueueingRabbit.publish(job, :line => "Hello, World!") }
19
+ Proc.new { job.enqueue("Hello, World!") }
16
20
  }
17
21
 
18
22
  it 'affects the queue size' do
19
- expect { 5.times { publishing.call } }.
23
+ expect { 5.times(&publishing) }.
20
24
  to change{QueueingRabbit.queue_size(job)}.by(5)
21
25
  end
22
26
  end
data/spec/spec_helper.rb CHANGED
@@ -9,7 +9,6 @@ require 'rspec'
9
9
  require 'rspec/autorun'
10
10
  require 'evented-spec'
11
11
  require 'support/shared_contexts'
12
- require 'support/shared_examples'
13
12
 
14
13
  require 'queueing_rabbit'
15
14
 
@@ -18,6 +17,10 @@ RSpec.configure do |config|
18
17
  QueueingRabbit.drop_connection
19
18
  }
20
19
 
20
+ config.exclusion_filter = {
21
+ :ruby => RUBY_VERSION
22
+ }
23
+
21
24
  QueueingRabbit.configure do |qr|
22
25
  qr.amqp_uri = "amqp://guest:guest@localhost:5672"
23
26
  qr.amqp_exchange_name = "queueing_rabbit_test"
@@ -19,32 +19,47 @@ describe QueueingRabbit::Client::AMQP do
19
19
  its(:connection_options) { should include(:heartbeat) }
20
20
  its(:connection_options) { should include(:on_tcp_connection_failure) }
21
21
 
22
- describe '.run_event_machine' do
22
+ describe '.ensure_event_machine_is_running' do
23
23
  context 'when the event machine reactor is running' do
24
24
  before do
25
25
  EM.should_receive(:reactor_running?).and_return(true)
26
26
  end
27
27
 
28
28
  it 'has no effect' do
29
- subject.run_event_machine
29
+ subject.ensure_event_machine_is_running
30
30
  end
31
31
  end
32
32
 
33
33
  context 'when the event machine reactor is not running' do
34
34
  before do
35
- EM.should_receive(:reactor_running?).and_return(false)
35
+ EM.should_receive(:reactor_running?).once.and_return(false)
36
+ EM.should_receive(:reactor_running?).and_return(true)
36
37
  Thread.should_receive(:new).and_yield
37
38
  EM.should_receive(:run).and_yield
38
39
  end
39
40
 
40
41
  it 'runs the event machine reactor in a separate thread' do
41
- subject.run_event_machine
42
+ subject.ensure_event_machine_is_running
42
43
  end
43
44
 
44
45
  it 'triggers :event_machine_started event' do
45
46
  QueueingRabbit.should_receive(:trigger_event).
46
47
  with(:event_machine_started)
47
- subject.run_event_machine
48
+ subject.ensure_event_machine_is_running
49
+ end
50
+ end
51
+
52
+ context 'when the event machine can reactor can not be started' do
53
+ before do
54
+ EM.should_receive(:reactor_running?).once.and_return(false)
55
+ EM.should_receive(:reactor_running?).and_raise(Timeout::Error)
56
+ Thread.should_receive(:new).and_yield
57
+ EM.should_receive(:run).and_yield
58
+ end
59
+
60
+ it 'raises a QueueingRabbitError after 5 seconds expire' do
61
+ expect { subject.ensure_event_machine_is_running }.
62
+ to raise_error(QueueingRabbit::QueueingRabbitError)
48
63
  end
49
64
  end
50
65
  end
@@ -85,98 +100,72 @@ describe QueueingRabbit::Client::AMQP do
85
100
  QueueingRabbit::Client::AMQP.stub(:run_event_machine => true)
86
101
  end
87
102
 
88
- it_behaves_like :client
89
-
90
103
  it { should be }
91
104
 
92
105
  describe '#define_queue' do
93
- let(:exchange) { mock }
94
106
  let(:channel) { mock }
95
107
  let(:queue) { mock }
96
108
  let(:queue_name) { "test_queue_name" }
97
- let(:routing_keys) { [:test_job] }
98
- let(:options) { {:durable => false, :routing_keys => routing_keys} }
109
+ let(:options) { {:durable => false} }
99
110
 
100
111
  before do
101
- client.stub(:exchange => exchange)
102
112
  channel.should_receive(:queue).with(queue_name, options).
103
113
  and_yield(queue)
104
- queue.should_receive(:bind).
105
- with(exchange, :routing_key => routing_keys.first.to_s).ordered
106
- queue.should_receive(:bind).
107
- with(exchange, :routing_key => queue_name).ordered
108
114
  end
109
115
 
110
- it "defines a queue and binds it to its name and the given routing keys" do
116
+ it "defines a queue" do
111
117
  client.define_queue(channel, queue_name, options)
112
118
  end
113
119
  end
114
120
 
115
121
  describe '#listen_queue' do
116
- let(:channel) { mock }
117
- let(:queue_name) { mock }
118
122
  let(:options) { mock }
119
123
  let(:queue) { mock }
120
- let(:metadata) { stub(:ack => true) }
121
- let(:data) { {:data => "data"}}
122
- let(:payload) { JSON.dump(data) }
124
+ let(:metadata) { mock }
125
+ let(:payload) { mock }
123
126
 
124
127
  before do
125
- client.should_receive(:define_queue).
126
- with(channel, queue_name, options).and_return(queue)
127
- queue.should_receive(:subscribe).
128
- with(:ack => true).and_yield(metadata, payload)
128
+ queue.should_receive(:subscribe).with(options).
129
+ and_yield(metadata, payload)
129
130
  end
130
131
 
131
132
  it 'listens to the queue and passes deserialized arguments to the block' do
132
- client.listen_queue(channel, queue_name, options) do |arguments|
133
- arguments.should == data
134
- end
135
- end
136
-
137
- context "when deserialization problems occur" do
138
- let(:error) { JSON::JSONError.new }
139
- before do
140
- client.should_receive(:deserialize).and_raise(error)
141
- client.should_receive(:error)
142
- client.should_receive(:debug).with(error)
143
- end
144
-
145
- it "keeps the record of the errors" do
146
- client.listen_queue(channel, queue_name, options)
147
- end
148
-
149
- it "silences JSON errors" do
150
- expect { client.listen_queue(channel, queue_name, options) }.
151
- to_not raise_error(error)
133
+ client.listen_queue(queue, options) do |payload, metadata|
134
+ payload.should == payload
135
+ metadata.should == metadata
152
136
  end
153
137
  end
154
138
  end
155
139
 
156
140
  describe '#process_message' do
157
- let(:arguments) { mock }
141
+ let(:payload) { mock }
142
+ let(:metadata) { mock }
158
143
 
159
144
  it "yields given arguments to the block" do
160
- client.process_message(arguments) do |a|
161
- a.should == arguments
145
+ client.process_message(payload, metadata) do |p, m|
146
+ p.should == payload
147
+ m.should == metadata
162
148
  end
163
149
  end
164
150
 
165
151
  it "silences all errors risen" do
166
152
  expect {
167
- client.process_message(arguments) { |a| raise StandardError.new }
153
+ client.process_message(payload, metadata) do |_, _|
154
+ raise StandardError.new
155
+ end
168
156
  }.to_not raise_error(StandardError)
169
157
  end
170
158
 
171
159
  context "logging" do
172
160
  let(:error) { StandardError.new }
161
+
173
162
  before do
174
163
  client.should_receive(:error)
175
164
  client.should_receive(:debug).with(error)
176
165
  end
177
166
 
178
167
  it "keeps the record of all errors risen" do
179
- client.process_message(arguments) { |a| raise error }
168
+ client.process_message(payload, metadata) { |_, _| raise error }
180
169
  end
181
170
  end
182
171
  end
@@ -195,7 +184,7 @@ describe QueueingRabbit::Client::AMQP do
195
184
 
196
185
  describe "#open_channel" do
197
186
  let(:next_channel_id) { mock }
198
- let(:options) { mock }
187
+ let(:options) { {:use_publisher_confirms => true} }
199
188
  let(:channel) { mock }
200
189
  let(:open_ok) { mock }
201
190
 
@@ -206,6 +195,7 @@ describe QueueingRabbit::Client::AMQP do
206
195
  with(connection, next_channel_id, options).
207
196
  and_yield(channel, open_ok)
208
197
  channel.should_receive(:on_error)
198
+ channel.should_receive(:confirm_select)
209
199
  end
210
200
 
211
201
  it 'opens a new AMQP channel with given options and installs ' \
@@ -213,5 +203,44 @@ describe QueueingRabbit::Client::AMQP do
213
203
  client.open_channel(options) {}
214
204
  end
215
205
  end
206
+
207
+ describe '#define_exchange' do
208
+ context 'when only channel is given' do
209
+ let(:channel) { mock }
210
+ let(:default_exchange) { mock }
211
+
212
+ before do
213
+ channel.should_receive(:default_exchange).and_return(default_exchange)
214
+ end
215
+
216
+ it 'returns the default exchange' do
217
+ client.define_exchange(channel).should == default_exchange
218
+ end
219
+ end
220
+
221
+ context 'with arguments and type' do
222
+ let(:channel) { mock }
223
+ let(:name) { 'some_exchange_name' }
224
+ let(:options) { {:type => 'direct'} }
225
+ let(:exchange) { mock }
226
+
227
+ it 'creates an exchange of given type and options' do
228
+ channel.should_receive(:direct).with(name, options).
229
+ and_return(exchange)
230
+ client.define_exchange(channel, name, options).should == exchange
231
+ end
232
+ end
233
+ end
234
+
235
+ describe '#enqueue' do
236
+ let(:exchange) { mock }
237
+ let(:payload) { mock }
238
+ let(:options) { mock }
239
+
240
+ it "publishes a new message to given exchange with given options" do
241
+ exchange.should_receive(:publish).with(payload, options)
242
+ client.enqueue(exchange, payload, options)
243
+ end
244
+ end
216
245
  end
217
246
  end
@@ -32,20 +32,19 @@ describe QueueingRabbit::Client::Bunny do
32
32
  Bunny.stub(:new => connection)
33
33
  end
34
34
 
35
- it_behaves_like :client
36
-
37
35
  it { should be }
38
36
 
39
37
  describe '#open_channel' do
40
- let(:options) { mock }
38
+ let(:options) { {:use_publisher_confirms => true} }
41
39
  let(:channel) { mock }
42
40
 
43
41
  before do
44
42
  connection.should_receive(:create_channel).and_return(channel)
43
+ channel.should_receive(:confirm_select)
45
44
  end
46
45
 
47
46
  it 'creates a channel and yields it' do
48
- client.open_channel do |c, _|
47
+ client.open_channel(options) do |c, _|
49
48
  c.should == channel
50
49
  end
51
50
  end
@@ -54,16 +53,22 @@ describe QueueingRabbit::Client::Bunny do
54
53
  describe '#define_queue' do
55
54
  let(:channel) { mock }
56
55
  let(:queue) { mock }
57
- let(:exchange) { mock }
58
56
  let(:name) { 'queue_name_test' }
59
57
  let(:options) { {:foo => 'bar'} }
60
58
 
61
59
  it 'creates a queue and binds it to the global exchange' do
62
60
  channel.should_receive(:queue).with(name, options).and_return(queue)
63
- client.should_receive(:exchange).with(channel).and_return(exchange)
64
- queue.should_receive(:bind).with(exchange, :routing_key => name)
61
+ client.define_queue(channel, name, options).should == queue
62
+ end
63
+
64
+ context 'when block is given' do
65
+ it 'yields the created queue' do
66
+ channel.should_receive(:queue).with(name, options).and_return(queue)
65
67
 
66
- client.define_queue(channel, name, options)
68
+ client.define_queue(channel, name, options) do |q|
69
+ q.should == queue
70
+ end
71
+ end
67
72
  end
68
73
  end
69
74
 
@@ -80,5 +85,45 @@ describe QueueingRabbit::Client::Bunny do
80
85
  end
81
86
  end
82
87
 
88
+ describe '#define_exchange' do
89
+ context 'when only channel is given' do
90
+ let(:channel) { mock }
91
+ let(:default_exchange) { mock }
92
+
93
+ before do
94
+ channel.should_receive(:default_exchange).
95
+ and_return(default_exchange)
96
+ end
97
+
98
+ it 'returns the default exchange' do
99
+ client.define_exchange(channel).should == default_exchange
100
+ end
101
+ end
102
+
103
+ context 'with arguments and type' do
104
+ let(:channel) { mock }
105
+ let(:name) { 'some_exchange_name' }
106
+ let(:options) { {:type => 'direct'} }
107
+ let(:exchange) { mock }
108
+
109
+ it 'creates an exchange of given type and options' do
110
+ channel.should_receive(:direct).with(name, options).
111
+ and_return(exchange)
112
+ client.define_exchange(channel, name, options).should == exchange
113
+ end
114
+ end
115
+ end
116
+
117
+ describe '#enqueue' do
118
+ let(:exchange) { mock }
119
+ let(:payload) { mock }
120
+ let(:options) { mock }
121
+
122
+ it "publishes a new message to given exchange with given options" do
123
+ exchange.should_receive(:publish).with(payload, options)
124
+ client.enqueue(exchange, payload, options)
125
+ end
126
+ end
127
+
83
128
  end
84
129
  end