tmm1-amqp 0.5.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 +62 -0
- data/examples/clock.rb +56 -0
- data/examples/hashtable.rb +52 -0
- data/examples/pingpong.rb +45 -0
- data/examples/primes-forked.rb +63 -0
- data/examples/primes-processes.rb +135 -0
- data/examples/primes-simple.rb +19 -0
- data/examples/primes-threaded.rb +49 -0
- data/examples/primes.rb +99 -0
- data/examples/simple.rb +77 -0
- data/examples/stocks.rb +56 -0
- data/lib/amqp.rb +14 -0
- data/lib/amqp/buffer.rb +395 -0
- data/lib/amqp/client.rb +135 -0
- data/lib/amqp/frame.rb +124 -0
- data/lib/amqp/protocol.rb +181 -0
- data/lib/amqp/spec.rb +818 -0
- data/lib/emfork.rb +67 -0
- data/lib/mq.rb +133 -0
- data/lib/mq/exchange.rb +36 -0
- data/lib/mq/queue.rb +52 -0
- data/lib/mq/rpc.rb +51 -0
- data/protocol/amqp-0.8.json +606 -0
- data/protocol/amqp-0.8.xml +3908 -0
- data/protocol/codegen.rb +172 -0
- data/protocol/doc.txt +281 -0
- metadata +86 -0
data/examples/primes.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__) + '/../lib'
|
2
|
+
require 'mq'
|
3
|
+
|
4
|
+
# check MAX numbers for prime-ness
|
5
|
+
MAX = 1000
|
6
|
+
|
7
|
+
# logging
|
8
|
+
def log *args
|
9
|
+
p args
|
10
|
+
end
|
11
|
+
|
12
|
+
# spawn workers
|
13
|
+
workers = ARGV[0] ? (Integer(ARGV[0]) rescue 1) : 1
|
14
|
+
EM.fork(workers) do
|
15
|
+
|
16
|
+
log MQ.id, :started
|
17
|
+
|
18
|
+
class Fixnum
|
19
|
+
def prime?
|
20
|
+
('1' * self) !~ /^1?$|^(11+?)\1+$/
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class PrimeChecker
|
25
|
+
def is_prime? number
|
26
|
+
log "prime checker #{MQ.id}", :prime?, number
|
27
|
+
number.prime?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
MQ.rpc('prime checker', PrimeChecker.new)
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
# use workers to check which numbers are prime
|
36
|
+
EM.run{
|
37
|
+
|
38
|
+
prime_checker = MQ.rpc('prime checker')
|
39
|
+
|
40
|
+
(10_000...(10_000+MAX)).each do |num|
|
41
|
+
log :checking, num
|
42
|
+
|
43
|
+
prime_checker.is_prime?(num) { |is_prime|
|
44
|
+
log :prime?, num, is_prime
|
45
|
+
(@primes||=[]) << num if is_prime
|
46
|
+
|
47
|
+
if (@responses = (@responses || 0) + 1) == MAX
|
48
|
+
log :primes=, @primes
|
49
|
+
EM.stop_event_loop
|
50
|
+
end
|
51
|
+
}
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
}
|
56
|
+
|
57
|
+
__END__
|
58
|
+
|
59
|
+
$ uname -a
|
60
|
+
Linux gc 2.6.24-ARCH #1 SMP PREEMPT Sun Mar 30 10:50:22 CEST 2008 x86_64 Intel(R) Xeon(R) CPU X3220 @ 2.40GHz GenuineIntel GNU/Linux
|
61
|
+
|
62
|
+
$ cat /proc/cpuinfo | grep processor | wc -l
|
63
|
+
4
|
64
|
+
|
65
|
+
$ time ruby primes-simple.rb
|
66
|
+
|
67
|
+
real 0m16.055s
|
68
|
+
user 0m16.052s
|
69
|
+
sys 0m0.000s
|
70
|
+
|
71
|
+
$ time ruby primes.rb 1 >/dev/null
|
72
|
+
|
73
|
+
real 0m18.278s
|
74
|
+
user 0m0.993s
|
75
|
+
sys 0m0.027s
|
76
|
+
|
77
|
+
$ time ruby primes.rb 2 >/dev/null
|
78
|
+
|
79
|
+
real 0m17.316s
|
80
|
+
user 0m0.967s
|
81
|
+
sys 0m0.053s
|
82
|
+
|
83
|
+
$ time ruby primes.rb 4 >/dev/null
|
84
|
+
|
85
|
+
real 0m8.229s
|
86
|
+
user 0m1.010s
|
87
|
+
sys 0m0.030s
|
88
|
+
|
89
|
+
$ time ruby primes.rb 8 >/dev/null
|
90
|
+
|
91
|
+
real 0m5.893s
|
92
|
+
user 0m1.023s
|
93
|
+
sys 0m0.050s
|
94
|
+
|
95
|
+
$ time ruby primes.rb 16 >/dev/null
|
96
|
+
|
97
|
+
real 0m5.601s
|
98
|
+
user 0m0.990s
|
99
|
+
sys 0m0.043s
|
data/examples/simple.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__) + '/../lib'
|
2
|
+
require 'amqp'
|
3
|
+
|
4
|
+
module SimpleClient
|
5
|
+
def process_frame frame
|
6
|
+
case frame
|
7
|
+
when Frame::Body
|
8
|
+
EM.stop_event_loop
|
9
|
+
|
10
|
+
when Frame::Method
|
11
|
+
case method = frame.payload
|
12
|
+
when Protocol::Connection::Start
|
13
|
+
send Protocol::Connection::StartOk.new({:platform => 'Ruby/EventMachine',
|
14
|
+
:product => 'AMQP',
|
15
|
+
:information => 'http://github.com/tmm1/amqp',
|
16
|
+
:version => '0.1.0'},
|
17
|
+
'AMQPLAIN',
|
18
|
+
{:LOGIN => 'guest',
|
19
|
+
:PASSWORD => 'guest'},
|
20
|
+
'en_US')
|
21
|
+
|
22
|
+
when Protocol::Connection::Tune
|
23
|
+
send Protocol::Connection::TuneOk.new(:channel_max => 0,
|
24
|
+
:frame_max => 131072,
|
25
|
+
:heartbeat => 0)
|
26
|
+
|
27
|
+
send Protocol::Connection::Open.new(:virtual_host => '/',
|
28
|
+
:capabilities => '',
|
29
|
+
:insist => false)
|
30
|
+
|
31
|
+
when Protocol::Connection::OpenOk
|
32
|
+
send Protocol::Channel::Open.new, :channel => 1
|
33
|
+
|
34
|
+
when Protocol::Channel::OpenOk
|
35
|
+
send Protocol::Access::Request.new(:realm => '/data',
|
36
|
+
:read => true,
|
37
|
+
:write => true,
|
38
|
+
:active => true), :channel => 1
|
39
|
+
|
40
|
+
when Protocol::Access::RequestOk
|
41
|
+
@ticket = method.ticket
|
42
|
+
send Protocol::Queue::Declare.new(:ticket => @ticket,
|
43
|
+
:queue => '',
|
44
|
+
:exclusive => false,
|
45
|
+
:auto_delete => true), :channel => 1
|
46
|
+
|
47
|
+
when Protocol::Queue::DeclareOk
|
48
|
+
@queue = method.queue
|
49
|
+
send Protocol::Queue::Bind.new(:ticket => @ticket,
|
50
|
+
:queue => @queue,
|
51
|
+
:exchange => '',
|
52
|
+
:routing_key => 'test_route'), :channel => 1
|
53
|
+
|
54
|
+
when Protocol::Queue::BindOk
|
55
|
+
send Protocol::Basic::Consume.new(:ticket => @ticket,
|
56
|
+
:queue => @queue,
|
57
|
+
:no_local => false,
|
58
|
+
:no_ack => true), :channel => 1
|
59
|
+
|
60
|
+
when Protocol::Basic::ConsumeOk
|
61
|
+
data = "this is a test!"
|
62
|
+
|
63
|
+
send Protocol::Basic::Publish.new(:ticket => @ticket,
|
64
|
+
:exchange => '',
|
65
|
+
:routing_key => 'test_route'), :channel => 1
|
66
|
+
send Protocol::Header.new(Protocol::Basic, data.length, :content_type => 'application/octet-stream',
|
67
|
+
:delivery_mode => 1,
|
68
|
+
:priority => 0), :channel => 1
|
69
|
+
send Frame::Body.new(data), :channel => 1
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
AMQP.logging = true
|
76
|
+
AMQP.client = SimpleClient
|
77
|
+
AMQP.start
|
data/examples/stocks.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__) + '/../lib'
|
2
|
+
require 'mq'
|
3
|
+
|
4
|
+
EM.run{
|
5
|
+
|
6
|
+
def log *args
|
7
|
+
p [ Time.now, *args ]
|
8
|
+
end
|
9
|
+
|
10
|
+
# AMQP.logging = true
|
11
|
+
|
12
|
+
EM.add_periodic_timer(1){
|
13
|
+
puts
|
14
|
+
|
15
|
+
log :publishing, 'stock.usd.appl', price = 170+rand(1000)/100.0
|
16
|
+
MQ.topic.publish(price, :key => 'stock.usd.appl', :headers => {:symbol => 'appl'})
|
17
|
+
|
18
|
+
log :publishing, 'stock.usd.msft', price = 22+rand(500)/100.0
|
19
|
+
MQ.topic.publish(price, :key => 'stock.usd.msft', :headers => {:symbol => 'msft'})
|
20
|
+
}
|
21
|
+
|
22
|
+
Thread.new{
|
23
|
+
amq = MQ.new
|
24
|
+
amq.queue('apple stock').bind(amq.topic, :key => 'stock.usd.appl').subscribe{ |price|
|
25
|
+
log 'apple stock', price
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
Thread.new{
|
30
|
+
amq = MQ.new
|
31
|
+
amq.queue('us stocks').bind(amq.topic, :key => 'stock.usd.*').subscribe{ |info, price|
|
32
|
+
log 'us stock', info.headers[:symbol], price
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
}
|
37
|
+
|
38
|
+
__END__
|
39
|
+
|
40
|
+
[Thu Jul 17 14:51:07 -0700 2008, :publishing, "stock.usd.appl", 170.84]
|
41
|
+
[Thu Jul 17 14:51:07 -0700 2008, :publishing, "stock.usd.msft", 23.68]
|
42
|
+
[Thu Jul 17 14:51:07 -0700 2008, "apple stock", "170.84"]
|
43
|
+
[Thu Jul 17 14:51:07 -0700 2008, "us stock", "appl", "170.84"]
|
44
|
+
[Thu Jul 17 14:51:07 -0700 2008, "us stock", "msft", "23.68"]
|
45
|
+
|
46
|
+
[Thu Jul 17 14:51:08 -0700 2008, :publishing, "stock.usd.appl", 173.61]
|
47
|
+
[Thu Jul 17 14:51:08 -0700 2008, :publishing, "stock.usd.msft", 25.8]
|
48
|
+
[Thu Jul 17 14:51:08 -0700 2008, "apple stock", "173.61"]
|
49
|
+
[Thu Jul 17 14:51:08 -0700 2008, "us stock", "appl", "173.61"]
|
50
|
+
[Thu Jul 17 14:51:08 -0700 2008, "us stock", "msft", "25.8"]
|
51
|
+
|
52
|
+
[Thu Jul 17 14:51:09 -0700 2008, :publishing, "stock.usd.appl", 173.94]
|
53
|
+
[Thu Jul 17 14:51:09 -0700 2008, :publishing, "stock.usd.msft", 24.88]
|
54
|
+
[Thu Jul 17 14:51:09 -0700 2008, "apple stock", "173.94"]
|
55
|
+
[Thu Jul 17 14:51:09 -0700 2008, "us stock", "appl", "173.94"]
|
56
|
+
[Thu Jul 17 14:51:09 -0700 2008, "us stock", "msft", "24.88"]
|
data/lib/amqp.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
module AMQP
|
2
|
+
DIR = File.expand_path(File.dirname(File.expand_path(__FILE__)))
|
3
|
+
|
4
|
+
$:.unshift DIR
|
5
|
+
|
6
|
+
%w[ buffer spec protocol frame client ].each do |file|
|
7
|
+
require "amqp/#{file}"
|
8
|
+
end
|
9
|
+
|
10
|
+
class << self
|
11
|
+
@logging = false
|
12
|
+
attr_accessor :logging
|
13
|
+
end
|
14
|
+
end
|
data/lib/amqp/buffer.rb
ADDED
@@ -0,0 +1,395 @@
|
|
1
|
+
if [].map.respond_to? :with_index
|
2
|
+
class Array
|
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
|
13
|
+
class Overflow < Exception; end
|
14
|
+
class InvalidType < Exception; 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
|