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
         |