zmqp 0.0.1

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