rstruct 0.1.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.
- data/.document +5 -0
- data/.rspec +2 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +675 -0
- data/README.rdoc +137 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/lib/rstruct.rb +70 -0
- data/lib/rstruct/base_types.rb +5 -0
- data/lib/rstruct/base_types/container_type.rb +102 -0
- data/lib/rstruct/base_types/packed_type.rb +78 -0
- data/lib/rstruct/base_types/type.rb +55 -0
- data/lib/rstruct/field.rb +22 -0
- data/lib/rstruct/registry.rb +66 -0
- data/lib/rstruct/struct_builder.rb +30 -0
- data/lib/rstruct/structure.rb +59 -0
- data/lib/rstruct/types.rb +44 -0
- data/samples/fatparse.rb +78 -0
- data/spec/registry_behaviors.rb +64 -0
- data/spec/registry_spec.rb +61 -0
- data/spec/rstruct_spec.rb +88 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/structure_spec.rb +297 -0
- data/spec/type_behaviors.rb +158 -0
- metadata +144 -0
@@ -0,0 +1,88 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Rstruct do
|
4
|
+
|
5
|
+
it "should be a module" do
|
6
|
+
Rstruct.should be_a(Module)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should supply a struct method" do
|
10
|
+
Rstruct.should respond_to(:struct)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should supply a helper to access the default registry" do
|
14
|
+
Rstruct.should respond_to(:default_registry)
|
15
|
+
Rstruct.default_registry.should == Rstruct::Registry::DEFAULT_REGISTRY
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should let others import methods with the ClassMethods mixin" do
|
19
|
+
c=Class.new(Object){ extend(Rstruct::ClassMethods) }
|
20
|
+
c.should respond_to(:struct)
|
21
|
+
c.should respond_to(:default_registry)
|
22
|
+
c.should respond_to(:typedef)
|
23
|
+
c.default_registry.should == Rstruct::Registry::DEFAULT_REGISTRY
|
24
|
+
|
25
|
+
m=Module.new{ extend(Rstruct::ClassMethods) }
|
26
|
+
m.should respond_to(:struct)
|
27
|
+
m.should respond_to(:default_registry)
|
28
|
+
m.should respond_to(:typedef)
|
29
|
+
m.default_registry.should == Rstruct::Registry::DEFAULT_REGISTRY
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'Typedefs' do
|
33
|
+
it "should allow types to be aliased with 'typedef'" do
|
34
|
+
Rstruct.typedef(:int32, :int32_copy)
|
35
|
+
reg = Rstruct.default_registry
|
36
|
+
reg[:int32_copy].should == reg[:int32]
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should raise an exception when invalid types are aliased with 'typedef'" do
|
40
|
+
lambda{ Rstruct.typedef(:nonexistent_type, :int32_copy) }.should raise_error(Rstruct::InvalidTypeError)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "The struct method" do
|
45
|
+
it "should require a block" do
|
46
|
+
lambda { Rstruct.struct(:rspec_fail_block) }.should raise_error(ArgumentError)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should require fields to be defined in the block" do
|
50
|
+
lambda {
|
51
|
+
Rstruct.struct(:rspec_return_struct, :register => false){ }
|
52
|
+
}.should raise_error(Rstruct::StructError)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should return a structure" do
|
56
|
+
s=Rstruct.struct(:rspec_return_struct, :register => false){ byte :foo }
|
57
|
+
s.should be_a(Rstruct::Structure)
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
it "should define structures the same way as by calling Rstruct::Structure.new" do
|
62
|
+
s = Rstruct.struct(:rspec_struct_test) {
|
63
|
+
int32 :someint1
|
64
|
+
int32 :someint2
|
65
|
+
}
|
66
|
+
s.should be_a(Rstruct::Structure)
|
67
|
+
s.name.should == :rspec_struct_test
|
68
|
+
s.sizeof.should == 8
|
69
|
+
s.fields.should be_an(Array)
|
70
|
+
s.field_names.should == [:someint1, :someint2]
|
71
|
+
|
72
|
+
f1 = s.fields[0]
|
73
|
+
f2 = s.fields[1]
|
74
|
+
f1.typ.should be_a(Rstruct::Type)
|
75
|
+
f2.typ.should be_a(Rstruct::Type)
|
76
|
+
f1.typ.name.should == :int32
|
77
|
+
f2.typ.name.should == :int32
|
78
|
+
f1.name.should == :someint1
|
79
|
+
f2.name.should == :someint2
|
80
|
+
f1.args.should be_empty
|
81
|
+
f2.args.should be_empty
|
82
|
+
f1.block.should be_nil
|
83
|
+
f2.block.should be_nil
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'rstruct'
|
5
|
+
|
6
|
+
# Requires supporting files with custom matchers and macros, etc,
|
7
|
+
# in ./support/ and its subdirectories.
|
8
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,297 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'type_behaviors'
|
3
|
+
|
4
|
+
describe Rstruct::Structure do
|
5
|
+
context "initialization" do
|
6
|
+
it "requires a block" do
|
7
|
+
lambda { Rstruct::Structure.new(:rstruct_klass_fail_block) }.should raise_error(ArgumentError)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should return a structure" do
|
11
|
+
s=Rstruct::Structure.new(:rstruct_klass_return_struct, :register => false){ byte :foo }
|
12
|
+
s.should be_a(Rstruct::Structure)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should allow structure fields to be defined" do
|
16
|
+
s = Rstruct::Structure.new(:rstruct_klass_struct_test) {
|
17
|
+
int32 :someint1
|
18
|
+
int32 :someint2
|
19
|
+
}
|
20
|
+
s.should be_a(Rstruct::Structure)
|
21
|
+
s.sizeof.should == 8
|
22
|
+
s.fields.should be_an(Array)
|
23
|
+
s.field_names.should == [:someint1, :someint2]
|
24
|
+
s.field_types.should == [Rstruct::Int32, Rstruct::Int32]
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'registration' do
|
28
|
+
|
29
|
+
it "should register a structure by default" do
|
30
|
+
s=Rstruct::Structure.new(:rstruct_klass_not_reg_dflt){ byte :foo }
|
31
|
+
s.should be_a(Rstruct::Structure)
|
32
|
+
Rstruct.default_registry.get(:rstruct_klass_not_reg_dflt).should == s
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should allow a struct to explicitely opt out of registration" do
|
36
|
+
s=Rstruct::Structure.new(:rstruct_klass_not_reg, :register=>false){ byte :foo}
|
37
|
+
s.should be_a(Rstruct::Structure)
|
38
|
+
Rstruct.default_registry.get(:rstruct_klass_not_reg).should be_nil
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should allow a struct to register itself with the default registry" do
|
42
|
+
s=Rstruct::Structure.new(:rstruct_klass_registered, :register=>true){ byte :foo}
|
43
|
+
s.should be_a(Rstruct::Structure)
|
44
|
+
Rstruct.default_registry.get(:rstruct_klass_registered).should == s
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should allow a structure to register itself in a registry and still access default types" do
|
48
|
+
# create a registry for this test
|
49
|
+
reg = Rstruct::Registry.new(:rstruct_klass_test_reg1)
|
50
|
+
|
51
|
+
# create a struct which registers itself to this registry
|
52
|
+
s = Rstruct::Structure.new(:rstruct_klass_test_struct1, :register => reg) {
|
53
|
+
int32 :someint1
|
54
|
+
int32 :someint2
|
55
|
+
}
|
56
|
+
|
57
|
+
# confirm declaration went ok
|
58
|
+
s.fields.should be_an(Array)
|
59
|
+
s.field_names.should == [:someint1, :someint2]
|
60
|
+
s.field_types.should == [Rstruct::Int32, Rstruct::Int32]
|
61
|
+
|
62
|
+
# confirm the struct is registered
|
63
|
+
reg[:rstruct_klass_test_struct1].should == s
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should allow structure fields to come from the registry the struct is registered to" do
|
67
|
+
# create a registry for this test
|
68
|
+
reg = Rstruct::Registry.new(:rstruct_klass_test_reg2)
|
69
|
+
|
70
|
+
# create a test type registered to this registry
|
71
|
+
c = Rstruct::Type.new(:reg_test_typ, :register => reg)
|
72
|
+
reg[:reg_test_typ].should == c
|
73
|
+
|
74
|
+
# create a struct which registers itself to this registry
|
75
|
+
# and declares a field of the above type
|
76
|
+
s = Rstruct::Structure.new(:rstruct_klass_test_struct2, :register => reg) {
|
77
|
+
int32 :someint1
|
78
|
+
int32 :someint2
|
79
|
+
reg_test_typ :sometype
|
80
|
+
}
|
81
|
+
|
82
|
+
# confirm the struct is registered
|
83
|
+
reg[:rstruct_klass_test_struct2].should == s
|
84
|
+
Rstruct.default_registry[:rstruct_klass_test_struct].should be_nil
|
85
|
+
|
86
|
+
# confirm declaration went ok
|
87
|
+
s.fields.should be_an(Array)
|
88
|
+
s.field_names.should == [:someint1, :someint2, :sometype]
|
89
|
+
s.field_types.should == [Rstruct::Int32, Rstruct::Int32, c]
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should allow fields to come an alternate registry without registration of the struct" do
|
93
|
+
# create a registry for this test
|
94
|
+
reg = Rstruct::Registry.new(:rstruct_klass_test_reg3)
|
95
|
+
|
96
|
+
# create a test type registered to this registry
|
97
|
+
c = Rstruct::Type.new(:reg_test_typ2, :register => reg)
|
98
|
+
reg[:reg_test_typ2].should == c
|
99
|
+
|
100
|
+
# create a struct which registers itself to this registry
|
101
|
+
# and declares a field of the above type
|
102
|
+
s = Rstruct::Structure.new(:rstruct_klass_test_struct2, :fields_from => reg, :register => false) {
|
103
|
+
int32 :someint1
|
104
|
+
int32 :someint2
|
105
|
+
reg_test_typ2 :sometype
|
106
|
+
}
|
107
|
+
|
108
|
+
# confirm the struct is not registered
|
109
|
+
reg[:rstruct_klass_test_struct2].should be_nil
|
110
|
+
Rstruct.default_registry[:rstruct_klass_test_struct2].should be_nil
|
111
|
+
|
112
|
+
# confirm declaration went ok
|
113
|
+
s.fields.should be_an(Array)
|
114
|
+
s.field_names.should == [:someint1, :someint2, :sometype]
|
115
|
+
s.field_types.should == [Rstruct::Int32, Rstruct::Int32, c]
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should allow a struct to register itself to a different registry than some of its fields" do
|
119
|
+
# create 2 registries for this test
|
120
|
+
reg = Rstruct::Registry.new(:rstuct_klass_test_reg3)
|
121
|
+
sreg = Rstruct::Registry.new(:rstuct_klass_test_reg3)
|
122
|
+
|
123
|
+
# create a test type registered to this registry
|
124
|
+
c = Rstruct::Type.new(:reg_test_typ2, :register => reg)
|
125
|
+
reg[:reg_test_typ2].should == c
|
126
|
+
sreg[:reg_test_typ2].should be_nil
|
127
|
+
|
128
|
+
# create a struct which registers to one registry, but looks up fields from another
|
129
|
+
# and declares a field of the above type
|
130
|
+
s = Rstruct::Structure.new(:rstuct_klass_test_struct3, :fields_from => reg, :register => sreg) {
|
131
|
+
int32 :someint1
|
132
|
+
int32 :someint2
|
133
|
+
reg_test_typ2 :sometype
|
134
|
+
}
|
135
|
+
|
136
|
+
# confirm the struct is correctly registered
|
137
|
+
sreg[:rstuct_klass_test_struct3].should == s
|
138
|
+
reg[:rstuct_klass_test_struct3].should be_nil
|
139
|
+
|
140
|
+
# confirm declaration went ok
|
141
|
+
s.fields.should be_an(Array)
|
142
|
+
s.field_names.should == [:someint1, :someint2, :sometype]
|
143
|
+
s.field_types.should == [Rstruct::Int32, Rstruct::Int32, c]
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
context "a simple fixed-length struct" do
|
151
|
+
before :each do
|
152
|
+
@struct = Rstruct::Structure.new(:simple_fixed_length_struct, :register => false) {
|
153
|
+
uint32be :someint1
|
154
|
+
uint32be :someint2
|
155
|
+
}
|
156
|
+
|
157
|
+
@values = { :someint1 => 0xdeadbeef, :someint2 => 0xfacefeeb }
|
158
|
+
|
159
|
+
@instance = @struct.instance
|
160
|
+
@populate = lambda { @values.each { |k,v| @instance[k] = v } }
|
161
|
+
|
162
|
+
@pack_format = "NN"
|
163
|
+
@rawdata = "\xde\xad\xbe\xef\xfa\xce\xfe\xeb"
|
164
|
+
end
|
165
|
+
|
166
|
+
it_should_behave_like "a structure"
|
167
|
+
it_should_behave_like "a packable type"
|
168
|
+
it_should_behave_like "a groupable type"
|
169
|
+
end
|
170
|
+
|
171
|
+
context "a fixed-length mach_header struct" do
|
172
|
+
before :each do
|
173
|
+
Rstruct.typedef :uint32le, :cpu_type_t unless Rstruct.default_registry[:cpu_type_t]
|
174
|
+
Rstruct.typedef :uint32le, :cpu_subtype_t unless Rstruct.default_registry[:cpu_subtype_t]
|
175
|
+
|
176
|
+
@struct = Rstruct.struct(:mach_header, :register => false) {
|
177
|
+
uint32le :magic # mach magic number identifier
|
178
|
+
cpu_type_t :cputype # cpu specifier
|
179
|
+
cpu_subtype_t :cpusubtype # machine specifier
|
180
|
+
uint32le :filetype # type of file
|
181
|
+
uint32le :ncmds # number of load commands
|
182
|
+
uint32le :sizeofcmds # the size of all the load commands
|
183
|
+
uint32le :flags # flags
|
184
|
+
}
|
185
|
+
|
186
|
+
@values = {
|
187
|
+
:magic => 0xfeedface,
|
188
|
+
:cputype => 0x00000007,
|
189
|
+
:cpusubtype => 0x00000003,
|
190
|
+
:filetype => 0x00000002,
|
191
|
+
:ncmds => 0x0000000d,
|
192
|
+
:sizeofcmds => 0x000005ec,
|
193
|
+
:flags => 0x00000085,
|
194
|
+
}
|
195
|
+
|
196
|
+
@instance = @struct.instance
|
197
|
+
@populate = lambda { @values.each { |k,v| @instance[k] = v } }
|
198
|
+
|
199
|
+
@pack_format = "VVVVVVV"
|
200
|
+
@rawdata = @values.values_at(
|
201
|
+
:magic, :cputype, :cpusubtype, :filetype, :ncmds, :sizeofcmds, :flags
|
202
|
+
).pack(@pack_format)
|
203
|
+
|
204
|
+
@rawdata.should == "\316\372\355\376\a\000\000\000\003\000\000\000\002\000\000\000\r\000\000\000\354\005\000\000\205\000\000\000"
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
it_should_behave_like "a structure"
|
209
|
+
it_should_behave_like "a packable type"
|
210
|
+
it_should_behave_like "a groupable type"
|
211
|
+
end
|
212
|
+
|
213
|
+
context "a simple fixed-length nested struct" do
|
214
|
+
before :each do
|
215
|
+
inner_struct = Rstruct::Structure.new(:inner_fix_len_test, :register => true) {
|
216
|
+
byte :byte1
|
217
|
+
byte :byte2
|
218
|
+
} unless Rstruct.default_registry[:inner_fix_len_test]
|
219
|
+
|
220
|
+
@struct = Rstruct::Structure.new(:simple_fixed_length_struct, :register => false) {
|
221
|
+
uint32be :someint1
|
222
|
+
uint32be :someint2
|
223
|
+
inner_fix_len_test :inner
|
224
|
+
}
|
225
|
+
|
226
|
+
@values = { :someint1 => 0xdeadbeef, :someint2 => 0xfacefeeb }
|
227
|
+
inner_values = { :byte1 => 1, :byte2 => 2 }
|
228
|
+
|
229
|
+
@instance = @struct.instance
|
230
|
+
|
231
|
+
@populate = lambda do
|
232
|
+
@values.each {|k,v| @instance[k] = v }
|
233
|
+
inner_values.each {|k,v| @instance.inner[k] = v}
|
234
|
+
end
|
235
|
+
|
236
|
+
@pack_format = "NNcc"
|
237
|
+
@rawdata = "\xde\xad\xbe\xef\xfa\xce\xfe\xeb\x01\x02"
|
238
|
+
|
239
|
+
@verify_unpack = lambda do |ret|
|
240
|
+
@values.each {|k,v| ret.__send__(k).should == v }
|
241
|
+
inner_values.each {|k,v| ret.inner.__send__(k).should == v}
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
it_should_behave_like "a structure"
|
246
|
+
it_should_behave_like "a packable type"
|
247
|
+
it_should_behave_like "a groupable type"
|
248
|
+
end
|
249
|
+
|
250
|
+
context "a doubly-nested fixed-length struct" do
|
251
|
+
before :each do
|
252
|
+
inner_inner_struct = Rstruct.struct(:double_nest_inner, :register => true) {
|
253
|
+
char :dchar1
|
254
|
+
char :dchar2
|
255
|
+
} unless Rstruct.default_registry[:double_nest_inner]
|
256
|
+
|
257
|
+
inner_struct = Rstruct.struct(:first_inner, :register => true) {
|
258
|
+
byte :byte1
|
259
|
+
byte :byte2
|
260
|
+
double_nest_inner :double_inner
|
261
|
+
} unless Rstruct.default_registry[:first_inner]
|
262
|
+
|
263
|
+
@struct = Rstruct::Structure.new(:double_nest_struct, :register => false) {
|
264
|
+
uint32be :someint1
|
265
|
+
uint32be :someint2
|
266
|
+
first_inner :inner
|
267
|
+
}
|
268
|
+
|
269
|
+
@values = { :someint1 => 0xdeadbeef, :someint2 => 0xfacefeeb }
|
270
|
+
inner_values = { :byte1 => 1, :byte2 => 2 }
|
271
|
+
double_inner_values = { :dchar1 => 'A', :dchar2 => 'B' }
|
272
|
+
|
273
|
+
@instance = @struct.instance
|
274
|
+
|
275
|
+
@populate = lambda do
|
276
|
+
@values.each {|k,v| @instance[k] = v }
|
277
|
+
inner_values.each {|k,v| @instance.inner[k] = v}
|
278
|
+
double_inner_values.each {|k,v| @instance.inner.double_inner[k] = v}
|
279
|
+
end
|
280
|
+
|
281
|
+
@pack_format = "NNccAA"
|
282
|
+
@rawdata = "\xde\xad\xbe\xef\xfa\xce\xfe\xeb\x01\x02\x41\x42"
|
283
|
+
|
284
|
+
@verify_unpack = lambda do |ret|
|
285
|
+
@values.each {|k,v| ret.__send__(k).should == v }
|
286
|
+
inner_values.each {|k,v| ret.inner.__send__(k).should == v}
|
287
|
+
double_inner_values.each {|k,v| ret.inner.double_inner.__send__(k).should == v}
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
it_should_behave_like "a structure"
|
292
|
+
it_should_behave_like "a packable type"
|
293
|
+
it_should_behave_like "a groupable type"
|
294
|
+
end
|
295
|
+
|
296
|
+
end
|
297
|
+
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
# Applies to types that can be packed.
|
5
|
+
shared_examples_for "a packable type" do
|
6
|
+
context "instances" do
|
7
|
+
it "should write raw data and return a string when no output is specified" do
|
8
|
+
@populate.call()
|
9
|
+
ret = @instance.write()
|
10
|
+
ret.should == @rawdata
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should write raw data to a string object correctly" do
|
14
|
+
s = "test"
|
15
|
+
@populate.call()
|
16
|
+
ret = @instance.write(s)
|
17
|
+
ret.should == @rawdata
|
18
|
+
s.should == "test" << @rawdata
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should write raw data to a StringIO object correctly" do
|
22
|
+
sio = StringIO.new
|
23
|
+
@populate.call()
|
24
|
+
ret = @instance.write(sio)
|
25
|
+
ret.should == @rawdata
|
26
|
+
sio.string.should == @rawdata
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should write raw data to a File IO object correctly" do
|
30
|
+
begin
|
31
|
+
tempf = Tempfile.new('rstruct_test_packing')
|
32
|
+
tempf.write("test")
|
33
|
+
@populate.call()
|
34
|
+
ret = @instance.write(tempf)
|
35
|
+
ret.should == @rawdata.size
|
36
|
+
tempf.rewind
|
37
|
+
tempf.read.should == "test" << @rawdata
|
38
|
+
ensure
|
39
|
+
tempf.close if tempf
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "parsing" do
|
45
|
+
it "should read data from a String object and return a populated container instance" do
|
46
|
+
datcp = @rawdata.dup
|
47
|
+
ret = @struct.read(@rawdata)
|
48
|
+
if @verify_unpack
|
49
|
+
@verify_unpack.call(ret)
|
50
|
+
else
|
51
|
+
@values.each {|k,v| ret.__send__(k).should == v }
|
52
|
+
end
|
53
|
+
@rawdata.should == datcp # ensure the original string was not modified
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should read data from a StringIO object and return a populated container instance" do
|
57
|
+
sio = StringIO.new()
|
58
|
+
sio.write(@rawdata)
|
59
|
+
testend = "#{rand(9999)}testend"
|
60
|
+
sio.write(testend)
|
61
|
+
sio.rewind()
|
62
|
+
ret = @struct.read(sio)
|
63
|
+
if @verify_unpack
|
64
|
+
@verify_unpack.call(ret)
|
65
|
+
else
|
66
|
+
@values.each {|k,v| ret.__send__(k).should == v }
|
67
|
+
end
|
68
|
+
sio.read().should == testend
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should read data from a File object and return a populated container instance" do
|
72
|
+
begin
|
73
|
+
fio = Tempfile.new('rstruct_test_unpacking')
|
74
|
+
fio.write(@rawdata)
|
75
|
+
testend = "#{rand(9999)}testend"
|
76
|
+
fio.write(testend)
|
77
|
+
fio.rewind()
|
78
|
+
ret = @struct.read(fio)
|
79
|
+
if @verify_unpack
|
80
|
+
@verify_unpack.call(ret)
|
81
|
+
else
|
82
|
+
@values.each {|k,v| ret.__send__(k).should == v }
|
83
|
+
end
|
84
|
+
fio.read().should == testend
|
85
|
+
ensure
|
86
|
+
fio.close if fio
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should parse to an instance which can be rewritten correctly" do
|
91
|
+
datcp = @rawdata.dup
|
92
|
+
ret = @struct.read(@rawdata)
|
93
|
+
if @verify_unpack
|
94
|
+
@verify_unpack.call(ret)
|
95
|
+
else
|
96
|
+
@values.each {|k,v| ret.__send__(k).should == v }
|
97
|
+
end
|
98
|
+
repacked = ret.write()
|
99
|
+
repacked.should == @rawdata
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
# Applies to structs, arrays, or other types that encapsulate
|
107
|
+
# more than one field.
|
108
|
+
shared_examples_for "a groupable type" do
|
109
|
+
it "should be groupable" do
|
110
|
+
@struct.should be_groupable
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should have the correct format" do
|
114
|
+
@struct.format.should == @pack_format
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Applies to structures
|
119
|
+
shared_examples_for "a structure" do
|
120
|
+
|
121
|
+
it "should return a value instance with a reference back to itself" do
|
122
|
+
s = @struct.instance()
|
123
|
+
s.rstruct_type.should == @struct
|
124
|
+
end
|
125
|
+
|
126
|
+
context "struct instance" do
|
127
|
+
it "should expose the same fields as the struct they belong to" do
|
128
|
+
@struct.field_names.each {|name| @instance.members.should include(name.to_s) }
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should allow struct field values to be set and retrieved with accessors" do
|
132
|
+
@values.each do |k,v|
|
133
|
+
@instance.__send__(k).should be_nil
|
134
|
+
@instance.__send__("#{k}=", v).should == v
|
135
|
+
@instance.__send__(k).should == v
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should allow field values to be set with arguments to instance creation" do
|
140
|
+
s=@struct.instance(@values)
|
141
|
+
s.rstruct_type.should == @struct
|
142
|
+
@values.each { |k,v| s.__send__(k).should == v }
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should yield itself to a block on instance creation" do
|
146
|
+
i=@struct.instance do |s|
|
147
|
+
@values.each do |k,v|
|
148
|
+
s.__send__(k).should be_nil
|
149
|
+
s.__send__("#{k}=", v).should == v
|
150
|
+
s.__send__(k).should == v
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
@values.each { |k,v| i.__send__(k).should == v }
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|