queueing_rabbit 0.1.0.rc1
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.
- data/.gitignore +17 -0
- data/.rvmrc +48 -0
- data/Gemfile +9 -0
- data/LICENSE +22 -0
- data/README.md +38 -0
- data/Rakefile +5 -0
- data/lib/queueing_rabbit/callbacks.rb +31 -0
- data/lib/queueing_rabbit/client/amqp.rb +148 -0
- data/lib/queueing_rabbit/client/bunny.rb +62 -0
- data/lib/queueing_rabbit/client/callbacks.rb +14 -0
- data/lib/queueing_rabbit/configuration.rb +24 -0
- data/lib/queueing_rabbit/job.rb +32 -0
- data/lib/queueing_rabbit/logging.rb +17 -0
- data/lib/queueing_rabbit/serializer.rb +19 -0
- data/lib/queueing_rabbit/tasks.rb +37 -0
- data/lib/queueing_rabbit/version.rb +3 -0
- data/lib/queueing_rabbit/worker.rb +96 -0
- data/lib/queueing_rabbit.rb +67 -0
- data/lib/tasks/queueing_rabbit.rake +2 -0
- data/queueing_rabbit.gemspec +49 -0
- data/spec/integration/asynchronous_publishing_and_consuming_spec.rb +62 -0
- data/spec/integration/jobs/print_line_job.rb +17 -0
- data/spec/integration/synchronous_publishing_and_asynchronous_consuming_spec.rb +39 -0
- data/spec/integration/synchronous_publishing_spec.rb +24 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/shared_contexts.rb +17 -0
- data/spec/support/shared_examples.rb +60 -0
- data/spec/unit/queueing_rabbit/callbacks_spec.rb +53 -0
- data/spec/unit/queueing_rabbit/client/amqp_spec.rb +193 -0
- data/spec/unit/queueing_rabbit/client/bunny_spec.rb +68 -0
- data/spec/unit/queueing_rabbit/client/callbacks_spec.rb +22 -0
- data/spec/unit/queueing_rabbit/configuration_spec.rb +19 -0
- data/spec/unit/queueing_rabbit/job_spec.rb +23 -0
- data/spec/unit/queueing_rabbit/logging_spec.rb +9 -0
- data/spec/unit/queueing_rabbit/serializer_spec.rb +26 -0
- data/spec/unit/queueing_rabbit/worker_spec.rb +133 -0
- data/spec/unit/queueing_rabbit_spec.rb +105 -0
- metadata +168 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
require "queueing_rabbit/version"
|
2
|
+
require "queueing_rabbit/callbacks"
|
3
|
+
require "queueing_rabbit/configuration"
|
4
|
+
require "queueing_rabbit/logging"
|
5
|
+
require "queueing_rabbit/serializer"
|
6
|
+
require "queueing_rabbit/client/callbacks"
|
7
|
+
require "queueing_rabbit/client/amqp"
|
8
|
+
require "queueing_rabbit/client/bunny"
|
9
|
+
require "queueing_rabbit/job"
|
10
|
+
require "queueing_rabbit/worker"
|
11
|
+
# require "queueing_rabbit/new_relic"
|
12
|
+
|
13
|
+
module QueueingRabbit
|
14
|
+
extend self
|
15
|
+
extend Logging
|
16
|
+
extend Callbacks
|
17
|
+
extend Configuration
|
18
|
+
|
19
|
+
class QueueingRabbitError < Exception; end
|
20
|
+
class JobNotFoundError < QueueingRabbitError; end
|
21
|
+
class JobNotPresentError < QueueingRabbitError; end
|
22
|
+
|
23
|
+
attr_accessor :logger, :client
|
24
|
+
|
25
|
+
def connect
|
26
|
+
@connection ||= client.connect
|
27
|
+
end
|
28
|
+
|
29
|
+
def connection
|
30
|
+
@connection ||= connect
|
31
|
+
end
|
32
|
+
|
33
|
+
def drop_connection
|
34
|
+
@connection = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def enqueue(job, arguments = {})
|
38
|
+
info "enqueueing job #{job} with arguments: #{arguments.inspect}."
|
39
|
+
|
40
|
+
connection.open_channel(job.channel_options) do |c, _|
|
41
|
+
connection.define_queue(c, job.queue_name, job.queue_options)
|
42
|
+
connection.enqueue(c, job.queue_name, arguments)
|
43
|
+
end
|
44
|
+
|
45
|
+
true
|
46
|
+
end
|
47
|
+
alias_method :publish, :enqueue
|
48
|
+
|
49
|
+
def queue_size(job)
|
50
|
+
size = 0
|
51
|
+
connection.open_channel(job.channel_options) do |c, _|
|
52
|
+
queue = connection.define_queue(c, job.queue_name, job.queue_options)
|
53
|
+
size = connection.queue_size(queue)
|
54
|
+
end
|
55
|
+
size
|
56
|
+
end
|
57
|
+
|
58
|
+
def purge_queue(job)
|
59
|
+
connection.open_channel(job.channel_options) do |c, _|
|
60
|
+
connection.define_queue(c, job.queue_name, job.queue_options).purge
|
61
|
+
end
|
62
|
+
true
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
QueueingRabbit.client = QueueingRabbit.default_client
|
67
|
+
QueueingRabbit.logger = Logger.new(STDOUT)
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/queueing_rabbit/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Artem Chistyakov"]
|
6
|
+
gem.email = ["chistyakov.artem@gmail.com"]
|
7
|
+
gem.summary = %q{QueueingRabbit is an AMQP-based queueing system}
|
8
|
+
gem.homepage = "https://github.com/temochka/queueing_rabbit"
|
9
|
+
|
10
|
+
gem.files = `git ls-files`.split($\)
|
11
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
12
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
13
|
+
gem.name = "queueing_rabbit"
|
14
|
+
gem.require_paths = ["lib"]
|
15
|
+
gem.version = QueueingRabbit::VERSION
|
16
|
+
|
17
|
+
gem.extra_rdoc_files = [ "LICENSE", "README.md" ]
|
18
|
+
gem.rdoc_options = ["--charset=UTF-8"]
|
19
|
+
|
20
|
+
gem.add_dependency "amqp", ">= 0.9.0"
|
21
|
+
gem.add_dependency "bunny", ">= 0.9.0.pre7"
|
22
|
+
gem.add_dependency "rake", ">= 0"
|
23
|
+
|
24
|
+
gem.description = <<description
|
25
|
+
QueueingRabbit is a Ruby library providing convenient object-oriented syntax
|
26
|
+
for managing background jobs using AMQP. All jobs' argumets are serialized
|
27
|
+
to JSON and transfered to jobs using AMQP message payload. The library
|
28
|
+
implements amqp and bunny gems as adapters making it possible to use
|
29
|
+
synchronous publishing and asynchronous consuming, which might be useful for
|
30
|
+
Rails app running on non-EventMachine based application servers (i. e.
|
31
|
+
Passenger).
|
32
|
+
|
33
|
+
Any Ruby class or Module can be transformed into QueueingRabbit's background
|
34
|
+
job by including QueueingRabbit::Job module. It is also possible to inherit
|
35
|
+
your class from QueueingRabbit::AbstractJob abstract class.
|
36
|
+
|
37
|
+
The library is bundled with a Rake task which is capable of starting a
|
38
|
+
worker processing a specified list of jobs.
|
39
|
+
|
40
|
+
To achieve the required simplicity the gem doesn't try to support all
|
41
|
+
features of AMQP protocol. It uses a restricted subset instead:
|
42
|
+
|
43
|
+
* Only a single direct exchange is used
|
44
|
+
* Every job is consumed using a separate channel
|
45
|
+
* All jobs are consumed with acknowledgements
|
46
|
+
* ACK is only sent to the broker if a job was processed successfully
|
47
|
+
* Currently all messages are published with persistent option
|
48
|
+
description
|
49
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'integration/jobs/print_line_job'
|
3
|
+
|
4
|
+
describe "Asynchronous publishing and consuming example" do
|
5
|
+
include_context "Evented spec"
|
6
|
+
include_context "StringIO logger"
|
7
|
+
|
8
|
+
let(:job) { PrintLineJob }
|
9
|
+
|
10
|
+
before(:all) { QueueingRabbit.client = QueueingRabbit::Client::AMQP }
|
11
|
+
after(:all) { QueueingRabbit.client = QueueingRabbit.default_client }
|
12
|
+
|
13
|
+
context "basic consuming" do
|
14
|
+
let(:connection) { QueueingRabbit.connection }
|
15
|
+
let(:worker) { QueueingRabbit::Worker.new(job.to_s) }
|
16
|
+
let(:io) { StringIO.new }
|
17
|
+
let(:line) { "Hello, world!" }
|
18
|
+
|
19
|
+
before(:each) do
|
20
|
+
QueueingRabbit.drop_connection
|
21
|
+
PrintLineJob.io = io
|
22
|
+
end
|
23
|
+
|
24
|
+
it "processes enqueued jobs" do
|
25
|
+
em {
|
26
|
+
QueueingRabbit.connect
|
27
|
+
queue_size = nil
|
28
|
+
|
29
|
+
delayed(0.5) {
|
30
|
+
3.times { QueueingRabbit.enqueue(job, :line => line) }
|
31
|
+
}
|
32
|
+
|
33
|
+
delayed(1.0) { worker.work }
|
34
|
+
|
35
|
+
delayed(2.0) {
|
36
|
+
connection.open_channel do |c, _|
|
37
|
+
connection.define_queue(c, :print_line_job, job.queue_options).status do |s, _|
|
38
|
+
queue_size = s
|
39
|
+
end
|
40
|
+
end
|
41
|
+
}
|
42
|
+
|
43
|
+
done(2.5) { queue_size.should == 0 }
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
it "actually outputs the line" do
|
48
|
+
em {
|
49
|
+
QueueingRabbit.connect
|
50
|
+
|
51
|
+
delayed(0.5) { QueueingRabbit.enqueue(job, :line => line) }
|
52
|
+
|
53
|
+
delayed(1.0) { worker.work }
|
54
|
+
|
55
|
+
done(2.0) {
|
56
|
+
io.string.should include(line)
|
57
|
+
}
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class PrintLineJob
|
2
|
+
extend QueueingRabbit::Job
|
3
|
+
|
4
|
+
class << self
|
5
|
+
attr_writer :io
|
6
|
+
end
|
7
|
+
|
8
|
+
queue :print_line_job, :durable => true
|
9
|
+
|
10
|
+
def self.perform(arguments = {})
|
11
|
+
self.io.puts arguments[:line]
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.io
|
15
|
+
@io ||= STDOUT
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'integration/jobs/print_line_job'
|
3
|
+
|
4
|
+
describe "Synchronous publishing and asynchronous consuming example" do
|
5
|
+
include_context "StringIO logger"
|
6
|
+
include_context "Evented spec"
|
7
|
+
|
8
|
+
let(:job) { PrintLineJob }
|
9
|
+
let(:line) { "Hello, world!" }
|
10
|
+
|
11
|
+
after(:all) { QueueingRabbit.client = QueueingRabbit.default_client }
|
12
|
+
|
13
|
+
context "when a message is published synchronously" do
|
14
|
+
before do
|
15
|
+
QueueingRabbit.publish(job, line: line)
|
16
|
+
QueueingRabbit.drop_connection
|
17
|
+
end
|
18
|
+
|
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
|
30
|
+
|
31
|
+
done(1.0) {
|
32
|
+
io.string.should include(line)
|
33
|
+
}
|
34
|
+
}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'integration/jobs/print_line_job'
|
3
|
+
|
4
|
+
describe "Synchronous publishing example" do
|
5
|
+
include_context "StringIO logger"
|
6
|
+
|
7
|
+
let(:job) { PrintLineJob }
|
8
|
+
|
9
|
+
context "when publishing a message" do
|
10
|
+
after do
|
11
|
+
QueueingRabbit.purge_queue(job)
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:publishing) {
|
15
|
+
-> { QueueingRabbit.publish(job, line: "Hello, World!") }
|
16
|
+
}
|
17
|
+
|
18
|
+
it 'affects the queue size' do
|
19
|
+
expect { 5.times { publishing.call } }
|
20
|
+
.to change{QueueingRabbit.queue_size(job)}.by(5)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
$:.unshift File.expand_path('..', __FILE__)
|
2
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'bundler'
|
6
|
+
Bundler.setup(:test)
|
7
|
+
|
8
|
+
require 'rspec'
|
9
|
+
require 'rspec/autorun'
|
10
|
+
require 'evented-spec'
|
11
|
+
require 'support/shared_contexts'
|
12
|
+
require 'support/shared_examples'
|
13
|
+
|
14
|
+
require 'queueing_rabbit'
|
15
|
+
|
16
|
+
RSpec.configure do |config|
|
17
|
+
config.before(:each) {
|
18
|
+
QueueingRabbit.drop_connection
|
19
|
+
}
|
20
|
+
|
21
|
+
QueueingRabbit.configure do |qr|
|
22
|
+
qr.amqp_uri = "amqp://guest:guest@localhost:5672"
|
23
|
+
qr.amqp_exchange_name = "queueing_rabbit_test"
|
24
|
+
qr.amqp_exchange_options = {:durable => true}
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
shared_context "StringIO logger" do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
QueueingRabbit.logger = Logger.new(StringIO.new)
|
7
|
+
end
|
8
|
+
|
9
|
+
after(:all) do
|
10
|
+
QueueingRabbit.logger = Logger.new(STDOUT)
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
shared_context "Evented spec" do
|
16
|
+
include EventedSpec::SpecHelper
|
17
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
shared_examples :client do
|
2
|
+
|
3
|
+
describe '#define_queue' do
|
4
|
+
let(:exchange) { mock }
|
5
|
+
let(:channel) { mock }
|
6
|
+
let(:queue) { mock }
|
7
|
+
let(:queue_name) { "test_queue_name" }
|
8
|
+
let(:routing_keys) { [:test_job] }
|
9
|
+
let(:options) { {:durable => false, :routing_keys => routing_keys} }
|
10
|
+
|
11
|
+
before do
|
12
|
+
client.stub(:exchange => exchange)
|
13
|
+
channel.should_receive(:queue).with(queue_name, options)
|
14
|
+
.and_yield(queue)
|
15
|
+
queue.should_receive(:bind)
|
16
|
+
.with(exchange, :routing_key => routing_keys.first.to_s).ordered
|
17
|
+
queue.should_receive(:bind)
|
18
|
+
.with(exchange, :routing_key => queue_name).ordered
|
19
|
+
end
|
20
|
+
|
21
|
+
it "defines a queue and binds it to its name and the given routing keys" do
|
22
|
+
client.define_queue(channel, queue_name, options)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#define_exchange' do
|
27
|
+
let(:channel) { mock }
|
28
|
+
let(:options) { {:durable => true} }
|
29
|
+
|
30
|
+
before do
|
31
|
+
channel.should_receive(:direct)
|
32
|
+
.with(QueueingRabbit.amqp_exchange_name,
|
33
|
+
QueueingRabbit.amqp_exchange_options.merge(options))
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'defines a new AMQP direct exchange with given name and options' do
|
37
|
+
client.define_exchange(channel, options)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#enqueue' do
|
42
|
+
let(:channel) { mock }
|
43
|
+
let(:exchange) { mock }
|
44
|
+
let(:routing_key) { :routing_key }
|
45
|
+
let(:payload) { {"test" => "data"} }
|
46
|
+
|
47
|
+
before do
|
48
|
+
client.should_receive(:exchange).with(channel).and_return(exchange)
|
49
|
+
exchange.should_receive(:publish).with(JSON.dump(payload),
|
50
|
+
:key => routing_key.to_s,
|
51
|
+
:persistent => true)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "publishes a new persistent message to the used exchange with " \
|
55
|
+
"serialized payload and routed using given routing key" do
|
56
|
+
client.enqueue(channel, routing_key, payload)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe QueueingRabbit::Callbacks do
|
4
|
+
let!(:klass) { Class.new { extend QueueingRabbit::Callbacks } }
|
5
|
+
subject { klass }
|
6
|
+
|
7
|
+
context 'when a single callback is set for an event' do
|
8
|
+
let(:callback) { Proc.new {} }
|
9
|
+
|
10
|
+
before do
|
11
|
+
subject.setup_callback(:test, &callback)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'saves the callback internally' do
|
15
|
+
subject.instance_variable_get(:@callbacks).should include(:test)
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'and when an event is triggered' do
|
19
|
+
before do
|
20
|
+
callback.should_receive(:call)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'executes the registered callback' do
|
24
|
+
subject.trigger_event(:test)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'when multiple callbacks are set for an event' do
|
30
|
+
let(:callback_1) { Proc.new {} }
|
31
|
+
let(:callback_2) { Proc.new {} }
|
32
|
+
|
33
|
+
before do
|
34
|
+
subject.setup_callback(:test, &callback_1)
|
35
|
+
subject.setup_callback(:test, &callback_2)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'saves the callbacks internally' do
|
39
|
+
subject.instance_variable_get(:@callbacks).should include(:test)
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'and when an event is triggered' do
|
43
|
+
before do
|
44
|
+
callback_1.should_receive(:call)
|
45
|
+
callback_2.should_receive(:call)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'executes all the registered callbacks' do
|
49
|
+
subject.trigger_event(:test)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe QueueingRabbit::Client::AMQP do
|
4
|
+
|
5
|
+
include_context "StringIO logger"
|
6
|
+
|
7
|
+
let(:connection) { mock :on_tcp_connection_loss => nil, :on_recovery => nil }
|
8
|
+
|
9
|
+
before do
|
10
|
+
QueueingRabbit.stub(:amqp_uri => 'amqp://localhost:5672',
|
11
|
+
:amqp_exchange_name => 'queueing_rabbit_test',
|
12
|
+
:amqp_exchange_options => {:durable => true})
|
13
|
+
end
|
14
|
+
|
15
|
+
context "class" do
|
16
|
+
subject { QueueingRabbit::Client::AMQP }
|
17
|
+
|
18
|
+
its(:connection_options) { should include(:timeout) }
|
19
|
+
its(:connection_options) { should include(:heartbeat) }
|
20
|
+
its(:connection_options) { should include(:on_tcp_connection_failure) }
|
21
|
+
|
22
|
+
describe '.run_event_machine' do
|
23
|
+
context 'when the event machine reactor is running' do
|
24
|
+
before do
|
25
|
+
EM.should_receive(:reactor_running?).and_return(true)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'has no effect' do
|
29
|
+
subject.run_event_machine
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when the event machine reactor is not running' do
|
34
|
+
before do
|
35
|
+
EM.should_receive(:reactor_running?).and_return(false)
|
36
|
+
Thread.should_receive(:new).and_yield
|
37
|
+
EM.should_receive(:run).and_yield
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'runs the event machine reactor in a separate thread' do
|
41
|
+
subject.run_event_machine
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'triggers :event_machine_started event' do
|
45
|
+
QueueingRabbit.should_receive(:trigger_event)
|
46
|
+
.with(:event_machine_started)
|
47
|
+
subject.run_event_machine
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe ".connect" do
|
53
|
+
before do
|
54
|
+
AMQP.should_receive(:connect).with(QueueingRabbit.amqp_uri)
|
55
|
+
.and_return(connection)
|
56
|
+
subject.should_receive(:run_event_machine)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "creates a class' instance" do
|
60
|
+
subject.connect.should be_instance_of(subject)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '.join_event_machine_thread' do
|
65
|
+
let(:thread) { mock(:join => true) }
|
66
|
+
|
67
|
+
before do
|
68
|
+
subject.instance_variable_set(:@event_machine_thread, thread)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "joins the thread if exists" do
|
72
|
+
subject.join_event_machine_thread
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "instance" do
|
78
|
+
let(:client) { QueueingRabbit::Client::AMQP.connect }
|
79
|
+
subject { client }
|
80
|
+
|
81
|
+
before do
|
82
|
+
EM.stub(:reactor_running? => true)
|
83
|
+
AMQP.stub(:connect => connection)
|
84
|
+
QueueingRabbit::Client::AMQP.stub(:run_event_machine => true)
|
85
|
+
end
|
86
|
+
|
87
|
+
it_behaves_like :client
|
88
|
+
|
89
|
+
it { should be }
|
90
|
+
|
91
|
+
describe '#listen_queue' do
|
92
|
+
let(:channel) { mock }
|
93
|
+
let(:queue_name) { mock }
|
94
|
+
let(:options) { mock }
|
95
|
+
let(:queue) { mock }
|
96
|
+
let(:metadata) { stub(:ack => true) }
|
97
|
+
let(:data) { {:data => "data"}}
|
98
|
+
let(:payload) { JSON.dump(data) }
|
99
|
+
|
100
|
+
before do
|
101
|
+
client.should_receive(:define_queue).with(channel, queue_name, options)
|
102
|
+
.and_return(queue)
|
103
|
+
queue.should_receive(:subscribe).with(:ack => true)
|
104
|
+
.and_yield(metadata, payload)
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'listens to the queue and passes deserialized arguments to the block' do
|
108
|
+
client.listen_queue(channel, queue_name, options) do |arguments|
|
109
|
+
arguments.should == data
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context "when deserialization problems occur" do
|
114
|
+
let(:error) { JSON::JSONError.new }
|
115
|
+
before do
|
116
|
+
client.should_receive(:deserialize).and_raise(error)
|
117
|
+
client.should_receive(:error)
|
118
|
+
client.should_receive(:debug).with(error)
|
119
|
+
end
|
120
|
+
|
121
|
+
it "keeps the record of the errors" do
|
122
|
+
client.listen_queue(channel, queue_name, options)
|
123
|
+
end
|
124
|
+
|
125
|
+
it "silences JSON errors" do
|
126
|
+
expect { client.listen_queue(channel, queue_name, options) }
|
127
|
+
.to_not raise_error(error)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe '#process_message' do
|
133
|
+
let(:arguments) { mock }
|
134
|
+
|
135
|
+
it "yields given arguments to the block" do
|
136
|
+
client.process_message(arguments) do |a|
|
137
|
+
a.should == arguments
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
it "silences all errors risen" do
|
142
|
+
expect {
|
143
|
+
client.process_message(arguments) { |a| raise StandardError.new }
|
144
|
+
}.to_not raise_error(StandardError)
|
145
|
+
end
|
146
|
+
|
147
|
+
context "logging" do
|
148
|
+
let(:error) { StandardError.new }
|
149
|
+
before do
|
150
|
+
client.should_receive(:error)
|
151
|
+
client.should_receive(:debug).with(error)
|
152
|
+
end
|
153
|
+
|
154
|
+
it "keeps the record of all errors risen" do
|
155
|
+
client.process_message(arguments) { |a| raise error }
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe '#disconnect' do
|
161
|
+
before do
|
162
|
+
subject.should_receive(:info)
|
163
|
+
connection.should_receive(:close).and_yield
|
164
|
+
EM.should_receive(:stop)
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'writes the log, closes connection and stops the reactor' do
|
168
|
+
client.disconnect
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe "#open_channel" do
|
173
|
+
let(:next_channel_id) { mock }
|
174
|
+
let(:options) { mock }
|
175
|
+
let(:channel) { mock }
|
176
|
+
let(:open_ok) { mock }
|
177
|
+
|
178
|
+
before do
|
179
|
+
AMQP::Channel.should_receive(:next_channel_id)
|
180
|
+
.and_return(next_channel_id)
|
181
|
+
AMQP::Channel.should_receive(:new)
|
182
|
+
.with(connection, next_channel_id, options)
|
183
|
+
.and_yield(channel, open_ok)
|
184
|
+
channel.should_receive(:on_error)
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'opens a new AMQP channel with given options and installs ' \
|
188
|
+
'on-error callback' do
|
189
|
+
client.open_channel(options) {}
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe QueueingRabbit::Client::Bunny do
|
4
|
+
let(:connection) { stub(:start => true) }
|
5
|
+
|
6
|
+
before do
|
7
|
+
QueueingRabbit.stub(:amqp_uri => 'amqp://localhost:5672',
|
8
|
+
:amqp_exchange_name => 'queueing_rabbit_test',
|
9
|
+
:amqp_exchange_options => {:durable => true})
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'class' do
|
13
|
+
subject { QueueingRabbit::Client::Bunny }
|
14
|
+
|
15
|
+
describe '.connect' do
|
16
|
+
before do
|
17
|
+
Bunny.should_receive(:new).with(QueueingRabbit.amqp_uri)
|
18
|
+
.and_return(connection)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "instantiates an instance of itself" do
|
22
|
+
subject.connect.should be_kind_of(subject)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'instance' do
|
28
|
+
let(:client) { QueueingRabbit::Client::Bunny.connect }
|
29
|
+
subject { client }
|
30
|
+
|
31
|
+
before do
|
32
|
+
Bunny.stub(:new => connection)
|
33
|
+
end
|
34
|
+
|
35
|
+
it_behaves_like :client
|
36
|
+
|
37
|
+
it { should be }
|
38
|
+
|
39
|
+
describe '#open_channel' do
|
40
|
+
let(:options) { mock }
|
41
|
+
let(:channel) { mock }
|
42
|
+
|
43
|
+
before do
|
44
|
+
connection.should_receive(:create_channel).and_return(channel)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'creates a channel and yields it' do
|
48
|
+
client.open_channel do |c, _|
|
49
|
+
c.should == channel
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#queue_size' do
|
55
|
+
let(:queue) { mock }
|
56
|
+
let(:status) { {:message_count => 42} }
|
57
|
+
|
58
|
+
before do
|
59
|
+
queue.should_receive(:status).and_return(status)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "returns a number of messages in queue" do
|
63
|
+
client.queue_size(queue).should be_a(Fixnum)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|