rstruct 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|