amq-protocol 2.3.4 → 2.5.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.
- checksums.yaml +4 -4
- data/AGENTS.md +23 -0
- data/CLAUDE.md +1 -0
- data/ChangeLog.md +30 -3
- data/GEMINI.md +1 -0
- data/Gemfile +5 -0
- data/README.md +2 -7
- data/benchmarks/frame_encoding.rb +75 -0
- data/benchmarks/method_encoding.rb +198 -0
- data/benchmarks/pack_unpack.rb +158 -0
- data/benchmarks/run_all.rb +64 -0
- data/benchmarks/table_encoding.rb +110 -0
- data/lib/amq/bit_set.rb +1 -0
- data/lib/amq/endianness.rb +2 -0
- data/lib/amq/int_allocator.rb +1 -0
- data/lib/amq/pack.rb +33 -42
- data/lib/amq/protocol/client.rb +30 -37
- data/lib/amq/protocol/constants.rb +2 -0
- data/lib/amq/protocol/exceptions.rb +3 -1
- data/lib/amq/protocol/float_32bit.rb +2 -0
- data/lib/amq/protocol/frame.rb +9 -3
- data/lib/amq/protocol/table.rb +20 -17
- data/lib/amq/protocol/table_value_decoder.rb +48 -52
- data/lib/amq/protocol/table_value_encoder.rb +1 -0
- data/lib/amq/protocol/type_constants.rb +1 -0
- data/lib/amq/protocol/version.rb +1 -1
- data/lib/amq/settings.rb +1 -0
- data/spec/amq/bit_set_spec.rb +22 -0
- data/spec/amq/endianness_spec.rb +23 -0
- data/spec/amq/int_allocator_spec.rb +26 -3
- data/spec/amq/pack_spec.rb +14 -24
- data/spec/amq/protocol/exceptions_spec.rb +70 -0
- data/spec/amq/protocol/float_32bit_spec.rb +27 -0
- data/spec/amq/protocol/frame_spec.rb +64 -0
- data/spec/amq/protocol/table_spec.rb +32 -0
- data/spec/amq/protocol/value_decoder_spec.rb +97 -0
- data/spec/amq/protocol/value_encoder_spec.rb +21 -0
- data/spec/amq/settings_spec.rb +37 -1
- data/spec/amq/uri_parsing_spec.rb +7 -0
- metadata +14 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# encoding: binary
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
|
-
require "amq/endianness"
|
|
4
4
|
require "amq/protocol/type_constants"
|
|
5
5
|
require "amq/protocol/float_32bit"
|
|
6
6
|
|
|
@@ -15,15 +15,23 @@ module AMQ
|
|
|
15
15
|
|
|
16
16
|
include TypeConstants
|
|
17
17
|
|
|
18
|
+
# Pack format strings use explicit endianness (available as of Ruby 1.9.3)
|
|
19
|
+
PACK_UINT32_BE = 'N'.freeze
|
|
20
|
+
PACK_INT64_BE = 'q>'.freeze
|
|
21
|
+
PACK_INT16_BE = 's>'.freeze
|
|
22
|
+
PACK_UINT64_BE = 'Q>'.freeze
|
|
23
|
+
PACK_FLOAT32 = 'f'.freeze # single precision float (native endian, matches encoder)
|
|
24
|
+
PACK_FLOAT64 = 'G'.freeze # big-endian double precision float
|
|
25
|
+
PACK_UCHAR_UINT32 = 'CN'.freeze
|
|
18
26
|
|
|
19
27
|
#
|
|
20
28
|
# API
|
|
21
29
|
#
|
|
22
30
|
|
|
23
31
|
def self.decode_array(data, initial_offset)
|
|
24
|
-
array_length = data.
|
|
32
|
+
array_length = data.byteslice(initial_offset, 4).unpack1(PACK_UINT32_BE)
|
|
25
33
|
|
|
26
|
-
ary =
|
|
34
|
+
ary = []
|
|
27
35
|
offset = initial_offset + 4
|
|
28
36
|
|
|
29
37
|
while offset <= (initial_offset + array_length)
|
|
@@ -54,25 +62,25 @@ module AMQ
|
|
|
54
62
|
when TYPE_BOOLEAN
|
|
55
63
|
v, offset = decode_boolean(data, offset)
|
|
56
64
|
v
|
|
57
|
-
when TYPE_BYTE
|
|
65
|
+
when TYPE_BYTE
|
|
58
66
|
v, offset = decode_byte(data, offset)
|
|
59
67
|
v
|
|
60
|
-
when TYPE_SIGNED_16BIT
|
|
68
|
+
when TYPE_SIGNED_16BIT
|
|
61
69
|
v, offset = decode_short(data, offset)
|
|
62
70
|
v
|
|
63
|
-
when TYPE_SIGNED_64BIT
|
|
71
|
+
when TYPE_SIGNED_64BIT
|
|
64
72
|
v, offset = decode_long(data, offset)
|
|
65
73
|
v
|
|
66
|
-
when TYPE_32BIT_FLOAT
|
|
74
|
+
when TYPE_32BIT_FLOAT
|
|
67
75
|
v, offset = decode_32bit_float(data, offset)
|
|
68
76
|
v
|
|
69
|
-
when TYPE_64BIT_FLOAT
|
|
77
|
+
when TYPE_64BIT_FLOAT
|
|
70
78
|
v, offset = decode_64bit_float(data, offset)
|
|
71
79
|
v
|
|
72
80
|
when TYPE_VOID
|
|
73
81
|
nil
|
|
74
82
|
when TYPE_ARRAY
|
|
75
|
-
v, offset =
|
|
83
|
+
v, offset = decode_array(data, offset)
|
|
76
84
|
v
|
|
77
85
|
else
|
|
78
86
|
raise ArgumentError.new("unsupported type in a table value: #{type.inspect}, do not know how to decode!")
|
|
@@ -83,113 +91,101 @@ module AMQ
|
|
|
83
91
|
|
|
84
92
|
|
|
85
93
|
[ary, initial_offset + array_length + 4]
|
|
86
|
-
end
|
|
94
|
+
end
|
|
87
95
|
|
|
88
96
|
|
|
89
97
|
def self.decode_string(data, offset)
|
|
90
|
-
length = data.
|
|
98
|
+
length = data.byteslice(offset, 4).unpack1(PACK_UINT32_BE)
|
|
91
99
|
offset += 4
|
|
92
|
-
v = data.
|
|
100
|
+
v = data.byteslice(offset, length)
|
|
93
101
|
offset += length
|
|
94
102
|
|
|
95
103
|
[v, offset]
|
|
96
|
-
end
|
|
104
|
+
end
|
|
97
105
|
|
|
98
106
|
|
|
99
107
|
def self.decode_integer(data, offset)
|
|
100
|
-
v = data.
|
|
108
|
+
v = data.byteslice(offset, 4).unpack1(PACK_UINT32_BE)
|
|
101
109
|
offset += 4
|
|
102
110
|
|
|
103
111
|
[v, offset]
|
|
104
|
-
end
|
|
105
|
-
|
|
112
|
+
end
|
|
106
113
|
|
|
107
|
-
if AMQ::Endianness.big_endian?
|
|
108
|
-
def self.decode_long(data, offset)
|
|
109
|
-
v = data.slice(offset, 8).unpack(PACK_INT64)
|
|
110
114
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def self.decode_long(data, offset)
|
|
116
|
-
slice = data.slice(offset, 8).bytes.to_a.reverse.map(&:chr).join
|
|
117
|
-
v = slice.unpack(PACK_INT64).first
|
|
118
|
-
|
|
119
|
-
offset += 8
|
|
120
|
-
[v, offset]
|
|
121
|
-
end
|
|
115
|
+
def self.decode_long(data, offset)
|
|
116
|
+
v = data.byteslice(offset, 8).unpack1(PACK_INT64_BE)
|
|
117
|
+
offset += 8
|
|
118
|
+
[v, offset]
|
|
122
119
|
end
|
|
123
120
|
|
|
124
121
|
|
|
125
122
|
def self.decode_big_decimal(data, offset)
|
|
126
|
-
decimals, raw = data.
|
|
123
|
+
decimals, raw = data.byteslice(offset, 5).unpack(PACK_UCHAR_UINT32)
|
|
127
124
|
offset += 5
|
|
128
125
|
v = BigDecimal(raw.to_s) * (BigDecimal(TEN) ** -decimals)
|
|
129
126
|
|
|
130
127
|
[v, offset]
|
|
131
|
-
end
|
|
128
|
+
end
|
|
132
129
|
|
|
133
130
|
|
|
134
131
|
def self.decode_time(data, offset)
|
|
135
|
-
timestamp = data.
|
|
132
|
+
timestamp = data.byteslice(offset, 8).unpack1(PACK_UINT64_BE)
|
|
136
133
|
v = Time.at(timestamp)
|
|
137
134
|
offset += 8
|
|
138
135
|
|
|
139
136
|
[v, offset]
|
|
140
|
-
end
|
|
137
|
+
end
|
|
141
138
|
|
|
142
139
|
|
|
143
140
|
def self.decode_boolean(data, offset)
|
|
144
|
-
|
|
141
|
+
byte = data.getbyte(offset)
|
|
145
142
|
offset += 1
|
|
146
|
-
[(
|
|
147
|
-
end
|
|
143
|
+
[(byte == 1), offset]
|
|
144
|
+
end
|
|
148
145
|
|
|
149
146
|
|
|
150
147
|
def self.decode_32bit_float(data, offset)
|
|
151
|
-
v = data.
|
|
148
|
+
v = data.byteslice(offset, 4).unpack1(PACK_FLOAT32)
|
|
152
149
|
offset += 4
|
|
153
150
|
|
|
154
151
|
[v, offset]
|
|
155
|
-
end
|
|
152
|
+
end
|
|
156
153
|
|
|
157
154
|
|
|
158
155
|
def self.decode_64bit_float(data, offset)
|
|
159
|
-
v = data.
|
|
156
|
+
v = data.byteslice(offset, 8).unpack1(PACK_FLOAT64)
|
|
160
157
|
offset += 8
|
|
161
158
|
|
|
162
159
|
[v, offset]
|
|
163
|
-
end
|
|
160
|
+
end
|
|
164
161
|
|
|
165
162
|
|
|
166
163
|
def self.decode_value_type(data, offset)
|
|
167
|
-
[data.
|
|
168
|
-
end
|
|
169
|
-
|
|
164
|
+
[data.byteslice(offset, 1), offset + 1]
|
|
165
|
+
end
|
|
170
166
|
|
|
171
167
|
|
|
172
168
|
def self.decode_hash(data, offset)
|
|
173
|
-
length = data.
|
|
174
|
-
v = Table.decode(data.
|
|
169
|
+
length = data.byteslice(offset, 4).unpack1(PACK_UINT32_BE)
|
|
170
|
+
v = Table.decode(data.byteslice(offset, length + 4))
|
|
175
171
|
offset += 4 + length
|
|
176
172
|
|
|
177
173
|
[v, offset]
|
|
178
|
-
end
|
|
174
|
+
end
|
|
179
175
|
|
|
180
176
|
|
|
181
177
|
# Decodes/Converts a byte value from the data at the provided offset.
|
|
182
178
|
#
|
|
183
|
-
# @param [
|
|
184
|
-
# @param [
|
|
185
|
-
# @return [Array] - The
|
|
179
|
+
# @param [String] data - Binary data string
|
|
180
|
+
# @param [Integer] offset - The offset from which to read the byte
|
|
181
|
+
# @return [Array] - The Integer value and new offset pair
|
|
186
182
|
def self.decode_byte(data, offset)
|
|
187
|
-
[data.
|
|
183
|
+
[data.getbyte(offset), offset + 1]
|
|
188
184
|
end
|
|
189
185
|
|
|
190
186
|
|
|
191
187
|
def self.decode_short(data, offset)
|
|
192
|
-
v =
|
|
188
|
+
v = data.byteslice(offset, 2).unpack1(PACK_INT16_BE)
|
|
193
189
|
offset += 2
|
|
194
190
|
[v, offset]
|
|
195
191
|
end
|
data/lib/amq/protocol/version.rb
CHANGED
data/lib/amq/settings.rb
CHANGED
data/spec/amq/bit_set_spec.rb
CHANGED
|
@@ -223,5 +223,27 @@ RSpec.describe AMQ::BitSet do
|
|
|
223
223
|
subject.unset(254)
|
|
224
224
|
expect(subject.get(254)).to be_falsey
|
|
225
225
|
end # it
|
|
226
|
+
|
|
227
|
+
it "returns -1 when all bits are set" do
|
|
228
|
+
bs = described_class.new(64)
|
|
229
|
+
0.upto(63) { |i| bs.set(i) }
|
|
230
|
+
expect(bs.next_clear_bit).to eq(-1)
|
|
231
|
+
end
|
|
232
|
+
end # describe
|
|
233
|
+
|
|
234
|
+
describe "#to_s" do
|
|
235
|
+
it "returns a string representation of the bit set" do
|
|
236
|
+
bs = described_class.new(64)
|
|
237
|
+
result = bs.to_s
|
|
238
|
+
expect(result).to be_a(String)
|
|
239
|
+
expect(result).to include(":")
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
it "shows set bits" do
|
|
243
|
+
bs = described_class.new(64)
|
|
244
|
+
bs.set(0)
|
|
245
|
+
result = bs.to_s
|
|
246
|
+
expect(result).to end_with("1:")
|
|
247
|
+
end
|
|
226
248
|
end # describe
|
|
227
249
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# encoding: binary
|
|
2
|
+
|
|
3
|
+
require "amq/endianness"
|
|
4
|
+
|
|
5
|
+
RSpec.describe AMQ::Endianness do
|
|
6
|
+
describe ".big_endian?" do
|
|
7
|
+
it "returns a boolean" do
|
|
8
|
+
expect([true, false]).to include(described_class.big_endian?)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe ".little_endian?" do
|
|
13
|
+
it "returns the opposite of big_endian?" do
|
|
14
|
+
expect(described_class.little_endian?).to eq(!described_class.big_endian?)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe "BIG_ENDIAN constant" do
|
|
19
|
+
it "is a boolean" do
|
|
20
|
+
expect([true, false]).to include(AMQ::Endianness::BIG_ENDIAN)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -11,13 +11,17 @@ RSpec.describe AMQ::IntAllocator do
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
# ...
|
|
15
|
-
|
|
16
|
-
|
|
17
14
|
#
|
|
18
15
|
# Examples
|
|
19
16
|
#
|
|
20
17
|
|
|
18
|
+
describe "#initialize" do
|
|
19
|
+
it "raises ArgumentError when hi <= lo" do
|
|
20
|
+
expect { described_class.new(5, 5) }.to raise_error(ArgumentError)
|
|
21
|
+
expect { described_class.new(10, 5) }.to raise_error(ArgumentError)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
21
25
|
describe "#number_of_bits" do
|
|
22
26
|
it "returns number of bits available for allocation" do
|
|
23
27
|
expect(subject.number_of_bits).to eq(4)
|
|
@@ -110,4 +114,23 @@ RSpec.describe AMQ::IntAllocator do
|
|
|
110
114
|
end
|
|
111
115
|
end
|
|
112
116
|
end
|
|
117
|
+
|
|
118
|
+
describe "#release" do
|
|
119
|
+
it "is an alias for #free" do
|
|
120
|
+
subject.allocate
|
|
121
|
+
expect(subject.allocated?(1)).to be_truthy
|
|
122
|
+
subject.release(1)
|
|
123
|
+
expect(subject.allocated?(1)).to be_falsey
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
describe "#reset" do
|
|
128
|
+
it "releases all allocations" do
|
|
129
|
+
4.times { subject.allocate }
|
|
130
|
+
expect(subject.allocate).to eq(-1)
|
|
131
|
+
|
|
132
|
+
subject.reset
|
|
133
|
+
expect(subject.allocate).to eq(1)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
113
136
|
end
|
data/spec/amq/pack_spec.rb
CHANGED
|
@@ -4,10 +4,18 @@ RSpec.describe AMQ::Pack do
|
|
|
4
4
|
context "16-bit big-endian packing / unpacking" do
|
|
5
5
|
let(:examples_16bit) {
|
|
6
6
|
{
|
|
7
|
-
|
|
7
|
+
0x068D => "\x06\x8D", # 1677
|
|
8
|
+
0x0000 => "\x00\x00", # 0
|
|
9
|
+
0x7FFF => "\x7F\xFF" # 32767 (max positive signed 16-bit)
|
|
8
10
|
}
|
|
9
11
|
}
|
|
10
12
|
|
|
13
|
+
it "packs signed integers into a big-endian string" do
|
|
14
|
+
examples_16bit.each do |key, value|
|
|
15
|
+
expect(described_class.pack_int16_big_endian(key)).to eq(value)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
11
19
|
it "unpacks signed integers from a string to a number" do
|
|
12
20
|
examples_16bit.each do |key, value|
|
|
13
21
|
expect(described_class.unpack_int16_big_endian(value)[0]).to eq(key)
|
|
@@ -35,34 +43,16 @@ RSpec.describe AMQ::Pack do
|
|
|
35
43
|
end
|
|
36
44
|
end
|
|
37
45
|
|
|
38
|
-
it "
|
|
46
|
+
it "unpacks string representation into integer" do
|
|
39
47
|
examples.each do |key, value|
|
|
40
48
|
expect(described_class.unpack_uint64_big_endian(value)[0]).to eq(key)
|
|
41
49
|
end
|
|
42
50
|
end
|
|
51
|
+
end
|
|
43
52
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
$KCODE = 'u'
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
after do
|
|
51
|
-
$KCODE = 'NONE'
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
it "packs integers into big-endian string" do
|
|
55
|
-
examples.each do |key, value|
|
|
56
|
-
expect(described_class.pack_uint64_big_endian(key)).to eq(value)
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
it "should unpack string representation into integer" do
|
|
61
|
-
examples.each do |key, value|
|
|
62
|
-
expect(described_class.unpack_uint64_big_endian(value)[0]).to eq(key)
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|
|
53
|
+
describe "AMQ::Hacks alias" do
|
|
54
|
+
it "is an alias for AMQ::Pack (backwards compatibility)" do
|
|
55
|
+
expect(AMQ::Hacks).to eq(AMQ::Pack)
|
|
66
56
|
end
|
|
67
57
|
end
|
|
68
58
|
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# encoding: binary
|
|
2
|
+
|
|
3
|
+
RSpec.describe AMQ::Protocol::Error do
|
|
4
|
+
describe ".[]" do
|
|
5
|
+
it "looks up exception class by error code" do
|
|
6
|
+
# This only works if subclasses define VALUE constant
|
|
7
|
+
# Default case: no subclass with VALUE defined returns nil
|
|
8
|
+
expect { described_class[999999] }.to raise_error(/No such exception class/)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe ".subclasses_with_values" do
|
|
13
|
+
it "returns subclasses that define VALUE constant" do
|
|
14
|
+
result = described_class.subclasses_with_values
|
|
15
|
+
expect(result).to be_an(Array)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe "#initialize" do
|
|
20
|
+
it "uses default message when none provided" do
|
|
21
|
+
error = described_class.new
|
|
22
|
+
expect(error.message).to eq("AMQP error")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "uses custom message when provided" do
|
|
26
|
+
error = described_class.new("Custom error")
|
|
27
|
+
expect(error.message).to eq("Custom error")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
RSpec.describe AMQ::Protocol::FrameTypeError do
|
|
33
|
+
it "formats message with valid types" do
|
|
34
|
+
error = described_class.new([:method, :headers])
|
|
35
|
+
expect(error.message).to include("[:method, :headers]")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
RSpec.describe AMQ::Protocol::EmptyResponseError do
|
|
40
|
+
it "has a default message" do
|
|
41
|
+
error = described_class.new
|
|
42
|
+
expect(error.message).to eq("Empty response received from the server.")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "accepts custom message" do
|
|
46
|
+
error = described_class.new("Custom empty response")
|
|
47
|
+
expect(error.message).to eq("Custom empty response")
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
RSpec.describe AMQ::Protocol::BadResponseError do
|
|
52
|
+
it "formats message with argument, expected, and actual values" do
|
|
53
|
+
error = described_class.new("channel", 1, 2)
|
|
54
|
+
expect(error.message).to include("channel")
|
|
55
|
+
expect(error.message).to include("1")
|
|
56
|
+
expect(error.message).to include("2")
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
RSpec.describe AMQ::Protocol::SoftError do
|
|
61
|
+
it "is a subclass of Protocol::Error" do
|
|
62
|
+
expect(described_class.superclass).to eq(AMQ::Protocol::Error)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
RSpec.describe AMQ::Protocol::HardError do
|
|
67
|
+
it "is a subclass of Protocol::Error" do
|
|
68
|
+
expect(described_class.superclass).to eq(AMQ::Protocol::Error)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# encoding: binary
|
|
2
|
+
|
|
3
|
+
RSpec.describe AMQ::Protocol::Float32Bit do
|
|
4
|
+
describe "#initialize" do
|
|
5
|
+
it "stores the value" do
|
|
6
|
+
f = described_class.new(3.14)
|
|
7
|
+
expect(f.value).to eq(3.14)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
describe "#value" do
|
|
12
|
+
it "returns the stored value" do
|
|
13
|
+
f = described_class.new(2.718)
|
|
14
|
+
expect(f.value).to eq(2.718)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "works with zero" do
|
|
18
|
+
f = described_class.new(0.0)
|
|
19
|
+
expect(f.value).to eq(0.0)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "works with negative values" do
|
|
23
|
+
f = described_class.new(-1.5)
|
|
24
|
+
expect(f.value).to eq(-1.5)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -86,6 +86,70 @@ module AMQ
|
|
|
86
86
|
expect(subject.properties[:delivery_mode]).to eq(2)
|
|
87
87
|
expect(subject.properties[:priority]).to eq(0)
|
|
88
88
|
end
|
|
89
|
+
|
|
90
|
+
it "is not final" do
|
|
91
|
+
expect(subject.final?).to eq(false)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
describe BodyFrame do
|
|
96
|
+
subject { BodyFrame.new("test payload", 1) }
|
|
97
|
+
|
|
98
|
+
it "returns payload as decode_payload" do
|
|
99
|
+
expect(subject.decode_payload).to eq("test payload")
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "is not final" do
|
|
103
|
+
expect(subject.final?).to eq(false)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it "has correct size" do
|
|
107
|
+
expect(subject.size).to eq(12)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
describe HeartbeatFrame do
|
|
112
|
+
it "encodes with empty payload on channel 0" do
|
|
113
|
+
encoded = HeartbeatFrame.encode
|
|
114
|
+
expect(encoded.bytes.last).to eq(0xCE)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "is final" do
|
|
118
|
+
frame = HeartbeatFrame.new("", 0)
|
|
119
|
+
expect(frame.final?).to eq(true)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
describe MethodFrame do
|
|
124
|
+
it "is not final when method has content" do
|
|
125
|
+
# Basic.Publish has content
|
|
126
|
+
payload = "\x00\x3C\x00\x28\x00\x00\x00\x00\x00"
|
|
127
|
+
frame = MethodFrame.new(payload, 1)
|
|
128
|
+
# This will depend on the method class
|
|
129
|
+
expect(frame).to respond_to(:final?)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
describe FrameSubclass do
|
|
134
|
+
subject { BodyFrame.new("test", 1) }
|
|
135
|
+
|
|
136
|
+
it "has channel accessor" do
|
|
137
|
+
expect(subject.channel).to eq(1)
|
|
138
|
+
subject.channel = 2
|
|
139
|
+
expect(subject.channel).to eq(2)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it "encodes to array" do
|
|
143
|
+
result = subject.encode_to_array
|
|
144
|
+
expect(result).to be_an(Array)
|
|
145
|
+
expect(result.size).to eq(3)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it "encodes to string" do
|
|
149
|
+
result = subject.encode
|
|
150
|
+
expect(result).to be_a(String)
|
|
151
|
+
expect(result.bytes.last).to eq(0xCE)
|
|
152
|
+
end
|
|
89
153
|
end
|
|
90
154
|
end
|
|
91
155
|
end
|
|
@@ -254,6 +254,38 @@ module AMQ
|
|
|
254
254
|
end
|
|
255
255
|
|
|
256
256
|
end # describe
|
|
257
|
+
|
|
258
|
+
describe ".length" do
|
|
259
|
+
it "returns the table length from binary data" do
|
|
260
|
+
encoded = Table.encode({"test" => 1})
|
|
261
|
+
expect(Table.length(encoded)).to eq(14)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
it "returns 0 for empty table" do
|
|
265
|
+
encoded = Table.encode({})
|
|
266
|
+
expect(Table.length(encoded)).to eq(0)
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
describe ".hash_size" do
|
|
271
|
+
it "calculates size for simple hash" do
|
|
272
|
+
size = Table.hash_size({"key" => "value"})
|
|
273
|
+
expect(size).to be > 0
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
it "returns 0 for empty hash" do
|
|
277
|
+
size = Table.hash_size({})
|
|
278
|
+
expect(size).to eq(0)
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
describe "Table::InvalidTableError" do
|
|
283
|
+
it "formats error message with key and value" do
|
|
284
|
+
error = Table::InvalidTableError.new("mykey", Object.new)
|
|
285
|
+
expect(error.message).to include("mykey")
|
|
286
|
+
expect(error.message).to include("Object")
|
|
287
|
+
end
|
|
288
|
+
end
|
|
257
289
|
end
|
|
258
290
|
end
|
|
259
291
|
end
|