adamh-amqp 0.6.3.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/README +128 -0
- data/Rakefile +15 -0
- data/amqp.gemspec +83 -0
- data/amqp.todo +32 -0
- data/doc/EXAMPLE_01_PINGPONG +2 -0
- data/doc/EXAMPLE_02_CLOCK +2 -0
- data/doc/EXAMPLE_03_STOCKS +2 -0
- data/doc/EXAMPLE_04_MULTICLOCK +2 -0
- data/doc/EXAMPLE_05_ACK +2 -0
- data/doc/EXAMPLE_05_POP +2 -0
- data/doc/EXAMPLE_06_HASHTABLE +2 -0
- data/examples/amqp/simple.rb +79 -0
- data/examples/mq/ack.rb +45 -0
- data/examples/mq/clock.rb +56 -0
- data/examples/mq/hashtable.rb +52 -0
- data/examples/mq/internal.rb +49 -0
- data/examples/mq/logger.rb +88 -0
- data/examples/mq/multiclock.rb +49 -0
- data/examples/mq/pingpong.rb +45 -0
- data/examples/mq/pop.rb +43 -0
- data/examples/mq/primes-simple.rb +19 -0
- data/examples/mq/primes.rb +99 -0
- data/examples/mq/stocks.rb +58 -0
- data/lib/amqp.rb +115 -0
- data/lib/amqp/buffer.rb +395 -0
- data/lib/amqp/client.rb +210 -0
- data/lib/amqp/frame.rb +124 -0
- data/lib/amqp/protocol.rb +212 -0
- data/lib/amqp/server.rb +99 -0
- data/lib/amqp/spec.rb +832 -0
- data/lib/ext/blankslate.rb +7 -0
- data/lib/ext/em.rb +51 -0
- data/lib/ext/emfork.rb +69 -0
- data/lib/mq.rb +823 -0
- data/lib/mq/exchange.rb +302 -0
- data/lib/mq/header.rb +33 -0
- data/lib/mq/logger.rb +89 -0
- data/lib/mq/queue.rb +433 -0
- data/lib/mq/rpc.rb +100 -0
- data/old/README +30 -0
- data/old/Rakefile +12 -0
- data/old/amqp-0.8.json +606 -0
- data/old/amqp_spec.rb +796 -0
- data/old/amqpc.rb +695 -0
- data/old/codegen.rb +148 -0
- data/protocol/amqp-0.8.json +617 -0
- data/protocol/amqp-0.8.xml +3908 -0
- data/protocol/codegen.rb +173 -0
- data/protocol/doc.txt +281 -0
- data/research/api.rb +88 -0
- data/research/primes-forked.rb +63 -0
- data/research/primes-processes.rb +135 -0
- data/research/primes-threaded.rb +49 -0
- metadata +121 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__) + '/../../lib'
|
2
|
+
require 'mq'
|
3
|
+
|
4
|
+
AMQP.start(:host => 'localhost') do
|
5
|
+
|
6
|
+
def log *args
|
7
|
+
p [ Time.now, *args ]
|
8
|
+
end
|
9
|
+
|
10
|
+
def publish_stock_prices
|
11
|
+
mq = MQ.new
|
12
|
+
EM.add_periodic_timer(1){
|
13
|
+
puts
|
14
|
+
|
15
|
+
{ :appl => 170+rand(1000)/100.0,
|
16
|
+
:msft => 22+rand(500)/100.0
|
17
|
+
}.each do |stock, price|
|
18
|
+
stock = "usd.#{stock}"
|
19
|
+
|
20
|
+
log :publishing, stock, price
|
21
|
+
mq.topic('stocks').publish(price, :key => stock)
|
22
|
+
end
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def watch_appl_stock
|
27
|
+
mq = MQ.new
|
28
|
+
mq.queue('apple stock').bind(mq.topic('stocks'), :key => 'usd.appl').subscribe{ |price|
|
29
|
+
log 'apple stock', price
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def watch_us_stocks
|
34
|
+
mq = MQ.new
|
35
|
+
mq.queue('us stocks').bind(mq.topic('stocks'), :key => 'usd.*').subscribe{ |info, price|
|
36
|
+
log 'us stock', info.routing_key, price
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
publish_stock_prices
|
41
|
+
watch_appl_stock
|
42
|
+
watch_us_stocks
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
__END__
|
47
|
+
|
48
|
+
[Fri Aug 15 01:39:00 -0700 2008, :publishing, "usd.appl", 173.45]
|
49
|
+
[Fri Aug 15 01:39:00 -0700 2008, :publishing, "usd.msft", 26.98]
|
50
|
+
[Fri Aug 15 01:39:00 -0700 2008, "apple stock", "173.45"]
|
51
|
+
[Fri Aug 15 01:39:00 -0700 2008, "us stock", "usd.appl", "173.45"]
|
52
|
+
[Fri Aug 15 01:39:00 -0700 2008, "us stock", "usd.msft", "26.98"]
|
53
|
+
|
54
|
+
[Fri Aug 15 01:39:01 -0700 2008, :publishing, "usd.appl", 179.72]
|
55
|
+
[Fri Aug 15 01:39:01 -0700 2008, :publishing, "usd.msft", 26.56]
|
56
|
+
[Fri Aug 15 01:39:01 -0700 2008, "apple stock", "179.72"]
|
57
|
+
[Fri Aug 15 01:39:01 -0700 2008, "us stock", "usd.appl", "179.72"]
|
58
|
+
[Fri Aug 15 01:39:01 -0700 2008, "us stock", "usd.msft", "26.56"]
|
data/lib/amqp.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
module AMQP
|
2
|
+
VERSION = '0.5.9'
|
3
|
+
|
4
|
+
DIR = File.expand_path(File.dirname(File.expand_path(__FILE__)))
|
5
|
+
$:.unshift DIR
|
6
|
+
|
7
|
+
require 'ext/em'
|
8
|
+
require 'ext/blankslate'
|
9
|
+
|
10
|
+
%w[ buffer spec protocol frame client ].each do |file|
|
11
|
+
require "amqp/#{file}"
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
@logging = false
|
16
|
+
attr_accessor :logging
|
17
|
+
attr_reader :conn, :closing
|
18
|
+
alias :closing? :closing
|
19
|
+
alias :connection :conn
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.connect *args
|
23
|
+
Client.connect *args
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.settings
|
27
|
+
@settings ||= {
|
28
|
+
# server address
|
29
|
+
:host => '127.0.0.1',
|
30
|
+
:port => PORT,
|
31
|
+
|
32
|
+
# login details
|
33
|
+
:user => 'guest',
|
34
|
+
:pass => 'guest',
|
35
|
+
:vhost => '/',
|
36
|
+
|
37
|
+
# connection timeout
|
38
|
+
:timeout => nil,
|
39
|
+
|
40
|
+
# logging
|
41
|
+
:logging => false,
|
42
|
+
|
43
|
+
# ssl
|
44
|
+
:ssl => false
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
# Must be called to startup the connection to the AMQP server.
|
49
|
+
#
|
50
|
+
# The method takes several arguments and an optional block.
|
51
|
+
#
|
52
|
+
# This takes any option that is also accepted by EventMachine::connect.
|
53
|
+
# Additionally, there are several AMQP-specific options.
|
54
|
+
#
|
55
|
+
# * :user => String (default 'guest')
|
56
|
+
# The username as defined by the AMQP server.
|
57
|
+
# * :pass => String (default 'guest')
|
58
|
+
# The password for the associated :user as defined by the AMQP server.
|
59
|
+
# * :vhost => String (default '/')
|
60
|
+
# The virtual host as defined by the AMQP server.
|
61
|
+
# * :timeout => Numeric (default nil)
|
62
|
+
# Measured in seconds.
|
63
|
+
# * :logging => true | false (default false)
|
64
|
+
# Toggle the extremely verbose logging of all protocol communications
|
65
|
+
# between the client and the server. Extremely useful for debugging.
|
66
|
+
#
|
67
|
+
# AMQP.start do
|
68
|
+
# # default is to connect to localhost:5672
|
69
|
+
#
|
70
|
+
# # define queues, exchanges and bindings here.
|
71
|
+
# # also define all subscriptions and/or publishers
|
72
|
+
# # here.
|
73
|
+
#
|
74
|
+
# # this block never exits unless EM.stop_event_loop
|
75
|
+
# # is called.
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# Most code will use the MQ api. Any calls to MQ.direct / MQ.fanout /
|
79
|
+
# MQ.topic / MQ.queue will implicitly call #start. In those cases,
|
80
|
+
# it is sufficient to put your code inside of an EventMachine.run
|
81
|
+
# block. See the code examples in MQ for details.
|
82
|
+
#
|
83
|
+
def self.start *args, &blk
|
84
|
+
EM.run{
|
85
|
+
@conn ||= connect *args
|
86
|
+
@conn.callback(&blk) if blk
|
87
|
+
@conn
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
class << self
|
92
|
+
alias :run :start
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.stop
|
96
|
+
if @conn and not @closing
|
97
|
+
@closing = true
|
98
|
+
@conn.close{
|
99
|
+
yield if block_given?
|
100
|
+
@conn = nil
|
101
|
+
@closing = false
|
102
|
+
}
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.fork workers
|
107
|
+
EM.fork(workers) do
|
108
|
+
# clean up globals in the fork
|
109
|
+
Thread.current[:mq] = nil
|
110
|
+
AMQP.instance_variable_set('@conn', nil)
|
111
|
+
|
112
|
+
yield
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/amqp/buffer.rb
ADDED
@@ -0,0 +1,395 @@
|
|
1
|
+
if [].map.respond_to? :with_index
|
2
|
+
class Array #:nodoc:
|
3
|
+
def enum_with_index
|
4
|
+
each.with_index
|
5
|
+
end
|
6
|
+
end
|
7
|
+
else
|
8
|
+
require 'enumerator'
|
9
|
+
end
|
10
|
+
|
11
|
+
module AMQP
|
12
|
+
class Buffer #:nodoc: all
|
13
|
+
class Overflow < StandardError; end
|
14
|
+
class InvalidType < StandardError; end
|
15
|
+
|
16
|
+
def initialize data = ''
|
17
|
+
@data = data
|
18
|
+
@pos = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :pos
|
22
|
+
|
23
|
+
def data
|
24
|
+
@data.clone
|
25
|
+
end
|
26
|
+
alias :contents :data
|
27
|
+
alias :to_s :data
|
28
|
+
|
29
|
+
def << data
|
30
|
+
@data << data.to_s
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def length
|
35
|
+
@data.length
|
36
|
+
end
|
37
|
+
|
38
|
+
def empty?
|
39
|
+
pos == length
|
40
|
+
end
|
41
|
+
|
42
|
+
def rewind
|
43
|
+
@pos = 0
|
44
|
+
end
|
45
|
+
|
46
|
+
def read_properties *types
|
47
|
+
types.shift if types.first == :properties
|
48
|
+
|
49
|
+
i = 0
|
50
|
+
values = []
|
51
|
+
|
52
|
+
while props = read(:short)
|
53
|
+
(0..14).each do |n|
|
54
|
+
# no more property types
|
55
|
+
break unless types[i]
|
56
|
+
|
57
|
+
# if flag is set
|
58
|
+
if props & (1<<(15-n)) != 0
|
59
|
+
if types[i] == :bit
|
60
|
+
# bit values exist in flags only
|
61
|
+
values << true
|
62
|
+
else
|
63
|
+
# save type name for later reading
|
64
|
+
values << types[i]
|
65
|
+
end
|
66
|
+
else
|
67
|
+
# property not set or is false bit
|
68
|
+
values << (types[i] == :bit ? false : nil)
|
69
|
+
end
|
70
|
+
|
71
|
+
i+=1
|
72
|
+
end
|
73
|
+
|
74
|
+
# bit(0) == 0 means no more property flags
|
75
|
+
break unless props & 1 == 1
|
76
|
+
end
|
77
|
+
|
78
|
+
values.map do |value|
|
79
|
+
value.is_a?(Symbol) ? read(value) : value
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def read *types
|
84
|
+
if types.first == :properties
|
85
|
+
return read_properties(*types)
|
86
|
+
end
|
87
|
+
|
88
|
+
values = types.map do |type|
|
89
|
+
case type
|
90
|
+
when :octet
|
91
|
+
_read(1, 'C')
|
92
|
+
when :short
|
93
|
+
_read(2, 'n')
|
94
|
+
when :long
|
95
|
+
_read(4, 'N')
|
96
|
+
when :longlong
|
97
|
+
upper, lower = _read(8, 'NN')
|
98
|
+
upper << 32 | lower
|
99
|
+
when :shortstr
|
100
|
+
_read read(:octet)
|
101
|
+
when :longstr
|
102
|
+
_read read(:long)
|
103
|
+
when :timestamp
|
104
|
+
Time.at read(:longlong)
|
105
|
+
when :table
|
106
|
+
t = Hash.new
|
107
|
+
|
108
|
+
table = Buffer.new(read(:longstr))
|
109
|
+
until table.empty?
|
110
|
+
key, type = table.read(:shortstr, :octet)
|
111
|
+
key = key.intern
|
112
|
+
t[key] ||= case type
|
113
|
+
when 83 # 'S'
|
114
|
+
table.read(:longstr)
|
115
|
+
when 73 # 'I'
|
116
|
+
table.read(:long)
|
117
|
+
when 68 # 'D'
|
118
|
+
exp = table.read(:octet)
|
119
|
+
num = table.read(:long)
|
120
|
+
num / 10.0**exp
|
121
|
+
when 84 # 'T'
|
122
|
+
table.read(:timestamp)
|
123
|
+
when 70 # 'F'
|
124
|
+
table.read(:table)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
t
|
129
|
+
when :bit
|
130
|
+
if (@bits ||= []).empty?
|
131
|
+
val = read(:octet)
|
132
|
+
@bits = (0..7).map{|i| (val & 1<<i) != 0 }
|
133
|
+
end
|
134
|
+
|
135
|
+
@bits.shift
|
136
|
+
else
|
137
|
+
raise InvalidType, "Cannot read data of type #{type}"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
types.size == 1 ? values.first : values
|
142
|
+
end
|
143
|
+
|
144
|
+
def write type, data
|
145
|
+
case type
|
146
|
+
when :octet
|
147
|
+
_write(data, 'C')
|
148
|
+
when :short
|
149
|
+
_write(data, 'n')
|
150
|
+
when :long
|
151
|
+
_write(data, 'N')
|
152
|
+
when :longlong
|
153
|
+
lower = data & 0xffffffff
|
154
|
+
upper = (data & ~0xffffffff) >> 32
|
155
|
+
_write([upper, lower], 'NN')
|
156
|
+
when :shortstr
|
157
|
+
data = (data || '').to_s
|
158
|
+
_write([data.length, data], 'Ca*')
|
159
|
+
when :longstr
|
160
|
+
if data.is_a? Hash
|
161
|
+
write(:table, data)
|
162
|
+
else
|
163
|
+
data = (data || '').to_s
|
164
|
+
_write([data.length, data], 'Na*')
|
165
|
+
end
|
166
|
+
when :timestamp
|
167
|
+
write(:longlong, data.to_i)
|
168
|
+
when :table
|
169
|
+
data ||= {}
|
170
|
+
write :longstr, (data.inject(Buffer.new) do |table, (key, value)|
|
171
|
+
table.write(:shortstr, key.to_s)
|
172
|
+
|
173
|
+
case value
|
174
|
+
when String
|
175
|
+
table.write(:octet, 83) # 'S'
|
176
|
+
table.write(:longstr, value.to_s)
|
177
|
+
when Fixnum
|
178
|
+
table.write(:octet, 73) # 'I'
|
179
|
+
table.write(:long, value)
|
180
|
+
when Float
|
181
|
+
table.write(:octet, 68) # 'D'
|
182
|
+
# XXX there's gotta be a better way to do this..
|
183
|
+
exp = value.to_s.split('.').last.length
|
184
|
+
num = value * 10**exp
|
185
|
+
table.write(:octet, exp)
|
186
|
+
table.write(:long, num)
|
187
|
+
when Time
|
188
|
+
table.write(:octet, 84) # 'T'
|
189
|
+
table.write(:timestamp, value)
|
190
|
+
when Hash
|
191
|
+
table.write(:octet, 70) # 'F'
|
192
|
+
table.write(:table, value)
|
193
|
+
end
|
194
|
+
|
195
|
+
table
|
196
|
+
end)
|
197
|
+
when :bit
|
198
|
+
[*data].to_enum(:each_slice, 8).each{|bits|
|
199
|
+
write(:octet, bits.enum_with_index.inject(0){ |byte, (bit, i)|
|
200
|
+
byte |= 1<<i if bit
|
201
|
+
byte
|
202
|
+
})
|
203
|
+
}
|
204
|
+
when :properties
|
205
|
+
values = []
|
206
|
+
data.enum_with_index.inject(0) do |short, ((type, value), i)|
|
207
|
+
n = i % 15
|
208
|
+
last = i+1 == data.size
|
209
|
+
|
210
|
+
if (n == 0 and i != 0) or last
|
211
|
+
if data.size > i+1
|
212
|
+
short |= 1<<0
|
213
|
+
elsif last and value
|
214
|
+
values << [type,value]
|
215
|
+
short |= 1<<(15-n)
|
216
|
+
end
|
217
|
+
|
218
|
+
write(:short, short)
|
219
|
+
short = 0
|
220
|
+
end
|
221
|
+
|
222
|
+
if value and !last
|
223
|
+
values << [type,value]
|
224
|
+
short |= 1<<(15-n)
|
225
|
+
end
|
226
|
+
|
227
|
+
short
|
228
|
+
end
|
229
|
+
|
230
|
+
values.each do |type, value|
|
231
|
+
write(type, value) unless type == :bit
|
232
|
+
end
|
233
|
+
else
|
234
|
+
raise InvalidType, "Cannot write data of type #{type}"
|
235
|
+
end
|
236
|
+
|
237
|
+
self
|
238
|
+
end
|
239
|
+
|
240
|
+
def extract
|
241
|
+
begin
|
242
|
+
cur_data, cur_pos = @data.clone, @pos
|
243
|
+
yield self
|
244
|
+
rescue Overflow
|
245
|
+
@data, @pos = cur_data, cur_pos
|
246
|
+
nil
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def _read size, pack = nil
|
251
|
+
if @pos + size > length
|
252
|
+
raise Overflow
|
253
|
+
else
|
254
|
+
data = @data[@pos,size]
|
255
|
+
@data[@pos,size] = ''
|
256
|
+
if pack
|
257
|
+
data = data.unpack(pack)
|
258
|
+
data = data.pop if data.size == 1
|
259
|
+
end
|
260
|
+
data
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def _write data, pack = nil
|
265
|
+
data = [*data].pack(pack) if pack
|
266
|
+
@data[@pos,0] = data
|
267
|
+
@pos += data.length
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
if $0 =~ /bacon/ or $0 == __FILE__
|
273
|
+
require 'bacon'
|
274
|
+
include AMQP
|
275
|
+
|
276
|
+
describe Buffer do
|
277
|
+
before do
|
278
|
+
@buf = Buffer.new
|
279
|
+
end
|
280
|
+
|
281
|
+
should 'have contents' do
|
282
|
+
@buf.contents.should == ''
|
283
|
+
end
|
284
|
+
|
285
|
+
should 'initialize with data' do
|
286
|
+
@buf = Buffer.new('abc')
|
287
|
+
@buf.contents.should == 'abc'
|
288
|
+
end
|
289
|
+
|
290
|
+
should 'append raw data' do
|
291
|
+
@buf << 'abc'
|
292
|
+
@buf << 'def'
|
293
|
+
@buf.contents.should == 'abcdef'
|
294
|
+
end
|
295
|
+
|
296
|
+
should 'append other buffers' do
|
297
|
+
@buf << Buffer.new('abc')
|
298
|
+
@buf.data.should == 'abc'
|
299
|
+
end
|
300
|
+
|
301
|
+
should 'have a position' do
|
302
|
+
@buf.pos.should == 0
|
303
|
+
end
|
304
|
+
|
305
|
+
should 'have a length' do
|
306
|
+
@buf.length.should == 0
|
307
|
+
@buf << 'abc'
|
308
|
+
@buf.length.should == 3
|
309
|
+
end
|
310
|
+
|
311
|
+
should 'know the end' do
|
312
|
+
@buf.empty?.should == true
|
313
|
+
end
|
314
|
+
|
315
|
+
should 'read and write data' do
|
316
|
+
@buf._write('abc')
|
317
|
+
@buf.rewind
|
318
|
+
@buf._read(2).should == 'ab'
|
319
|
+
@buf._read(1).should == 'c'
|
320
|
+
end
|
321
|
+
|
322
|
+
should 'raise on overflow' do
|
323
|
+
lambda{ @buf._read(1) }.should.raise Buffer::Overflow
|
324
|
+
end
|
325
|
+
|
326
|
+
should 'raise on invalid types' do
|
327
|
+
lambda{ @buf.read(:junk) }.should.raise Buffer::InvalidType
|
328
|
+
lambda{ @buf.write(:junk, 1) }.should.raise Buffer::InvalidType
|
329
|
+
end
|
330
|
+
|
331
|
+
{ :octet => 0b10101010,
|
332
|
+
:short => 100,
|
333
|
+
:long => 100_000_000,
|
334
|
+
:longlong => 666_555_444_333_222_111,
|
335
|
+
:shortstr => 'hello',
|
336
|
+
:longstr => 'bye'*500,
|
337
|
+
:timestamp => time = Time.at(Time.now.to_i),
|
338
|
+
:table => { :this => 'is', :a => 'hash', :with => {:nested => 123, :and => time, :also => 123.456} },
|
339
|
+
:bit => true
|
340
|
+
}.each do |type, value|
|
341
|
+
|
342
|
+
should "read and write a #{type}" do
|
343
|
+
@buf.write(type, value)
|
344
|
+
@buf.rewind
|
345
|
+
@buf.read(type).should == value
|
346
|
+
@buf.should.be.empty
|
347
|
+
end
|
348
|
+
|
349
|
+
end
|
350
|
+
|
351
|
+
should 'read and write multiple bits' do
|
352
|
+
bits = [true, false, false, true, true, false, false, true, true, false]
|
353
|
+
@buf.write(:bit, bits)
|
354
|
+
@buf.write(:octet, 100)
|
355
|
+
|
356
|
+
@buf.rewind
|
357
|
+
|
358
|
+
bits.map do
|
359
|
+
@buf.read(:bit)
|
360
|
+
end.should == bits
|
361
|
+
@buf.read(:octet).should == 100
|
362
|
+
end
|
363
|
+
|
364
|
+
should 'read and write properties' do
|
365
|
+
properties = ([
|
366
|
+
[:octet, 1],
|
367
|
+
[:shortstr, 'abc'],
|
368
|
+
[:bit, true],
|
369
|
+
[:bit, false],
|
370
|
+
[:shortstr, nil],
|
371
|
+
[:timestamp, nil],
|
372
|
+
[:table, { :a => 'hash' }],
|
373
|
+
]*5).sort_by{rand}
|
374
|
+
|
375
|
+
@buf.write(:properties, properties)
|
376
|
+
@buf.rewind
|
377
|
+
@buf.read(:properties, *properties.map{|type,_| type }).should == properties.map{|_,value| value }
|
378
|
+
@buf.should.be.empty
|
379
|
+
end
|
380
|
+
|
381
|
+
should 'do transactional reads with #extract' do
|
382
|
+
@buf.write :octet, 8
|
383
|
+
orig = @buf.to_s
|
384
|
+
|
385
|
+
@buf.rewind
|
386
|
+
@buf.extract do |b|
|
387
|
+
b.read :octet
|
388
|
+
b.read :short
|
389
|
+
end
|
390
|
+
|
391
|
+
@buf.pos.should == 0
|
392
|
+
@buf.data.should == orig
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|