iostruct 0.5.0 → 0.7.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,287 @@
1
+ require 'spec_helper'
2
+ require 'stringio'
3
+
4
+ describe IOStruct do
5
+ describe ".get_type_size" do
6
+ it "returns size for known types" do
7
+ expect(described_class.get_type_size('int')).to eq 4
8
+ expect(described_class.get_type_size('char')).to eq 1
9
+ expect(described_class.get_type_size('short')).to eq 2
10
+ expect(described_class.get_type_size('long long')).to eq 8
11
+ expect(described_class.get_type_size('double')).to eq 8
12
+ expect(described_class.get_type_size('float')).to eq 4
13
+ end
14
+
15
+ it "works with symbol input" do
16
+ expect(described_class.get_type_size(:int)).to eq 4
17
+ end
18
+
19
+ it "returns nil for unknown types" do
20
+ expect(described_class.get_type_size('unknown')).to be_nil
21
+ end
22
+ end
23
+
24
+ describe "hash format ctor" do
25
+ it "works" do
26
+ klass = described_class.new(
27
+ fields: {
28
+ x: "int",
29
+ y: :int,
30
+ z: { type: :int },
31
+ },
32
+ struct_name: 'Point'
33
+ )
34
+ expect(klass.new.inspect).to match(/<Point x=nil y=nil z=nil>/)
35
+ expect(klass.size).to eq(12)
36
+ expect(klass::SIZE).to eq(12)
37
+ end
38
+
39
+ it "respects :name" do
40
+ klass = described_class.new(
41
+ fields: {
42
+ x: "int",
43
+ y: :int,
44
+ z: { type: :int },
45
+ },
46
+ struct_name: 'Point'
47
+ )
48
+ expect(klass.new.inspect).to match(/<Point x=nil y=nil z=nil>/)
49
+ end
50
+
51
+ it "respects :offset" do
52
+ klass = described_class.new(
53
+ struct_name: 'Point',
54
+ fields: {
55
+ x: "int",
56
+ y: :int,
57
+ z: { type: :int, offset: 0x10 },
58
+ }
59
+ )
60
+ expect(klass.new.inspect).to match(/<Point x=nil y=nil z=nil>/)
61
+ expect(klass.size).to eq(0x14)
62
+
63
+ obj = klass.read((0..0x20).to_a.pack('i*'))
64
+ expect(obj.x).to eq(0)
65
+ expect(obj.y).to eq(1)
66
+ expect(obj.z).to eq(4)
67
+ end
68
+
69
+ context 'when two fields have same offset' do
70
+ it 'fails' do
71
+ expect do
72
+ described_class.new(
73
+ struct_name: 'Point',
74
+ fields: {
75
+ x: { type: :int, offset: 0 },
76
+ y: { type: :char, offset: 0 },
77
+ }
78
+ )
79
+ end.to raise_error(RuntimeError)
80
+ end
81
+ end
82
+
83
+ it "can override size" do
84
+ klass = described_class.new(
85
+ fields: {
86
+ x: "int",
87
+ y: :int,
88
+ z: { type: 'int' },
89
+ },
90
+ size: 0x100
91
+ )
92
+ expect(klass.size).to eq(0x100)
93
+ end
94
+
95
+ it "supports arrays" do
96
+ klass = described_class.new(
97
+ fields: {
98
+ x: "int",
99
+ a: { type: 'int', count: 3 },
100
+ y: :int,
101
+ },
102
+ )
103
+ expect(klass.size).to eq(4 * 5)
104
+
105
+ v = klass.read([1, 2, 3, 4, 5, 6, 7].pack('i*'))
106
+
107
+ expect(v.x).to eq(1)
108
+ expect(v.a).to eq([2, 3, 4])
109
+ expect(v.y).to eq(5)
110
+ end
111
+
112
+ it "supports nesting" do
113
+ point = described_class.new( fields: { x: "int", y: :int } )
114
+ rect = described_class.new(
115
+ struct_name: 'Rect',
116
+ fields: {
117
+ topLeft: point,
118
+ bottomRight: point,
119
+ }
120
+ )
121
+ expect(rect.size).to eq(16)
122
+
123
+ r = rect.read([10, 20, 100, 200].pack('i*'))
124
+ expect(r.topLeft).to be_instance_of(point)
125
+ expect(r.bottomRight).to be_instance_of(point)
126
+ expect(r.topLeft.x).to eq(10)
127
+ expect(r.topLeft.y).to eq(20)
128
+ expect(r.bottomRight.x).to eq(100)
129
+ expect(r.bottomRight.y).to eq(200)
130
+ end
131
+
132
+ it "packs nested structs" do
133
+ point = described_class.new(fields: { x: "int", y: :int })
134
+ rect = described_class.new(fields: { topLeft: point, bottomRight: point })
135
+
136
+ r = rect.read([10, 20, 100, 200].pack('i*'))
137
+ packed = r.pack
138
+ reparsed = rect.read(packed)
139
+
140
+ expect(reparsed.topLeft.x).to eq 10
141
+ expect(reparsed.topLeft.y).to eq 20
142
+ expect(reparsed.bottomRight.x).to eq 100
143
+ expect(reparsed.bottomRight.y).to eq 200
144
+ end
145
+
146
+ it "supports nested struct arrays" do
147
+ point = described_class.new(fields: { x: "int", y: :int })
148
+ polygon = described_class.new(
149
+ fields: {
150
+ num_points: 'int',
151
+ points: { type: point, count: 3 },
152
+ }
153
+ )
154
+ expect(polygon.size).to eq(4 + (8 * 3))
155
+
156
+ data = [3, 10, 20, 30, 40, 50, 60].pack('i*')
157
+ p = polygon.read(data)
158
+
159
+ expect(p.num_points).to eq 3
160
+ expect(p.points.size).to eq 3
161
+ expect(p.points[0]).to be_instance_of(point)
162
+ expect(p.points[0].x).to eq 10
163
+ expect(p.points[0].y).to eq 20
164
+ expect(p.points[1].x).to eq 30
165
+ expect(p.points[1].y).to eq 40
166
+ expect(p.points[2].x).to eq 50
167
+ expect(p.points[2].y).to eq 60
168
+
169
+ # Test round-trip
170
+ reparsed = polygon.read(p.pack)
171
+ expect(reparsed.points[2].y).to eq 60
172
+ end
173
+
174
+ it "packs arrays" do
175
+ klass = described_class.new(
176
+ fields: {
177
+ x: "int",
178
+ a: { type: 'int', count: 3 },
179
+ y: :int,
180
+ }
181
+ )
182
+
183
+ v = klass.read([1, 2, 3, 4, 5].pack('i*'))
184
+ packed = v.pack
185
+ reparsed = klass.read(packed)
186
+
187
+ expect(reparsed.x).to eq 1
188
+ expect(reparsed.a).to eq [2, 3, 4]
189
+ expect(reparsed.y).to eq 5
190
+ end
191
+
192
+ context "error handling" do
193
+ it "raises on unknown field type" do
194
+ expect do
195
+ described_class.new(fields: { x: "unknown_type" })
196
+ end.to raise_error(/unknown field type/)
197
+ end
198
+
199
+ it "raises on invalid type format" do
200
+ expect do
201
+ described_class.new(fields: { x: 12345 })
202
+ end.to raise_error(/unexpected field desc type/)
203
+ end
204
+
205
+ it "raises when forced size is smaller than actual" do
206
+ expect do
207
+ described_class.new(
208
+ fields: { x: "int", y: "int", z: "int" },
209
+ size: 4
210
+ )
211
+ end.to raise_error(/actual struct size .* is greater than forced size/)
212
+ end
213
+ end
214
+
215
+ context "C type aliases" do
216
+ it "supports uint types" do
217
+ klass = described_class.new(fields: {
218
+ a: 'uint8_t',
219
+ b: 'uint16_t',
220
+ c: 'uint32_t',
221
+ d: 'uint64_t'
222
+ })
223
+ expect(klass.size).to eq(1 + 2 + 4 + 8)
224
+ end
225
+
226
+ it "supports int types" do
227
+ klass = described_class.new(fields: {
228
+ a: 'int8_t',
229
+ b: 'int16_t',
230
+ c: 'int32_t',
231
+ d: 'int64_t'
232
+ })
233
+ expect(klass.size).to eq(1 + 2 + 4 + 8)
234
+ end
235
+
236
+ it "supports _BYTE type" do
237
+ klass = described_class.new(fields: { a: '_BYTE' })
238
+ expect(klass.size).to eq 1
239
+ expect(klass.read("\xff").a).to eq 255 # unsigned
240
+ end
241
+ end
242
+
243
+ context "endian types" do
244
+ it "supports big-endian uint16" do
245
+ klass = described_class.new(fields: { a: 'uint16_be' })
246
+ expect(klass.size).to eq 2
247
+ expect(klass.read("\x01\x02").a).to eq 0x0102 # big-endian
248
+ end
249
+
250
+ it "supports big-endian uint32" do
251
+ klass = described_class.new(fields: { a: 'uint32_be' })
252
+ expect(klass.size).to eq 4
253
+ expect(klass.read("\x01\x02\x03\x04").a).to eq 0x01020304
254
+ end
255
+
256
+ it "supports little-endian uint16" do
257
+ klass = described_class.new(fields: { a: 'uint16_le' })
258
+ expect(klass.size).to eq 2
259
+ expect(klass.read("\x01\x02").a).to eq 0x0201 # little-endian
260
+ end
261
+
262
+ it "supports little-endian uint32" do
263
+ klass = described_class.new(fields: { a: 'uint32_le' })
264
+ expect(klass.size).to eq 4
265
+ expect(klass.read("\x01\x02\x03\x04").a).to eq 0x04030201
266
+ end
267
+
268
+ it "supports alternate names (be16, le32, etc.)" do
269
+ klass = described_class.new(fields: {
270
+ a: 'be16',
271
+ b: 'be32',
272
+ c: 'le16',
273
+ d: 'le32'
274
+ })
275
+ expect(klass.size).to eq(2 + 4 + 2 + 4)
276
+ end
277
+
278
+ it "round-trips big-endian values" do
279
+ klass = described_class.new(fields: { a: 'uint16_be', b: 'uint32_be' })
280
+ obj = klass.read([0x1234, 0xDEADBEEF].pack('nN'))
281
+ expect(obj.a).to eq 0x1234
282
+ expect(obj.b).to eq 0xDEADBEEF
283
+ expect(obj.pack).to eq [0x1234, 0xDEADBEEF].pack('nN')
284
+ end
285
+ end
286
+ end
287
+ end
@@ -2,40 +2,240 @@ require 'spec_helper'
2
2
  require 'stringio'
3
3
 
4
4
  describe IOStruct do
5
- describe ":inspect_name_override" do
5
+ describe "#pack" do
6
+ it "serializes struct back to binary" do
7
+ struct = described_class.new('L S C', :a, :b, :c)
8
+ obj = struct.new(a: 0x12345678, b: 0xABCD, c: 0xEF)
9
+ expect(obj.pack).to eq [0x12345678, 0xABCD, 0xEF].pack('L S C')
10
+ end
11
+
12
+ it "round-trips correctly" do
13
+ struct = described_class.new('L S C', :a, :b, :c)
14
+ data = [123, 456, 78].pack('L S C')
15
+ obj = struct.read(data)
16
+ expect(obj.pack).to eq data
17
+ end
18
+ end
19
+
20
+ describe "#empty?" do
21
+ let(:struct) { described_class.new('L S', :a, :b) }
22
+
23
+ it "returns true when all fields are zero" do
24
+ expect(struct.new(a: 0, b: 0)).to be_empty
25
+ end
26
+
27
+ it "returns true when all fields are nil" do
28
+ expect(struct.new).to be_empty
29
+ end
30
+
31
+ it "returns false when any field is non-zero" do
32
+ expect(struct.new(a: 1, b: 0)).not_to be_empty
33
+ end
34
+
35
+ it "returns true for null-filled strings" do
36
+ str_struct = described_class.new('a4', :name)
37
+ expect(str_struct.new(name: "\x00\x00\x00\x00")).to be_empty
38
+ end
39
+
40
+ it "returns false for non-empty strings" do
41
+ str_struct = described_class.new('a4', :name)
42
+ expect(str_struct.new(name: "test")).not_to be_empty
43
+ end
44
+ end
45
+
46
+ describe "#inspect" do
47
+ [nil, "MyClass"].each do |struct_name|
48
+ context "when struct_name is #{struct_name.inspect}" do
49
+ [:hex, :dec].each do |inspect_mode|
50
+ context "when inspect is :#{inspect_mode}" do
51
+ context "for IOStruct" do
52
+ it "shows default struct name" do
53
+ struct = described_class.new('L S C', :a, :b, :c, inspect: inspect_mode, struct_name: struct_name)
54
+ cname = struct_name || "struct"
55
+ expect(struct.new.inspect).to match(/<#{cname} a=/)
56
+ end
57
+ end
58
+
59
+ context "for IOStruct anonymous subclass" do
60
+ it "shows default struct name" do
61
+ struct = Class.new( described_class.new('L S C', :a, :b, :c, inspect: inspect_mode, struct_name: struct_name) )
62
+ cname = struct_name || "struct"
63
+ expect(struct.new.inspect).to match(/<#{cname} a=/)
64
+ end
65
+ end
66
+
67
+ context "for named IOStruct subclass" do
68
+ it "shows custom struct name" do
69
+ stub_const("C1", described_class.new('L S C', :a, :b, :c, inspect: inspect_mode, struct_name: struct_name) )
70
+ cname = struct_name || "C1"
71
+ expect(C1.new.inspect).to match(/<#{cname} a=/)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ describe "hash-style initialization" do
81
+ let(:struct) { described_class.new('L S C', :a, :b, :c) }
82
+
83
+ it "initializes fields by name" do
84
+ obj = struct.new(a: 100, c: 50)
85
+ expect(obj.a).to eq 100
86
+ expect(obj.b).to be_nil
87
+ expect(obj.c).to eq 50
88
+ end
89
+ end
90
+
91
+ describe "auto-generated field names" do
92
+ it "generates names based on offset" do
93
+ struct = described_class.new('C S L')
94
+ expect(struct.members).to eq [:f0, :f1, :f3]
95
+ end
96
+ end
97
+
98
+ describe "field renaming" do
99
+ it "renames auto-generated fields via kwargs" do
100
+ struct = described_class.new('C S L', f0: :byte_field, f3: :long_field)
101
+ expect(struct.members).to eq [:byte_field, :f1, :long_field]
102
+ end
103
+
104
+ it "renames named fields via kwargs" do
105
+ struct = described_class.new('C S L', :a, :b, :c, a: :first, c: :last)
106
+ expect(struct.members).to eq [:first, :b, :last]
107
+ end
108
+ end
109
+
110
+ describe "float types" do
111
+ it "unpacks single-precision float (f/F)" do
112
+ data = [3.14159].pack('f')
113
+ struct = described_class.new('f', :val)
114
+ expect(struct.read(data).val).to be_within(0.0001).of(3.14159)
115
+ expect(struct::SIZE).to eq 4
116
+ end
117
+
118
+ it "unpacks double-precision float (d/D)" do
119
+ data = [3.141592653589793].pack('d')
120
+ struct = described_class.new('d', :val)
121
+ expect(struct.read(data).val).to be_within(0.0000001).of(3.141592653589793)
122
+ expect(struct::SIZE).to eq 8
123
+ end
124
+
125
+ it "unpacks little-endian floats (e/E)" do
126
+ data = [2.5].pack('e')
127
+ struct = described_class.new('e', :val)
128
+ expect(struct.read(data).val).to be_within(0.0001).of(2.5)
129
+ end
130
+
131
+ it "unpacks big-endian floats (g/G)" do
132
+ data = [2.5].pack('G')
133
+ struct = described_class.new('G', :val)
134
+ expect(struct.read(data).val).to be_within(0.0001).of(2.5)
135
+ end
136
+
137
+ it "formats floats in to_table" do
138
+ struct = described_class.new('f', :val)
139
+ obj = struct.new(val: 3.14159)
140
+ expect(obj.to_table).to match(/val=\s*3\.142/)
141
+ end
142
+ end
143
+
144
+ describe "signed integer types" do
145
+ it "reads signed 8-bit (c)" do
146
+ struct = described_class.new('c', :val)
147
+ expect(struct.read("\x80").val).to eq(-128)
148
+ expect(struct.read("\x7f").val).to eq(127)
149
+ end
150
+
151
+ it "reads signed 16-bit (s)" do
152
+ struct = described_class.new('s', :val)
153
+ expect(struct.read("\x00\x80").val).to eq(-32768)
154
+ end
155
+
156
+ it "reads signed 32-bit (i)" do
157
+ struct = described_class.new('i', :val)
158
+ expect(struct.read("\x00\x00\x00\x80").val).to eq(-2147483648)
159
+ end
160
+
161
+ it "reads signed 64-bit (q)" do
162
+ struct = described_class.new('q', :val)
163
+ expect(struct.read("\x00\x00\x00\x00\x00\x00\x00\x80").val).to eq(-9223372036854775808)
164
+ end
165
+ end
166
+
167
+ describe "string types" do
168
+ it "unpacks space-padded string (A)" do
169
+ struct = described_class.new('A8', :name)
170
+ obj = struct.read("hello ")
171
+ expect(obj.name).to eq "hello"
172
+ end
173
+
174
+ it "unpacks null-terminated string (Z)" do
175
+ struct = described_class.new('Z8', :name)
176
+ obj = struct.read("hello\x00\x00\x00")
177
+ expect(obj.name).to eq "hello"
178
+ end
179
+
180
+ it "unpacks raw string (a)" do
181
+ struct = described_class.new('a8', :name)
182
+ obj = struct.read("hello\x00\x00\x00")
183
+ expect(obj.name).to eq "hello\x00\x00\x00"
184
+ end
185
+ end
186
+
187
+ describe "error handling" do
188
+ it "raises when no fmt and no :fields" do
189
+ expect { described_class.new }.to raise_error(/no fmt and no :fields/)
190
+ end
191
+
192
+ it "raises when reading from unsupported source" do
193
+ struct = described_class.new('L', :val)
194
+ expect { struct.read(12345) }.to raise_error(/don't know how to read/)
195
+ end
196
+ end
197
+
198
+ describe "inspect_name_override (deprecated)" do
199
+ it "works as alias for struct_name" do
200
+ struct = described_class.new('L', :val, inspect_name_override: 'MyStruct')
201
+ expect(struct.new.inspect).to match(/<MyStruct/)
202
+ end
203
+ end
204
+
205
+ describe ":name" do
6
206
  context "when set" do
7
207
  it "uses the custom name" do
8
- x = IOStruct.new('LL', :x, :y, inspect_name_override: 'Point')
9
- expect(x.new.inspect).to match /<Point x=nil y=nil>/
208
+ x = described_class.new('LL', :x, :y, struct_name: 'Point')
209
+ expect(x.new.inspect).to match(/<Point x=nil y=nil>/)
10
210
  end
11
211
  end
12
212
 
13
213
  context "when not set" do
14
214
  it "has default name" do
15
- x = IOStruct.new('LL', :x, :y)
16
- expect(x.new.inspect).to match /<#<Class:0x\h+> x=nil y=nil>/
215
+ x = described_class.new('LL', :x, :y)
216
+ expect(x.new.inspect).to match(/<struct x=nil y=nil>/)
17
217
  end
18
218
  end
19
219
  end
20
220
 
21
- describe "#read" do
221
+ describe "read" do
22
222
  let(:a) { [12345, 56789] }
23
223
  let(:data) { a.pack('L2') }
24
224
 
25
225
  it "reads from IO" do
26
- x = IOStruct.new('LL', :x, :y).read(StringIO.new(data))
226
+ x = described_class.new('LL', :x, :y).read(StringIO.new(data))
27
227
  expect(x.x).to eq a[0]
28
228
  expect(x.y).to eq a[1]
29
229
  end
30
230
 
31
231
  it "reads from String" do
32
- x = IOStruct.new('LL', :x, :y).read(data)
232
+ x = described_class.new('LL', :x, :y).read(data)
33
233
  expect(x.x).to eq a[0]
34
234
  expect(x.y).to eq a[1]
35
235
  end
36
236
 
37
237
  it "creates a new instance of a subclass" do
38
- klass = Class.new( IOStruct.new('LL', :x, :y) )
238
+ klass = Class.new( described_class.new('LL', :x, :y) )
39
239
  x = klass.read(data)
40
240
  expect(x).to be_a klass
41
241
  end
@@ -43,7 +243,7 @@ describe IOStruct do
43
243
 
44
244
  context "zero-length strings" do
45
245
  let(:data) { [1, 2].pack('CC') }
46
- let(:struct) { IOStruct.new('C a0 C', :a, :b, :c) }
246
+ let(:struct) { described_class.new('C a0 C', :a, :b, :c) }
47
247
 
48
248
  it "deserializes" do
49
249
  x = struct.read(data)
@@ -61,8 +261,8 @@ describe IOStruct do
61
261
  end
62
262
 
63
263
  it "reads correct number of bytes from IO" do
64
- io = StringIO.new(data*2)
65
- x = struct.read(io)
264
+ io = StringIO.new(data * 2)
265
+ struct.read(io)
66
266
  expect(io.pos).to eq 2
67
267
  end
68
268
 
@@ -75,29 +275,30 @@ describe IOStruct do
75
275
  it "skips on 'x'" do
76
276
  a = [12345, 56789]
77
277
  data = a.pack('L2')
78
- x = IOStruct.new('x4L', :y).read(data)
278
+ x = described_class.new('x4L', :y).read(data)
79
279
  expect(x.y).to eq a[1]
80
280
  end
81
281
 
82
282
  it "unpacks hex-string (H)" do
83
283
  data = "1234"
84
- struct = IOStruct.new('H8', :x).read(data)
284
+ struct = described_class.new('H8', :x).read(data)
85
285
  expect(struct.x).to eq "31323334"
86
286
  expect(struct.pack).to eq data
87
287
  end
88
288
 
89
289
  it "unpacks reverse-nibbled hex-string (h)" do
90
290
  data = "1234"
91
- struct = IOStruct.new('h8', :x).read(data)
291
+ struct = described_class.new('h8', :x).read(data)
92
292
  expect(struct.x).to eq "13233343"
93
293
  expect(struct.pack).to eq data
94
294
  end
95
295
 
296
+ # rubocop:disable RSpec/RepeatedExample
96
297
  ['n', 'N', 'S>', 'L>', 'I>'].each do |fmt|
97
298
  it "unpacks unsigned big-endian '#{fmt}'" do
98
299
  a = [12345]
99
300
  data = a.pack(fmt)
100
- x = IOStruct.new(fmt, :x).read(data)
301
+ x = described_class.new(fmt, :x).read(data)
101
302
  expect(x.x).to eq a[0]
102
303
  expect(x.pack).to eq data
103
304
  end
@@ -107,27 +308,26 @@ describe IOStruct do
107
308
  it "unpacks unsigned little-endian '#{fmt}'" do
108
309
  a = [12345]
109
310
  data = a.pack(fmt)
110
- x = IOStruct.new(fmt, :x).read(data)
311
+ x = described_class.new(fmt, :x).read(data)
111
312
  expect(x.x).to eq a[0]
112
313
  expect(x.pack).to eq data
113
314
  end
114
315
  end
316
+ # rubocop:enable RSpec/RepeatedExample
115
317
 
116
318
  it "throws exception on unknown format" do
117
- expect { IOStruct.new('K', :x) }.to raise_error('Unknown field type "K"')
319
+ expect { described_class.new('K', :x) }.to raise_error('Unknown field type "K"')
118
320
  end
119
321
 
120
322
  context '__offset field' do
121
323
  let(:data) { 0x100.times.to_a.pack('L*') }
122
324
  let(:io) { StringIO.new(data) }
123
- let(:struct) { IOStruct.new('LLLL', :a, :b, :c, :d) }
325
+ let(:struct) { described_class.new('LLLL', :a, :b, :c, :d) }
124
326
 
125
327
  context 'when src is an IO' do
126
328
  it 'is set to the current IO position' do
127
329
  a = []
128
- while !io.eof?
129
- a << struct.read(io)
130
- end
330
+ a << struct.read(io) until io.eof?
131
331
  expect(a.map(&:__offset)).to eq (0...0x400).step(0x10).to_a
132
332
  end
133
333
  end
@@ -139,4 +339,77 @@ describe IOStruct do
139
339
  end
140
340
  end
141
341
  end
342
+
343
+ describe "to_table" do
344
+ context "when inspect is :hex" do
345
+ it "formats signed struct as table" do
346
+ signed_struct = described_class.new('c s i q', inspect: :hex)
347
+ expect(signed_struct.new.to_table).to eq(
348
+ "<struct f0= 0 f1= 0 f3= 0 f7= 0>"
349
+ )
350
+ expect(signed_struct.read("\xff" * 16).to_table).to eq(
351
+ "<struct f0=ff f1=ffff f3=ffffffff f7=ffffffffffffffff>"
352
+ )
353
+ end
354
+
355
+ it "formats unsigned struct as table" do
356
+ unsigned_struct = described_class.new('C S I Q', inspect: :hex)
357
+ expect(unsigned_struct.new.to_table).to eq(
358
+ "<struct f0= 0 f1= 0 f3= 0 f7= 0>"
359
+ )
360
+ expect(unsigned_struct.read("\xff" * 16).to_table).to eq(
361
+ "<struct f0=ff f1=ffff f3=ffffffff f7=ffffffffffffffff>"
362
+ )
363
+ end
364
+
365
+ it "works with unknown types" do
366
+ struct = described_class.new('C', :a, :name, inspect: :hex)
367
+ s = struct.new
368
+ expect(s.to_table).to eq(
369
+ "<struct a= 0 name=nil>"
370
+ )
371
+ s.name = "test"
372
+ expect(s.to_table).to eq(
373
+ '<struct a= 0 name="test">'
374
+ )
375
+ end
376
+ end
377
+
378
+ context "when inspect is not :hex" do
379
+ it "formats signed struct as table" do
380
+ signed_struct = described_class.new('c s i q', inspect: :dec)
381
+ expect(signed_struct.new.to_table).to eq(
382
+ "<struct f0= 0 f1= 0 f3= 0 f7= 0>"
383
+ )
384
+ expect(signed_struct.read("\xff" * 16).to_table).to eq(
385
+ "<struct f0= -1 f1= -1 f3= -1 f7= -1>"
386
+ )
387
+ expect(signed_struct.read("\x80\x00\x80\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80").to_table).to eq(
388
+ "<struct f0=-128 f1=-32768 f3=-2147483648 f7=-9223372036854775808>"
389
+ )
390
+ end
391
+
392
+ it "formats unsigned struct as table" do
393
+ unsigned_struct = described_class.new('C S I Q', inspect: :dec)
394
+ expect(unsigned_struct.new.to_table).to eq(
395
+ "<struct f0= 0 f1= 0 f3= 0 f7= 0>"
396
+ )
397
+ expect(unsigned_struct.read("\xff" * 16).to_table).to eq(
398
+ "<struct f0= 255 f1= 65535 f3= 4294967295 f7=18446744073709551615>"
399
+ )
400
+ end
401
+
402
+ it "works with unknown types" do
403
+ struct = described_class.new('C', :a, :name, inspect: :dec)
404
+ s = struct.new
405
+ expect(s.to_table).to eq(
406
+ "<struct a= 0 name=nil>"
407
+ )
408
+ s.name = "test"
409
+ expect(s.to_table).to eq(
410
+ '<struct a= 0 name="test">'
411
+ )
412
+ end
413
+ end
414
+ end
142
415
  end