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