rstruct 0.1.0

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