fotonauts-bunny 0.4.0

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.
@@ -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