fotonauts-bunny 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,28 @@
1
+ $: << File.expand_path(File.dirname(__FILE__))
2
+
3
+ require 'protocol/spec'
4
+ require 'protocol/protocol'
5
+
6
+ require 'transport/buffer'
7
+ require 'transport/frame'
8
+
9
+ require 'client'
10
+
11
+ module Qrack
12
+
13
+ include Protocol
14
+ include Transport
15
+
16
+ # Errors
17
+ class BufferOverflowError < StandardError; end
18
+ class InvalidTypeError < StandardError; end
19
+
20
+ # Qrack version number
21
+ VERSION = '0.0.1'
22
+
23
+ # Return the Qrack version
24
+ def self.version
25
+ VERSION
26
+ end
27
+
28
+ end
@@ -0,0 +1,267 @@
1
+ module Qrack
2
+ module Transport #:nodoc: all
3
+ class Buffer
4
+ require 'enumerator' if RUBY_VERSION < '1.8.7'
5
+
6
+ def initialize data = ''
7
+ @data = data
8
+ @pos = 0
9
+ end
10
+
11
+ attr_reader :pos
12
+
13
+ def data
14
+ @data.clone
15
+ end
16
+ alias :contents :data
17
+ alias :to_s :data
18
+
19
+ def << data
20
+ @data << data.to_s
21
+ self
22
+ end
23
+
24
+ def length
25
+ @data.length
26
+ end
27
+
28
+ def empty?
29
+ pos == length
30
+ end
31
+
32
+ def rewind
33
+ @pos = 0
34
+ end
35
+
36
+ def read_properties *types
37
+ types.shift if types.first == :properties
38
+
39
+ i = 0
40
+ values = []
41
+
42
+ while props = read(:short)
43
+ (0..14).each do |n|
44
+ # no more property types
45
+ break unless types[i]
46
+
47
+ # if flag is set
48
+ if props & (1<<(15-n)) != 0
49
+ if types[i] == :bit
50
+ # bit values exist in flags only
51
+ values << true
52
+ else
53
+ # save type name for later reading
54
+ values << types[i]
55
+ end
56
+ else
57
+ # property not set or is false bit
58
+ values << (types[i] == :bit ? false : nil)
59
+ end
60
+
61
+ i+=1
62
+ end
63
+
64
+ # bit(0) == 0 means no more property flags
65
+ break unless props & 1 == 1
66
+ end
67
+
68
+ values.map do |value|
69
+ value.is_a?(Symbol) ? read(value) : value
70
+ end
71
+ end
72
+
73
+ def read *types
74
+ if types.first == :properties
75
+ return read_properties(*types)
76
+ end
77
+
78
+ values = types.map do |type|
79
+ case type
80
+ when :octet
81
+ _read(1, 'C')
82
+ when :short
83
+ _read(2, 'n')
84
+ when :long
85
+ _read(4, 'N')
86
+ when :longlong
87
+ upper, lower = _read(8, 'NN')
88
+ upper << 32 | lower
89
+ when :shortstr
90
+ _read read(:octet)
91
+ when :longstr
92
+ _read read(:long)
93
+ when :timestamp
94
+ Time.at read(:longlong)
95
+ when :table
96
+ t = Hash.new
97
+
98
+ table = Buffer.new(read(:longstr))
99
+ until table.empty?
100
+ key, type = table.read(:shortstr, :octet)
101
+ key = key.intern
102
+ t[key] ||= case type
103
+ when 83 # 'S'
104
+ table.read(:longstr)
105
+ when 73 # 'I'
106
+ table.read(:long)
107
+ when 68 # 'D'
108
+ exp = table.read(:octet)
109
+ num = table.read(:long)
110
+ num / 10.0**exp
111
+ when 84 # 'T'
112
+ table.read(:timestamp)
113
+ when 70 # 'F'
114
+ table.read(:table)
115
+ end
116
+ end
117
+
118
+ t
119
+ when :bit
120
+ if (@bits ||= []).empty?
121
+ val = read(:octet)
122
+ @bits = (0..7).map{|i| (val & 1<<i) != 0 }
123
+ end
124
+
125
+ @bits.shift
126
+ else
127
+ raise Qrack::InvalidTypeError, "Cannot read data of type #{type}"
128
+ end
129
+ end
130
+
131
+ types.size == 1 ? values.first : values
132
+ end
133
+
134
+ def write type, data
135
+ case type
136
+ when :octet
137
+ _write(data, 'C')
138
+ when :short
139
+ _write(data, 'n')
140
+ when :long
141
+ _write(data, 'N')
142
+ when :longlong
143
+ lower = data & 0xffffffff
144
+ upper = (data & ~0xffffffff) >> 32
145
+ _write([upper, lower], 'NN')
146
+ when :shortstr
147
+ data = (data || '').to_s
148
+ _write([data.length, data], 'Ca*')
149
+ when :longstr
150
+ if data.is_a? Hash
151
+ write(:table, data)
152
+ else
153
+ data = (data || '').to_s
154
+ _write([data.length, data], 'Na*')
155
+ end
156
+ when :timestamp
157
+ write(:longlong, data.to_i)
158
+ when :table
159
+ data ||= {}
160
+ write :longstr, (data.inject(Buffer.new) do |table, (key, value)|
161
+ table.write(:shortstr, key.to_s)
162
+
163
+ case value
164
+ when String
165
+ table.write(:octet, 83) # 'S'
166
+ table.write(:longstr, value.to_s)
167
+ when Fixnum
168
+ table.write(:octet, 73) # 'I'
169
+ table.write(:long, value)
170
+ when Float
171
+ table.write(:octet, 68) # 'D'
172
+ # XXX there's gotta be a better way to do this..
173
+ exp = value.to_s.split('.').last.length
174
+ num = value * 10**exp
175
+ table.write(:octet, exp)
176
+ table.write(:long, num)
177
+ when Time
178
+ table.write(:octet, 84) # 'T'
179
+ table.write(:timestamp, value)
180
+ when Hash
181
+ table.write(:octet, 70) # 'F'
182
+ table.write(:table, value)
183
+ end
184
+
185
+ table
186
+ end)
187
+ when :bit
188
+ [*data].to_enum(:each_slice, 8).each{|bits|
189
+ write(:octet, bits.enum_with_index.inject(0){ |byte, (bit, i)|
190
+ byte |= 1<<i if bit
191
+ byte
192
+ })
193
+ }
194
+ when :properties
195
+ values = []
196
+ data.enum_with_index.inject(0) do |short, ((type, value), i)|
197
+ n = i % 15
198
+ last = i+1 == data.size
199
+
200
+ if (n == 0 and i != 0) or last
201
+ if data.size > i+1
202
+ short |= 1<<0
203
+ elsif last and value
204
+ values << [type,value]
205
+ short |= 1<<(15-n)
206
+ end
207
+
208
+ write(:short, short)
209
+ short = 0
210
+ end
211
+
212
+ if value and !last
213
+ values << [type,value]
214
+ short |= 1<<(15-n)
215
+ end
216
+
217
+ short
218
+ end
219
+
220
+ values.each do |type, value|
221
+ write(type, value) unless type == :bit
222
+ end
223
+ else
224
+ raise Qrack::InvalidTypeError, "Cannot write data of type #{type}"
225
+ end
226
+
227
+ self
228
+ end
229
+
230
+ def extract
231
+ begin
232
+ cur_data, cur_pos = @data.clone, @pos
233
+ yield self
234
+ rescue Qrack::BufferOverflowError
235
+ @data, @pos = cur_data, cur_pos
236
+ nil
237
+ end
238
+ end
239
+
240
+ def _read(size, pack = nil)
241
+ if @data.is_a?(Qrack::Client)
242
+ raw = @data.read(size)
243
+ return raw if raw.nil? or pack.nil?
244
+ return raw.unpack(pack).first
245
+ end
246
+
247
+ if @pos + size > length
248
+ raise Qrack::BufferOverflowError
249
+ else
250
+ data = @data[@pos,size]
251
+ @data[@pos,size] = ''
252
+ if pack
253
+ data = data.unpack(pack)
254
+ data = data.pop if data.size == 1
255
+ end
256
+ data
257
+ end
258
+ end
259
+
260
+ def _write data, pack = nil
261
+ data = [*data].pack(pack) if pack
262
+ @data[@pos,0] = data
263
+ @pos += data.length
264
+ end
265
+ end
266
+ end
267
+ end
@@ -0,0 +1,100 @@
1
+
2
+ #:stopdoc:
3
+ # this file was autogenerated on Fri May 15 10:11:23 +0100 2009
4
+ #
5
+ # DO NOT EDIT! (edit ext/qparser.rb and config.yml instead, and run 'ruby qparser.rb')
6
+
7
+ module Qrack
8
+ module Transport
9
+ class Frame
10
+ def self.types
11
+ @types ||= {}
12
+ end
13
+
14
+ def self.Frame id
15
+ (@_base_frames ||= {})[id] ||= Class.new(Frame) do
16
+ class_eval %[
17
+ def self.inherited klass
18
+ klass.const_set(:ID, #{id})
19
+ Frame.types[#{id}] = klass
20
+ end
21
+ ]
22
+ end
23
+ end
24
+
25
+ class Method < Frame( 1 ); end
26
+ class Header < Frame( 2 ); end
27
+ class Body < Frame( 3 ); end
28
+ class OobMethod < Frame( 4 ); end
29
+ class OobHeader < Frame( 5 ); end
30
+ class OobBody < Frame( 6 ); end
31
+ class Trace < Frame( 7 ); end
32
+ class Heartbeat < Frame( 8 ); end
33
+
34
+ FOOTER = 206
35
+ end
36
+ end
37
+ end
38
+
39
+ module Qrack
40
+ module Transport
41
+ class Frame
42
+ def initialize payload = nil, channel = 0
43
+ @channel, @payload = channel, payload
44
+ end
45
+ attr_accessor :channel, :payload
46
+
47
+ def id
48
+ self.class::ID
49
+ end
50
+
51
+ def to_binary
52
+ buf = Transport::Buffer.new
53
+ buf.write :octet, id
54
+ buf.write :short, channel
55
+ buf.write :longstr, payload
56
+ buf.write :octet, Transport::Frame::FOOTER
57
+ buf.rewind
58
+ buf
59
+ end
60
+
61
+ def to_s
62
+ to_binary.to_s
63
+ end
64
+
65
+ def == frame
66
+ [ :id, :channel, :payload ].inject(true) do |eql, field|
67
+ eql and __send__(field) == frame.__send__(field)
68
+ end
69
+ end
70
+
71
+ class Method
72
+ def initialize payload = nil, channel = 0
73
+ super
74
+ unless @payload.is_a? Protocol::Class::Method or @payload.nil?
75
+ @payload = Protocol.parse(@payload)
76
+ end
77
+ end
78
+ end
79
+
80
+ class Header
81
+ def initialize payload = nil, channel = 0
82
+ super
83
+ unless @payload.is_a? Protocol::Header or @payload.nil?
84
+ @payload = Protocol::Header.new(@payload)
85
+ end
86
+ end
87
+ end
88
+
89
+ class Body; end
90
+
91
+ def self.parse buf
92
+ buf = Transport::Buffer.new(buf) unless buf.is_a? Transport::Buffer
93
+ buf.extract do
94
+ id, channel, payload, footer = buf.read(:octet, :short, :longstr, :octet)
95
+ Transport::Frame.types[id].new(payload, channel) if footer == Transport::Frame::FOOTER
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,36 @@
1
+ # bunny_spec.rb
2
+
3
+ # Assumes that target message broker/server has a user called 'guest' with a password 'guest'
4
+ # and that it is running on 'localhost'.
5
+
6
+ # If this is not the case, please change the 'Bunny.new' call below to include
7
+ # the relevant arguments e.g. @b = Bunny.new(:user => 'john', :pass => 'doe', :host => 'foobar')
8
+
9
+ require File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib bunny]))
10
+
11
+ describe Bunny do
12
+
13
+ before(:each) do
14
+ @b = Bunny.new
15
+ @b.start
16
+ end
17
+
18
+ it "should connect to an AMQP server" do
19
+ @b.status.should == :connected
20
+ end
21
+
22
+ it "should be able to create an exchange" do
23
+ exch = @b.exchange('test_exchange')
24
+ exch.should be_an_instance_of Bunny::Exchange
25
+ exch.name.should == 'test_exchange'
26
+ @b.exchanges.has_key?('test_exchange').should be true
27
+ end
28
+
29
+ it "should be able to create a queue" do
30
+ q = @b.queue('test1')
31
+ q.should be_an_instance_of Bunny::Queue
32
+ q.name.should == 'test1'
33
+ @b.queues.has_key?('test1').should be true
34
+ end
35
+
36
+ end
@@ -0,0 +1,117 @@
1
+ # exchange_spec.rb
2
+
3
+ # Assumes that target message broker/server has a user called 'guest' with a password 'guest'
4
+ # and that it is running on 'localhost'.
5
+
6
+ # If this is not the case, please change the 'Bunny.new' call below to include
7
+ # the relevant arguments e.g. @b = Bunny.new(:user => 'john', :pass => 'doe', :host => 'foobar')
8
+
9
+ require File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib bunny]))
10
+
11
+ describe Bunny::Exchange do
12
+
13
+ before(:each) do
14
+ @b = Bunny.new
15
+ @b.start
16
+ end
17
+
18
+ it "should raise an error if instantiated as non-existent type" do
19
+ lambda { @b.exchange('bogus_ex', :type => :bogus) }.should raise_error(Bunny::ProtocolError)
20
+ end
21
+
22
+ it "should allow a default direct exchange to be instantiated by specifying :type" do
23
+ exch = @b.exchange('amq.direct', :type => :direct)
24
+ exch.should be_an_instance_of Bunny::Exchange
25
+ exch.name.should == 'amq.direct'
26
+ exch.type.should == :direct
27
+ @b.exchanges.has_key?('amq.direct').should be true
28
+ end
29
+
30
+ it "should allow a default direct exchange to be instantiated without specifying :type" do
31
+ exch = @b.exchange('amq.direct')
32
+ exch.should be_an_instance_of Bunny::Exchange
33
+ exch.name.should == 'amq.direct'
34
+ exch.type.should == :direct
35
+ @b.exchanges.has_key?('amq.direct').should be true
36
+ end
37
+
38
+ it "should allow a default fanout exchange to be instantiated without specifying :type" do
39
+ exch = @b.exchange('amq.fanout')
40
+ exch.should be_an_instance_of Bunny::Exchange
41
+ exch.name.should == 'amq.fanout'
42
+ exch.type.should == :fanout
43
+ @b.exchanges.has_key?('amq.fanout').should be true
44
+ end
45
+
46
+ it "should allow a default topic exchange to be instantiated without specifying :type" do
47
+ exch = @b.exchange('amq.topic')
48
+ exch.should be_an_instance_of Bunny::Exchange
49
+ exch.name.should == 'amq.topic'
50
+ exch.type.should == :topic
51
+ @b.exchanges.has_key?('amq.topic').should be true
52
+ end
53
+
54
+ # headers exchange not implemented in RabbitMQ yet. Uncomment if target broker/server supports it
55
+ #
56
+ #it "should allow a default headers exchange to be instantiated without specifying :type" do
57
+ # exch = @b.exchange('amq.match')
58
+ # exch.should be_an_instance_of Bunny::Exchange
59
+ # exch.name.should == 'amq.match'
60
+ # exch.type.should == :headers
61
+ # @b.exchanges.has_key?('amq.match').should be true
62
+ #end
63
+
64
+ it "should create an exchange as direct by default" do
65
+ exch = @b.exchange('direct_defaultex')
66
+ exch.should be_an_instance_of Bunny::Exchange
67
+ exch.name.should == 'direct_defaultex'
68
+ exch.type.should == :direct
69
+ @b.exchanges.has_key?('direct_defaultex').should be true
70
+ end
71
+
72
+ it "should be able to be instantiated as a direct exchange" do
73
+ exch = @b.exchange('direct_exchange', :type => :direct)
74
+ exch.should be_an_instance_of Bunny::Exchange
75
+ exch.name.should == 'direct_exchange'
76
+ exch.type.should == :direct
77
+ @b.exchanges.has_key?('direct_exchange').should be true
78
+ end
79
+
80
+ it "should be able to be instantiated as a topic exchange" do
81
+ exch = @b.exchange('topic_exchange', :type => :topic)
82
+ exch.should be_an_instance_of Bunny::Exchange
83
+ exch.name.should == 'topic_exchange'
84
+ exch.type.should == :topic
85
+ @b.exchanges.has_key?('topic_exchange').should be true
86
+ end
87
+
88
+ it "should be able to be instantiated as a fanout exchange" do
89
+ exch = @b.exchange('fanout_exchange', :type => :fanout)
90
+ exch.should be_an_instance_of Bunny::Exchange
91
+ exch.name.should == 'fanout_exchange'
92
+ exch.type.should == :fanout
93
+ @b.exchanges.has_key?('fanout_exchange').should be true
94
+ end
95
+
96
+ it "should ignore the :nowait option when instantiated" do
97
+ exch = @b.exchange('direct2_exchange', :nowait => true)
98
+ end
99
+
100
+ it "should be able to publish a message" do
101
+ exch = @b.exchange('direct_exchange')
102
+ exch.publish('This is a published message')
103
+ end
104
+
105
+ it "should be able to be deleted" do
106
+ exch = @b.exchange('direct_exchange')
107
+ res = exch.delete
108
+ res.should == :delete_ok
109
+ @b.exchanges.has_key?('direct_exchange').should be false
110
+ end
111
+
112
+ it "should ignore the :nowait option when deleted" do
113
+ exch = @b.exchange('direct2_exchange')
114
+ exch.delete(:nowait => true)
115
+ end
116
+
117
+ end
@@ -0,0 +1,90 @@
1
+ # queue_spec.rb
2
+
3
+ # Assumes that target message broker/server has a user called 'guest' with a password 'guest'
4
+ # and that it is running on 'localhost'.
5
+
6
+ # If this is not the case, please change the 'Bunny.new' call below to include
7
+ # the relevant arguments e.g. @b = Bunny.new(:user => 'john', :pass => 'doe', :host => 'foobar')
8
+
9
+ require File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib bunny]))
10
+
11
+ describe Bunny::Queue do
12
+
13
+ before(:each) do
14
+ @b = Bunny.new
15
+ @b.start
16
+ end
17
+
18
+ it "should ignore the :nowait option when instantiated" do
19
+ q = @b.queue('test0', :nowait => true)
20
+ end
21
+
22
+ it "should ignore the :nowait option when binding to an exchange" do
23
+ exch = @b.exchange('direct_exch')
24
+ q = @b.queue('test0')
25
+ q.bind(exch, :nowait => true).should == :bind_ok
26
+ end
27
+
28
+ it "should be able to bind to an exchange" do
29
+ exch = @b.exchange('direct_exch')
30
+ q = @b.queue('test1')
31
+ q.bind(exch).should == :bind_ok
32
+ end
33
+
34
+ it "should ignore the :nowait option when unbinding from an exchange" do
35
+ exch = @b.exchange('direct_exch')
36
+ q = @b.queue('test0')
37
+ q.unbind(exch, :nowait => true).should == :unbind_ok
38
+ end
39
+
40
+ it "should be able to unbind from an exchange" do
41
+ exch = @b.exchange('direct_exch')
42
+ q = @b.queue('test1')
43
+ q.unbind(exch).should == :unbind_ok
44
+ end
45
+
46
+ it "should be able to publish a message" do
47
+ q = @b.queue('test1')
48
+ q.publish('This is a test message')
49
+ q.message_count.should == 1
50
+ end
51
+
52
+ it "should be able to pop a message complete with header and delivery details" do
53
+ q = @b.queue('test1')
54
+ msg = q.pop(:header => true)
55
+ msg.should be_an_instance_of Hash
56
+ msg[:header].should be_an_instance_of Bunny::Protocol::Header
57
+ msg[:payload].should == 'This is a test message'
58
+ msg[:delivery_details].should be_an_instance_of Hash
59
+ q.message_count.should == 0
60
+ end
61
+
62
+ it "should be able to pop a message and just get the payload" do
63
+ q = @b.queue('test1')
64
+ q.publish('This is another test message')
65
+ msg = q.pop
66
+ msg.should == 'This is another test message'
67
+ q.message_count.should == 0
68
+ end
69
+
70
+ it "should return an empty message when popping an empty queue" do
71
+ q = @b.queue('test1')
72
+ q.publish('This is another test message')
73
+ q.pop
74
+ msg = q.pop
75
+ msg.should == :queue_empty
76
+ end
77
+
78
+ it "should be able to be deleted" do
79
+ q = @b.queue('test1')
80
+ res = q.delete
81
+ res.should == :delete_ok
82
+ @b.queues.has_key?('test1').should be false
83
+ end
84
+
85
+ it "should ignore the :nowait option when deleted" do
86
+ q = @b.queue('test0')
87
+ q.delete(:nowait => true)
88
+ end
89
+
90
+ end