zmqp 0.0.1

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 ADDED
@@ -0,0 +1,24 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+ log
21
+ .idea
22
+ .bundle
23
+
24
+ ## PROJECT::SPECIFIC
data/HISTORY ADDED
@@ -0,0 +1,7 @@
1
+ == 0.0.0 / 2010-11-12
2
+
3
+ * Birthday!
4
+
5
+ == 0.0.1 / 2010-11-12
6
+
7
+ * README and initial setup
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Arvicco
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,76 @@
1
+ by: Arvicco
2
+ url: http://github.com/arvicco/zmqp
3
+
4
+ Description
5
+ ===========
6
+
7
+ [Asynchronous AMQP library](http://github.com/tmm1/amqp) sets a standard for messaging.
8
+ It is well documented and has clean and simple interfaces for most common messaging tasks.
9
+ However, it implements AMQP standard that has some serious
10
+ [problems and limitation](http://www.imatix.com/articles:whats-wrong-with-amqp).
11
+
12
+ [ZeroMQ](http://www.zeromq.org) offers a viable alternative to AMQP, as well as 3-10 times
13
+ performance boost for most common messaging tasks. However, its interfaces are a very similar
14
+ to socket programming and look a bit frightening for uninitiated.
15
+
16
+ This library wraps ZeroMQ/ZMQMachine with interfaces that are very similar to the ones
17
+ offered by [tmm1/amqp](http://github.com/tmm1/amqp). That way, you'll be able to leverage
18
+ your existing code developed for AMQP, getting additional benefits such as:
19
+
20
+ * Performance boost (3-10 times for most common messaging tasks)
21
+ * No broker-related bottleneck for high-throughput applications
22
+ * Ability to flexibly extend your messaging architecture
23
+ * No external dependency on 3rd-party broker software (such as RabbitMQ)
24
+
25
+ Because ZMQis inherently threaded, it does not make sense to use this library with
26
+ anything less than MRI 1.9.2, JRuby or Rubinius. MRI 1.9.1 may or may not work, MRI 1.8.7
27
+ and its below are broken beyond repair (as far as threading goes).
28
+
29
+ !!! Currently this library is at pre-alpha stage. Please do not use it for anything serious.
30
+
31
+ Getting started
32
+ ===============
33
+
34
+ First things first, start with:
35
+
36
+ $ gem install zmqp
37
+
38
+ Documentation
39
+ =============
40
+
41
+ To be added...
42
+
43
+ Credits
44
+ =======
45
+
46
+ (c) 2010 [Arvicco](http://github.com/arvicco)
47
+
48
+ This project was inspired by [tmm1/amqp](http://github.com/tmm1/amqp).
49
+ It is based on [ZMQMachine](http://github.com/chuckremes/zmqmachine).
50
+ Special thanks to Aman Gupta and Chuck Remes.
51
+
52
+ Resources
53
+ =========
54
+
55
+ * [ZeroMQ](http://www.zeromq.org) (iMatix/FastMQ/Intel, C++, GPL3)
56
+
57
+ * [Ruby bindings to ZeroMQ](http://github.com/chuckremes/ffi-rzmq)
58
+
59
+ * [Learn ZeroMQ by Example](http://github.com/andrewvc/learn-ruby-zeromq)
60
+
61
+ * Analysis of AMQP [problems](http://www.imatix.com/articles:whats-wrong-with-amqp)
62
+
63
+ * ZeroMQ's [analysis of the messaging technology market](http://www.zeromq.org/whitepapers:market-analysis)
64
+
65
+ * [A Critique of the Remote Procedure Call Paradigm](http://www.cs.vu.nl/~ast/publications/euteco-1988.pdf)
66
+
67
+ * [A Note on Distributed Computing](http://research.sun.com/techrep/1994/smli_tr-94-29.pdf)
68
+
69
+ * [Convenience Over Correctness](http://steve.vinoski.net/pdf/IEEE-Convenience_Over_Correctness.pdf)
70
+
71
+ * [Metaprotocol Taxonomy and Communications Patterns](http://hessian.caucho.com/doc/metaprotocol-taxonomy.xtp)
72
+
73
+ * Joe Armstrong on [Erlang messaging vs RPC](http://armstrongonsoftware.blogspot.com/2008/05/road-we-didnt-go-down.html)
74
+
75
+ == LICENSE:
76
+ Copyright (c) 2010 Arvicco. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require 'pathname'
2
+ BASE_PATH = Pathname.new(__FILE__).dirname
3
+ LIB_PATH = BASE_PATH + 'lib'
4
+ PKG_PATH = BASE_PATH + 'pkg'
5
+ DOC_PATH = BASE_PATH + 'rdoc'
6
+
7
+ $LOAD_PATH.unshift LIB_PATH.to_s
8
+
9
+ require 'version'
10
+ require 'rake'
11
+
12
+ NAME = 'zmqp'
13
+ CLASS_NAME = ZMQP
14
+ VERSION = CLASS_NAME::VERSION
15
+
16
+ # Load rakefile tasks
17
+ Dir['tasks/*.rake'].sort.each { |file| load file }
18
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/bin/zmqp ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pathname'
4
+ require Pathname.new(__FILE__).dirname + '../lib/zmqp'
5
+
6
+ # Put your code here
7
+
File without changes
@@ -0,0 +1,10 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
2
+
3
+ require 'pathname'
4
+ require 'bundler'
5
+ Bundler.setup
6
+ Bundler.require :cucumber
7
+
8
+ require 'zmqp'
9
+
10
+ BASE_PATH = Pathname.new(__FILE__).dirname + '../..'
@@ -0,0 +1,12 @@
1
+ module SystemHelper
2
+
3
+ def windows?
4
+ RUBY_PLATFORM =~ /mswin|windows|mingw/ || cygwin?
5
+ end
6
+
7
+ def cygwin?
8
+ RUBY_PLATFORM =~ /cygwin/
9
+ end
10
+ end
11
+
12
+ World(WinGui, SystemHelper)
@@ -0,0 +1,9 @@
1
+ Feature: something something
2
+ In order to something something
3
+ A user something something
4
+ something something something
5
+
6
+ Scenario: something something
7
+ Given inspiration
8
+ When I create a sweet new gem
9
+ Then everyone should see how awesome I am
data/lib/version.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'pathname'
2
+
3
+ module ZMQP
4
+
5
+ VERSION_FILE = Pathname.new(__FILE__).dirname + '../VERSION' # :nodoc:
6
+ VERSION = VERSION_FILE.exist? ? VERSION_FILE.read.strip : nil
7
+
8
+ end
data/lib/zmqp.rb ADDED
@@ -0,0 +1,127 @@
1
+ require 'version'
2
+
3
+ module ZMQP
4
+
5
+ require "bundler/setup"
6
+ Bundler.require :default
7
+
8
+ # Requires ruby source file(s). Accepts either single filename/glob or Array of filenames/globs.
9
+ # Accepts following options:
10
+ # :*file*:: Lib(s) required relative to this file - defaults to __FILE__
11
+ # :*dir*:: Required lib(s) located under this dir name - defaults to gem name
12
+ #
13
+ def self.require_libs( libs, opts={} )
14
+ file = Pathname.new(opts[:file] || __FILE__)
15
+ [libs].flatten.each do |lib|
16
+ name = file.dirname + (opts[:dir] || file.basename('.*')) + lib.gsub(/(?<!.rb)$/, '.rb')
17
+ Pathname.glob(name.to_s).sort.each {|rb| require rb}
18
+ end
19
+ end
20
+ end # module ZMQP
21
+
22
+ require 'zmqmachine'
23
+
24
+ # Did I say that ZMQP is a drop-in substitution for AMQP?
25
+ AMQP = ZMQP unless defined? AMQP
26
+ EM = ZM unless defined? EM
27
+
28
+ # Require all ruby source files located under directory lib/zmqp
29
+ # If you need files in specific order, you should specify it here before the glob
30
+ ZMQP.require_libs %W[**/*]
31
+
32
+
33
+ module ZMQP
34
+ class << self
35
+ @logging = false
36
+ attr_accessor :logging
37
+ attr_reader :conn, :closing
38
+ alias :closing? :closing
39
+ alias :connection :conn
40
+ end
41
+
42
+ def self.connect *args
43
+ Client.connect *args
44
+ end
45
+
46
+ def self.settings
47
+ @settings ||= {
48
+ # broker address
49
+ :host => '127.0.0.1',
50
+ :port => PORT,
51
+
52
+ # login details
53
+ :user => 'guest',
54
+ :pass => 'guest',
55
+ :vhost => '/',
56
+
57
+ # connection timeout
58
+ :timeout => nil,
59
+
60
+ # logging
61
+ :logging => false,
62
+
63
+ # ssl
64
+ :ssl => false
65
+ }
66
+ end
67
+
68
+ # Must be called to startup the connection to lightweight service broker (was:the AMQP server).
69
+ #
70
+ # The method takes several arguments and an optional block.
71
+ #
72
+ # This takes any option that is also accepted by *??* ZMQMachine::connect.
73
+ # Additionally, there are several ZMQP-specific options.
74
+ #
75
+ # * :user => String (default 'guest')
76
+ # The username as defined by the AMQP server.
77
+ # * :pass => String (default 'guest')
78
+ # The password for the associated :user as defined by the AMQP server.
79
+ # * :vhost => String (default '/')
80
+ # The virtual host as defined by the AMQP server.
81
+ # * :timeout => Numeric (default nil)
82
+ # Measured in seconds.
83
+ # * :logging => true | false (default false)
84
+ # Toggle the extremely verbose logging of all protocol communications
85
+ # between the client and the server. Extremely useful for debugging.
86
+ #
87
+ # AMQP.start do
88
+ # # default is to connect to localhost:5672
89
+ #
90
+ # # define queues, exchanges and bindings here.
91
+ # # also define all subscriptions and/or publishers
92
+ # # here.
93
+ #
94
+ # # this block never exits unless EM.stop_event_loop
95
+ # # is called.
96
+ # end
97
+ #
98
+ # Most code will use the MQ api. Any calls to MQ.direct / MQ.fanout /
99
+ # MQ.topic / MQ.queue will implicitly call #start. In those cases,
100
+ # it is sufficient to put your code inside of an EventMachine.run
101
+ # block. See the code examples in MQ for details.
102
+ #
103
+ def self.start *args, &blk
104
+ EM.run{
105
+ @conn ||= connect *args
106
+ @conn.callback(&blk) if blk
107
+ @conn
108
+ }
109
+ end
110
+
111
+ class << self
112
+ alias :run :start
113
+ end
114
+
115
+ def self.stop
116
+ if @conn and not @closing
117
+ @closing = true
118
+ @conn.close{
119
+ yield if block_given?
120
+ @conn = nil
121
+ @closing = false
122
+ }
123
+ end
124
+ end
125
+
126
+ end
127
+
@@ -0,0 +1,17 @@
1
+ module AMQP
2
+
3
+ # ZMQP Client impersonates AMQP Client/Connection.
4
+ # As such, it hides the difference between AMQP and ZMQ communication.
5
+ # This means that the Client should be encapsulating all the knowledge about
6
+ # ZMQ Sockets, socket handlers etc. It should probably do all the ZMQ "frame processing"
7
+ # inside, accepting only raw sum/pub/pop/etc from MQ and feeding it back raw
8
+ # data/exceptions
9
+ #
10
+ # Do we need Client at all? What is its role exactly?
11
+ # Maybe MQ instance alone is enough to hide Z/A difference, and actual communication
12
+ # will flow through "pseudo"-Exchanges and Queues anyway.
13
+ #
14
+ module Client
15
+
16
+ end
17
+ end
data/lib/zmqp/spec.rb ADDED
@@ -0,0 +1,7 @@
1
+ # This file contains all specifications and conventions
2
+ # that allow ZMQP to impersonate AMQP
3
+
4
+ module ZMQP
5
+ # PORT to be used by lightweight ZMQP broker (more like service registry, in fact)
6
+ PORT = 5673 # 5672
7
+ end
data/spec/amqp.yml ADDED
@@ -0,0 +1,28 @@
1
+ # AMQP client configuration file
2
+
3
+ # These values will be used to configure the ampq gem, any values
4
+ # omitted will let the gem use it's own defaults.
5
+
6
+ # The configuration specifies the following keys:
7
+ # * user - Username for the broker
8
+ # * pass - Password for the broker
9
+ # * host - Hostname where the broker is running
10
+ # * vhost - Vhost to connect to
11
+ # * port - Port where the broker is running
12
+ # * ssl - Use ssl or not
13
+ # * timeout - Timeout
14
+
15
+ defaults: &defaults
16
+ user: guest
17
+ pass: guest
18
+ host: 10.211.55.2
19
+ vhost: /
20
+
21
+ development:
22
+ <<: *defaults
23
+
24
+ test:
25
+ <<: *defaults
26
+
27
+ production:
28
+ <<: *defaults
data/spec/mq_spec.rb ADDED
@@ -0,0 +1,436 @@
1
+ require 'spec_helper'
2
+ require 'amqp-spec/rspec'
3
+
4
+ require 'mq'
5
+
6
+ # Mocking AMQP::Client::EM_CONNECTION_CLASS in order to
7
+ # specify MQ instance behavior without the need to start EM loop.
8
+ class MockConnection
9
+
10
+ def initialize
11
+ EM.should_receive(:reactor_running?).and_return(true)
12
+ end
13
+
14
+ def callback &block
15
+ callbacks << block
16
+ block.call(self) if block
17
+ end
18
+
19
+ def add_channel mq
20
+ channels[key = (channels.keys.max || 0) + 1] = mq
21
+ key
22
+ end
23
+
24
+ def send data, opts = {}
25
+ messages << {data: data, opts: opts}
26
+ end
27
+
28
+ def connected?
29
+ true
30
+ end
31
+
32
+ def channels
33
+ @channels||={}
34
+ end
35
+
36
+ # Not part of AMQP::Client::EM_CONNECTION_CLASS interface, for mock introspection only
37
+ def callbacks
38
+ @callbacks||=[]
39
+ end
40
+
41
+ def messages
42
+ @messages||=[]
43
+ end
44
+
45
+ def close
46
+ end
47
+ end
48
+
49
+ describe 'MQ', 'as a class' do
50
+ # after{AMQP.cleanup_state} # Tips off Thread.current[:mq] call
51
+ subject { MQ }
52
+
53
+ its(:logging) { should be_false }
54
+
55
+ describe '.logging=' do
56
+ it 'is independent from AMQP.logging' do
57
+ #
58
+ AMQP.logging = true
59
+ MQ.logging.should be_false
60
+ MQ.logging = false
61
+ AMQP.logging.should == true
62
+
63
+ #
64
+ AMQP.logging = false
65
+ MQ.logging = true
66
+ AMQP.logging.should be_false
67
+ MQ.logging = false
68
+ end
69
+ end
70
+
71
+ describe '.default' do
72
+ it 'creates MQ object and stashes it in Thread-local Hash' do
73
+ MQ.should_receive(:new).with(no_args).and_return('MQ mock')
74
+ Thread.current.should_receive(:[]).with(:mq)
75
+ Thread.current.should_receive(:[]=).with(:mq, 'MQ mock')
76
+ MQ.default.should == 'MQ mock'
77
+ end
78
+ end
79
+
80
+ describe '.id' do
81
+ it 'returns Thread-local uuid for mq' do
82
+ Thread.current.should_receive(:[]).with(:mq_id)
83
+ Thread.current.should_receive(:[]=).with(:mq_id, /.*-[0-9]{4,5}-[0-9]{10}/)
84
+ MQ.id.should =~ /.*-[0-9]{4,5}-[0-9]{10}/
85
+ end
86
+ end
87
+
88
+ describe '.error' do
89
+ it 'is noop unless @error_callback was previously set' do
90
+ MQ.instance_variable_get(:@error_callback).should be_nil
91
+ MQ.error("Whatever").should be_nil
92
+ end
93
+
94
+ it 'is setting @error_callback if block is given' do
95
+ MQ.error("Whatever") { |message| message.should == "Whatever"; @callback_fired = true }
96
+ MQ.instance_variable_get(:@error_callback).should_not be_nil
97
+ @callback_fired.should be_false
98
+ end
99
+
100
+ it 'is causes @error_callback to fire if it was set' do
101
+ MQ.error("Whatever")
102
+ @callback_fired.should be_true
103
+ end
104
+ end
105
+
106
+ describe '.method_missing' do
107
+ it 'routes all unrecognized methods to MQ.default' do
108
+ MQ.should_receive(:new).with(no_args).and_return(mock('schmock'))
109
+ MQ.default.should_receive(:abracadabra).with("Whatever")
110
+ MQ.abracadabra("Whatever")
111
+ end
112
+ end
113
+ end
114
+
115
+ describe 'MQ', 'object, also vaguely known as "channel"' do
116
+
117
+ context 'when initialized with a mock connection' do
118
+ before { @conn = MockConnection.new }
119
+ after { AMQP.cleanup_state }
120
+ subject { MQ.new(@conn).tap { |mq| mq.succeed } } # Indicates that channel is connected
121
+ # its(:connection) { should be_nil } - relies on subject.send(:connection), not working
122
+
123
+ it 'has public accessors' do
124
+ subject.channel.should == 1 # Essentially, channel number
125
+ subject.consumers.should be_a Hash
126
+ subject.consumers.should be_empty
127
+ subject.exchanges.should be_a Hash
128
+ subject.exchanges.should be_empty
129
+ subject.queues.should be_a Hash
130
+ subject.queues.should be_empty
131
+ subject.rpcs.should be_a Hash
132
+ subject.rpcs.should be_empty
133
+
134
+ #connection was declared as both private and public accessor for some reason... Why?
135
+ subject.connection.should == @conn
136
+ subject.conn.should == @conn
137
+ end
138
+
139
+ describe '#process_frame' do # The meat of mq operations
140
+ describe ' Frame::Header'
141
+ describe ' Frame::Body'
142
+ describe ' Frame::Method'
143
+ describe ' Protocol::Channel::OpenOk'
144
+ describe ' Protocol::Access::RequestOk'
145
+ describe ' Protocol::Basic::CancelOk'
146
+ describe ' Protocol::Queue::DeclareOk'
147
+ describe ' Protocol::Basic::GetOk'
148
+ describe ' Protocol::Basic::Deliver'
149
+ describe ' Protocol::Basic::GetEmpty'
150
+ describe ' Protocol::Channel::Close'
151
+ describe ' Protocol::Basic::ConsumeOk'
152
+
153
+ describe 'Protocol::Channel::CloseOk' do
154
+ it 'closes the channel down' do
155
+ @conn.channels.should_receive(:delete) { |key| key.should == 1; @conn.channels.clear }
156
+ subject.process_frame AMQP::Frame::Method.new(AMQP::Protocol::Channel::CloseOk.new)
157
+ end
158
+
159
+ it 'closes down connection if no other channels' do
160
+ @conn.should_receive(:close)
161
+ subject.process_frame AMQP::Frame::Method.new(AMQP::Protocol::Channel::CloseOk.new)
162
+ end
163
+ end
164
+ end
165
+
166
+ describe '#send' do
167
+ it 'sends given data through its connection' do
168
+ args = [mock('data1'), mock('data2'), mock('data3')]
169
+
170
+ subject.send *args
171
+
172
+ @conn.messages[-3..-1].map { |message| message[:data] }.should == args
173
+ end
174
+
175
+ it 'sets data`s ticket property if @ticket is set for MQ object' do
176
+ subject.instance_variable_set(:@ticket, ticket = 'mock ticket')
177
+ data = mock('data1')
178
+ data.should_receive(:ticket=).with(ticket)
179
+
180
+ subject.send data
181
+ end
182
+
183
+ it 'is Mutex-synchronized' do
184
+ mutex = subject.instance_variable_get(:@_send_mutex)
185
+ mutex.should_receive(:synchronize)
186
+ subject.send mock('data1')
187
+ end
188
+ end
189
+
190
+ describe '#reset' do
191
+ it 'resets and reinitializes the channel, clears and resets its queues/exchanges' do
192
+ subject.queue('test').should_receive(:reset)
193
+ subject.fanout('fanout').should_receive(:reset)
194
+ subject.should_receive(:initialize).with(@conn)
195
+
196
+ subject.reset
197
+ subject.queues.should be_empty
198
+ subject.exchanges.should be_empty
199
+ subject.consumers.should be_empty
200
+ end
201
+ end
202
+
203
+ describe '#prefetch' do
204
+ it 'sends Protocol::Basic::Qos, setting :prefetch_count for broker' do
205
+ subject.prefetch 13
206
+ @conn.messages.last[:data].should be_an AMQP::Protocol::Basic::Qos
207
+ @conn.messages.last[:data].instance_variable_get(:@prefetch_count).should == 13
208
+ end
209
+
210
+ it 'returns MQ object itself, allowing for method chains' do
211
+ subject.prefetch(1).should == subject
212
+ end
213
+ end
214
+
215
+ describe '#recover' do
216
+ it 'sends Protocol::Basic::Recover, asking broker to redeliver all unack`ed messages on this channel' do
217
+ subject.recover
218
+ @conn.messages.last[:data].should be_an AMQP::Protocol::Basic::Recover
219
+ end
220
+
221
+ it 'by default, requeue property is nil, so messages will be redelivered to the original recipient' do
222
+ subject.recover
223
+ @conn.messages.last[:data].instance_variable_get(:@requeue).should == nil
224
+ end
225
+
226
+ it 'you can set requeue to true, prompting broker to requeue the messages (to other subscribers, potentially)' do
227
+ subject.recover true
228
+ @conn.messages.last[:data].instance_variable_get(:@requeue).should == true
229
+ end
230
+
231
+ it 'returns MQ object itself, allowing for method chains' do
232
+ subject.recover.should == subject
233
+ end
234
+ end
235
+
236
+ describe '#close' do
237
+ it 'can be simplified, getting rid of @closing ivar? Just set callback sending Protocol::Channel::Close...'
238
+
239
+ it 'sends Protocol::Channel::Close through @connection' do
240
+ subject.close
241
+ @conn.messages.last[:data].should be_an AMQP::Protocol::Channel::Close
242
+ end
243
+
244
+ it 'does not actually delete the channel or close connection' do
245
+ @conn.channels.should_not_receive(:delete)
246
+ @conn.should_not_receive(:close)
247
+ subject.close
248
+ end
249
+
250
+ it 'actual channel closing happens ONLY when Protocol::Channel::CloseOk is received' do
251
+ @conn.channels.should_receive(:delete) { |key| key.should == 1; @conn.channels.clear }
252
+ @conn.should_receive(:close)
253
+ subject.process_frame AMQP::Frame::Method.new(AMQP::Protocol::Channel::CloseOk.new)
254
+ end
255
+ end
256
+
257
+ describe '#get_queue' do
258
+ it 'yields a FIFO queue to a given block' do
259
+ subject.get_queue do |fifo|
260
+ fifo.should == []
261
+ end
262
+ end
263
+
264
+ it 'FIFO queue contains consumers that called Queue#pop' do
265
+ queue = subject.queue('test')
266
+ queue.pop
267
+ queue.pop
268
+ subject.get_queue do |fifo|
269
+ fifo.should have(2).consumers
270
+ fifo.each { |consumer| consumer.should == queue }
271
+ end
272
+ end
273
+
274
+ it 'is Mutex-synchronized' do
275
+ subject # Evaluates subject, tripping add_channel Mutex at initialize
276
+ mutex = Mutex.new
277
+ Mutex.should_receive(:new).and_return(mutex)
278
+ mutex.should_receive(:synchronize)
279
+ subject.get_queue {}
280
+ end
281
+ end
282
+
283
+ describe '#connected?' do # This is an addition to standard MQ interface
284
+ it 'delegates to @connection to determine its connectivity status' do
285
+ @conn.should_receive(:connected?).with(no_args)
286
+ subject.connected?
287
+ end
288
+ end
289
+
290
+ describe 'setting up exchanges/queues/rpcs' do
291
+ describe '#rpc' do
292
+ it 'creates new rpc and saves it into @rpcs Hash' do
293
+ MQ::RPC.should_receive(:new).with(subject, 'name', nil).and_return('mock_rpc')
294
+ subject.rpcs.should_receive(:[]=).with('name', 'mock_rpc')
295
+ rpc = subject.rpc 'name'
296
+ rpc.should == 'mock_rpc'
297
+ end
298
+
299
+ it 'raises error rpc if no name given' do
300
+ expect { subject.rpc }.to raise_error ArgumentError
301
+ end
302
+
303
+ it 'does not replace rpc with existing name' do
304
+ MQ::RPC.should_receive(:new).with(subject, 'name', nil).and_return('mock_rpc')
305
+ subject.rpc 'name'
306
+ MQ::RPC.should_not_receive(:new)
307
+ subject.rpcs.should_not_receive(:[]=)
308
+ rpc = subject.rpc 'name'
309
+ rpc.should == 'mock_rpc'
310
+ end
311
+ end
312
+
313
+ describe '#queue' do
314
+ it 'creates new Queue and saves it into @queues Hash' do
315
+ MQ::Queue.should_receive(:new).with(subject, 'name', {}).and_return('mock_queue')
316
+ subject.queues.should_receive(:[]=).with('name', 'mock_queue')
317
+ queue = subject.queue 'name'
318
+ queue.should == 'mock_queue'
319
+ end
320
+
321
+ it 'raises error Queue if no name given' do
322
+ expect { subject.queue }.to raise_error ArgumentError
323
+ end
324
+
325
+ it 'does not replace queue with existing name' do
326
+ MQ::Queue.should_receive(:new).with(subject, 'name', {}).and_return('mock_queue')
327
+ subject.queue 'name'
328
+ MQ::Queue.should_not_receive(:new)
329
+ subject.queues.should_not_receive(:[]=)
330
+ queue = subject.queue 'name'
331
+ queue.should == 'mock_queue'
332
+ end
333
+ end
334
+
335
+ describe '#direct' do
336
+ it 'creates new :direct Exchange and saves it into @exchanges Hash' do
337
+ MQ::Exchange.should_receive(:new).with(subject, :direct, 'name', {}).and_return('mock_exchange')
338
+ subject.exchanges.should_receive(:[]=).with('name', 'mock_exchange')
339
+ exchange = subject.direct 'name'
340
+ exchange.should == 'mock_exchange'
341
+ end
342
+
343
+ it 'creates "amq.direct" Exchange if no name given' do
344
+ MQ::Exchange.should_receive(:new).with(subject, :direct, 'amq.direct', {}).and_return('mock_exchange')
345
+ subject.exchanges.should_receive(:[]=).with('amq.direct', 'mock_exchange')
346
+ exchange = subject.direct
347
+ exchange.should == 'mock_exchange'
348
+ end
349
+
350
+ it 'does not replace exchange with existing name' do
351
+ MQ::Exchange.should_receive(:new).with(subject, :direct, 'name', {}).and_return('mock_exchange')
352
+ subject.direct 'name'
353
+ MQ::Exchange.should_not_receive(:new)
354
+ subject.exchanges.should_not_receive(:[]=)
355
+ exchange = subject.direct 'name'
356
+ exchange.should == 'mock_exchange'
357
+ end
358
+ end
359
+
360
+ describe '#fanout' do
361
+ it 'creates new :fanout Exchange and saves it into @exchanges Hash' do
362
+ MQ::Exchange.should_receive(:new).with(subject, :fanout, 'name', {}).and_return('mock_exchange')
363
+ subject.exchanges.should_receive(:[]=).with('name', 'mock_exchange')
364
+ exchange = subject.fanout 'name'
365
+ exchange.should == 'mock_exchange'
366
+ end
367
+
368
+ it 'creates "amq.fanout" Exchange if no name given' do
369
+ MQ::Exchange.should_receive(:new).with(subject, :fanout, 'amq.fanout', {}).and_return('mock_exchange')
370
+ subject.exchanges.should_receive(:[]=).with('amq.fanout', 'mock_exchange')
371
+ exchange = subject.fanout
372
+ exchange.should == 'mock_exchange'
373
+ end
374
+
375
+ it 'does not replace exchange with existing name' do
376
+ MQ::Exchange.should_receive(:new).with(subject, :fanout, 'name', {}).and_return('mock_exchange')
377
+ subject.fanout 'name'
378
+ MQ::Exchange.should_not_receive(:new)
379
+ subject.exchanges.should_not_receive(:[]=)
380
+ exchange = subject.fanout 'name'
381
+ exchange.should == 'mock_exchange'
382
+ end
383
+ end
384
+
385
+ describe '#topic' do
386
+ it 'creates new :topic Exchange and saves it into @exchanges Hash' do
387
+ MQ::Exchange.should_receive(:new).with(subject, :topic, 'name', {}).and_return('mock_exchange')
388
+ subject.exchanges.should_receive(:[]=).with('name', 'mock_exchange')
389
+ exchange = subject.topic 'name'
390
+ exchange.should == 'mock_exchange'
391
+ end
392
+
393
+ it 'creates "amq.topic" Exchange if no name given' do
394
+ MQ::Exchange.should_receive(:new).with(subject, :topic, 'amq.topic', {}).and_return('mock_exchange')
395
+ subject.exchanges.should_receive(:[]=).with('amq.topic', 'mock_exchange')
396
+ exchange = subject.topic
397
+ exchange.should == 'mock_exchange'
398
+ end
399
+
400
+ it 'does not replace exchange with existing name' do
401
+ MQ::Exchange.should_receive(:new).with(subject, :topic, 'name', {}).and_return('mock_exchange')
402
+ subject.topic 'name'
403
+ MQ::Exchange.should_not_receive(:new)
404
+ subject.exchanges.should_not_receive(:[]=)
405
+ exchange = subject.topic 'name'
406
+ exchange.should == 'mock_exchange'
407
+ end
408
+ end
409
+
410
+ describe '#headers' do
411
+ it 'creates new :headers Exchange and saves it into @exchanges Hash' do
412
+ MQ::Exchange.should_receive(:new).with(subject, :headers, 'name', {}).and_return('mock_exchange')
413
+ subject.exchanges.should_receive(:[]=).with('name', 'mock_exchange')
414
+ exchange = subject.headers 'name'
415
+ exchange.should == 'mock_exchange'
416
+ end
417
+
418
+ it 'creates "amq.match" Exchange if no name given' do
419
+ MQ::Exchange.should_receive(:new).with(subject, :headers, 'amq.match', {}).and_return('mock_exchange')
420
+ subject.exchanges.should_receive(:[]=).with('amq.match', 'mock_exchange')
421
+ exchange = subject.headers
422
+ exchange.should == 'mock_exchange'
423
+ end
424
+
425
+ it 'does not replace exchange with existing name' do
426
+ MQ::Exchange.should_receive(:new).with(subject, :headers, 'name', {}).and_return('mock_exchange')
427
+ subject.headers 'name'
428
+ MQ::Exchange.should_not_receive(:new)
429
+ subject.exchanges.should_not_receive(:[]=)
430
+ exchange = subject.headers 'name'
431
+ exchange.should == 'mock_exchange'
432
+ end
433
+ end
434
+ end
435
+ end
436
+ end