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.
@@ -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
+