bindata 0.5.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.

Potentially problematic release.


This version of bindata might be problematic. Click here for more details.

@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(File.dirname(__FILE__)) + '/spec_common'
4
+ require 'bindata/array'
5
+ require 'bindata/int'
6
+ require 'bindata/struct'
7
+
8
+ context "Instantiating an Array" do
9
+ specify "should ensure mandatory parameters are supplied" do
10
+ args = {}
11
+ lambda { BinData::Array.new(args) }.should raise_error(ArgumentError)
12
+ args = {:type => :int8}
13
+ lambda { BinData::Array.new(args) }.should raise_error(ArgumentError)
14
+ args = {:initial_length => 3}
15
+ lambda { BinData::Array.new(args) }.should raise_error(ArgumentError)
16
+ end
17
+
18
+ specify "should fail if a given type is unknown" do
19
+ args = {:type => :does_not_exist, :initial_length => 3}
20
+ lambda { BinData::Array.new(args) }.should raise_error(TypeError)
21
+ end
22
+ end
23
+
24
+ context "An Array with several elements" do
25
+ setup do
26
+ type = [:int16le, {:initial_value => lambda { index + 1 }}]
27
+ @data = BinData::Array.new(:type => type, :initial_length => 5)
28
+ end
29
+
30
+ specify "should return a correct snapshot" do
31
+ @data.snapshot.should == [1, 2, 3, 4, 5]
32
+ end
33
+
34
+ specify "should have correct num elements" do
35
+ @data.length.should == 5
36
+ @data.size.should == 5
37
+ end
38
+
39
+ specify "should have correct num_bytes" do
40
+ @data.num_bytes.should == 10
41
+ end
42
+
43
+ specify "should have correct num_bytes for individual elements" do
44
+ @data.num_bytes(0).should == 2
45
+ end
46
+
47
+ specify "should have no field_names" do
48
+ @data.field_names.should be_empty
49
+ end
50
+
51
+ specify "should be able to directly access elements" do
52
+ @data[1] = 8
53
+ @data[1].should == 8
54
+ end
55
+
56
+ specify "should be able to use methods from Enumerable" do
57
+ @data.select { |x| (x % 2) == 0 }.should == [2, 4]
58
+ end
59
+
60
+ specify "should clear" do
61
+ @data[1] = 8
62
+ @data.clear
63
+ @data.collect.should == [1, 2, 3, 4, 5]
64
+ end
65
+
66
+ specify "should clear a single element" do
67
+ @data[1] = 8
68
+ @data.clear(1)
69
+ @data[1].should == 2
70
+ end
71
+
72
+ specify "should be clear upon creation" do
73
+ @data.clear?.should be_true
74
+ end
75
+
76
+ specify "should be clear if all elements are clear" do
77
+ @data[1] = 8
78
+ @data.clear(1)
79
+ @data.clear?.should be_true
80
+ end
81
+
82
+ specify "should test clear status of individual elements" do
83
+ @data[1] = 8
84
+ @data.clear?(0).should be_true
85
+ @data.clear?(1).should be_false
86
+ end
87
+
88
+ specify "should read and write correctly" do
89
+ io = StringIO.new
90
+ @data[1] = 8
91
+ @data.write(io)
92
+
93
+ @data.clear
94
+ io.rewind
95
+ @data[1].should == 2
96
+
97
+ @data.read(io)
98
+ @data[1].should == 8
99
+ end
100
+ end
101
+
102
+ context "An Array containing structs" do
103
+ setup do
104
+ type = [:struct, {:fields => [[:int8, :a,
105
+ {:initial_value => lambda { parent.index }}],
106
+ [:int8, :b]]}]
107
+ @data = BinData::Array.new(:type => type, :initial_length => 5)
108
+ end
109
+
110
+ specify "should access elements, not values" do
111
+ @data[3].a.should == 3
112
+ end
113
+
114
+ specify "should not be able to modify elements" do
115
+ lambda { @data[1] = 3 }.should raise_error(NoMethodError)
116
+ end
117
+
118
+ specify "should interate over each element" do
119
+ @data.collect { |s| s.a }.should == [0, 1, 2, 3, 4]
120
+ end
121
+ end
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(File.dirname(__FILE__)) + '/spec_common'
4
+ require 'bindata/base'
5
+
6
+ context "A data object with mandatory option" do
7
+ context_setup do
8
+ eval <<-END
9
+ class Mandatory < BinData::Base
10
+ mandatory_parameter :p1
11
+ end
12
+ END
13
+ end
14
+ specify "should ensure that those options are present" do
15
+ lambda { Mandatory.new(:p1 => "a") }.should_not raise_error
16
+ end
17
+
18
+ specify "should fail when those options are not present" do
19
+ lambda { Mandatory.new(:p2 => "a") }.should raise_error(ArgumentError)
20
+ end
21
+ end
22
+
23
+ context "A data object with mutually exclusive options" do
24
+ context_setup do
25
+ eval <<-END
26
+ class MutexParam < BinData::Base
27
+ optional_parameters :p1, :p2
28
+ def initialize(params = {}, env = nil)
29
+ super(params, env)
30
+ ensure_mutual_exclusion(:p1, :p2)
31
+ end
32
+ end
33
+ END
34
+ end
35
+
36
+ specify "should not fail when neither of those options is present" do
37
+ lambda { MutexParam.new }.should_not raise_error
38
+ end
39
+
40
+ specify "should not fail when only one of those options is present" do
41
+ lambda { MutexParam.new(:p1 => "a") }.should_not raise_error
42
+ lambda { MutexParam.new(:p2 => "a") }.should_not raise_error
43
+ end
44
+
45
+ specify "should fail when both those options are present" do
46
+ lambda { MutexParam.new(:p1 => "a", :p2 => "b") }.should raise_error(ArgumentError)
47
+ end
48
+ end
49
+
50
+ context "A data object with parameters" do
51
+ context_setup do
52
+ eval <<-END
53
+ class WithParam < BinData::Base
54
+ mandatory_parameter :p1
55
+ optional_parameters :p2, :p3
56
+ public :has_param?, :eval_param, :param
57
+ end
58
+ END
59
+ end
60
+
61
+ specify "should not allow nil parameters" do
62
+ lambda { WithParam.new(:p1 => 1, :p2 => nil) }.should raise_error(ArgumentError)
63
+ end
64
+
65
+ specify "should identify extra parameters" do
66
+ env = mock("env")
67
+ env.should_receive(:params=).with(:p4 => 4, :p5 => 5)
68
+ env.should_receive(:data_object=)
69
+ obj = WithParam.new({:p1 => 1, :p3 => 3, :p4 => 4, :p5 => 5}, env)
70
+ end
71
+
72
+ specify "should only recall mandatory and optional parameters" do
73
+ obj = WithParam.new(:p1 => 1, :p3 => 3, :p4 => 4, :p5 => 5)
74
+ obj.should have_param(:p1)
75
+ obj.should_not have_param(:p2)
76
+ obj.should have_param(:p3)
77
+ obj.should_not have_param(:p4)
78
+ obj.should_not have_param(:p5)
79
+ end
80
+
81
+ specify "should evaluate mandatory and optional parameters" do
82
+ obj = WithParam.new(:p1 => 1, :p3 => lambda {1 + 2}, :p4 => 4, :p5 => 5)
83
+ obj.eval_param(:p1).should == 1
84
+ obj.eval_param(:p2).should_be_nil
85
+ obj.eval_param(:p3).should == 3
86
+ obj.eval_param(:p4).should_be_nil
87
+ obj.eval_param(:p5).should_be_nil
88
+ end
89
+
90
+ specify "should be able to access without evaluating" do
91
+ obj = WithParam.new(:p1 => :asym, :p3 => lambda {1 + 2})
92
+ obj.param(:p1).should == :asym
93
+ obj.param(:p2).should_be_nil
94
+ obj.param(:p3).should respond_to(:arity)
95
+ end
96
+ end
97
+
98
+ context "A data object with :check_offset" do
99
+ context_setup do
100
+ eval <<-END
101
+ class TenByteOffset < BinData::Base
102
+ def do_read(io)
103
+ # advance the io position before checking offset
104
+ io.seek(10, IO::SEEK_CUR)
105
+ super(io)
106
+ end
107
+ def _do_read(io) end
108
+ def done_read; end
109
+ def clear; end
110
+ end
111
+ END
112
+ end
113
+
114
+ specify "should fail if offset is incorrect" do
115
+ io = StringIO.new("12345678901234567890")
116
+ io.seek(2)
117
+ obj = TenByteOffset.new(:check_offset => 8)
118
+ lambda { obj.read(io) }.should raise_error(BinData::ValidityError)
119
+ end
120
+
121
+ specify "should succeed if offset is correct" do
122
+ io = StringIO.new("12345678901234567890")
123
+ io.seek(3)
124
+ obj = TenByteOffset.new(:check_offset => 10)
125
+ lambda { obj.read(io) }.should_not raise_error
126
+ end
127
+
128
+ specify "should fail if :check_offset fails" do
129
+ io = StringIO.new("12345678901234567890")
130
+ io.seek(4)
131
+ obj = TenByteOffset.new(:check_offset => lambda { offset == 11 } )
132
+ lambda { obj.read(io) }.should raise_error(BinData::ValidityError)
133
+ end
134
+
135
+ specify "should succeed if :check_offset succeeds" do
136
+ io = StringIO.new("12345678901234567890")
137
+ io.seek(5)
138
+ obj = TenByteOffset.new(:check_offset => lambda { offset == 10 } )
139
+ lambda { obj.read(io) }.should_not raise_error
140
+ end
141
+ end
142
+
143
+ context "A data object with :readwrite => false" do
144
+ context_setup do
145
+ eval <<-END
146
+ class NoIO < BinData::Base
147
+ def _do_read(io)
148
+ @_do_read = true
149
+ end
150
+ def _write(io)
151
+ @_do_write = true
152
+ end
153
+ def _num_bytes
154
+ 5
155
+ end
156
+ def done_read; end
157
+ def clear; end
158
+ attr_reader :_do_read, :_do_write
159
+ end
160
+ END
161
+ @obj = NoIO.new :readwrite => false
162
+ end
163
+
164
+ specify "should not read" do
165
+ io = StringIO.new("12345678901234567890")
166
+ @obj.read(io)
167
+ @obj._do_read.should_not == true
168
+ end
169
+
170
+ specify "should not write" do
171
+ io = StringIO.new
172
+ @obj.write(io)
173
+ @obj._do_write.should_not == true
174
+ end
175
+
176
+ specify "should have zero num_bytes" do
177
+ @obj.num_bytes.should == 0
178
+ end
179
+ end
180
+
181
+ context "A data object defining a value method" do
182
+ context_setup do
183
+ eval <<-END
184
+ class SingleValueObject < BinData::Base
185
+ def value; end
186
+ end
187
+ END
188
+ end
189
+
190
+ specify "should be a single value object" do
191
+ obj = SingleValueObject.new
192
+ obj.should be_a_single_value
193
+ end
194
+ end
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(File.dirname(__FILE__)) + '/spec_common'
4
+ require 'bindata/choice'
5
+ require 'bindata/int'
6
+ require 'bindata/lazy'
7
+ require 'bindata/struct'
8
+
9
+ context "Instantiating a Choice" do
10
+ specify "should ensure mandatory parameters are supplied" do
11
+ args = {}
12
+ lambda { BinData::Choice.new(args) }.should raise_error(ArgumentError)
13
+ args = {:selection => 1}
14
+ lambda { BinData::Choice.new(args) }.should raise_error(ArgumentError)
15
+ args = {:choices => []}
16
+ lambda { BinData::Choice.new(args) }.should raise_error(ArgumentError)
17
+ end
18
+
19
+ specify "should fail if a given type is unknown" do
20
+ args = {:choices => [:does_not_exist], :selection => 0}
21
+ lambda { BinData::Choice.new(args) }.should raise_error(TypeError)
22
+ end
23
+ end
24
+
25
+ context "A Choice with several choices" do
26
+ setup do
27
+ # allow specifications to select the choice
28
+ @env = BinData::LazyEvalEnv.new
29
+ @env.class.class_eval { attr_accessor :choose }
30
+
31
+ @data = BinData::Choice.new({:choices => [[:int8, {:initial_value => 3}],
32
+ [:int16le, {:initial_value => 5}],
33
+ :int8,
34
+ [:struct,
35
+ {:fields =>[[:int8, :a]]}],
36
+ [:int8, {:initial_value => 7}]],
37
+ :selection => :choose},
38
+ @env)
39
+ end
40
+
41
+ specify "should be able to select the choice" do
42
+ @env.choose = 0
43
+ @data.value.should == 3
44
+ @env.choose = 1
45
+ @data.value.should == 5
46
+ @env.choose = 2
47
+ @data.value.should == 0
48
+ @env.choose = 3
49
+ @data.a.should == 0
50
+ @env.choose = 4
51
+ @data.value.should == 7
52
+ end
53
+
54
+ specify "should not be able to select an invalid choice" do
55
+ @env.choose = -1
56
+ lambda { @data.value }.should raise_error(IndexError)
57
+ @env.choose = 5
58
+ lambda { @data.value }.should raise_error(IndexError)
59
+ end
60
+
61
+ specify "should be able to interact directly with the choice" do
62
+ @env.choose = 0
63
+ @data.value = 17
64
+ @data.value.should == 17
65
+ end
66
+
67
+ specify "should handle missing methods correctly" do
68
+ @env.choose = 0
69
+
70
+ @data.should respond_to(:value)
71
+ @data.should_not respond_to(:does_not_exist)
72
+ lambda { @data.does_not_exist }.should raise_error(NoMethodError)
73
+ end
74
+
75
+ specify "should delegate methods to the selected single choice" do
76
+ @env.choose = 1
77
+ @data.value = 17
78
+
79
+ @data.find_obj_for_name("does_not_exist").should be_nil
80
+ @data.num_bytes.should == 2
81
+ @data.field_names.should be_empty
82
+ @data.value.should == 17
83
+
84
+ io = StringIO.new
85
+ @data.write(io)
86
+
87
+ @data.clear
88
+ @data.clear?.should be_true
89
+ @data.value.should == 5
90
+
91
+ io.rewind
92
+ @data.read(io)
93
+ @data.value.should == 17
94
+
95
+ @data.snapshot.should == 17
96
+ end
97
+
98
+ specify "should delegate methods to the selected complex choice" do
99
+ @env.choose = 3
100
+ @data.find_obj_for_name("a").should_not be_nil
101
+ @data.field_names.should == ["a"]
102
+ @data.num_bytes.should == 1
103
+ end
104
+ end
105
+
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(File.dirname(__FILE__)) + '/spec_common'
4
+ require 'bindata/int'
5
+
6
+ context "All signed integers" do
7
+ specify "should have a sensible value of zero" do
8
+ [BinData::Int8,
9
+ BinData::Int16le,
10
+ BinData::Int16be,
11
+ BinData::Int32le,
12
+ BinData::Int32be].each do |klass|
13
+ klass.new.value.should == 0
14
+ end
15
+ end
16
+
17
+ specify "should pass these tests" do
18
+ [
19
+ [1, true, BinData::Int8],
20
+ [2, false, BinData::Int16le],
21
+ [2, true, BinData::Int16be],
22
+ [4, false, BinData::Int32le],
23
+ [4, true, BinData::Int32be],
24
+ ].each do |nbytes, big_endian, klass|
25
+ gen_int_test_data(nbytes, big_endian).each do |val, clamped_val, str|
26
+ test_read_write(klass, val, clamped_val, str)
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ context "All unsigned integers" do
33
+ specify "should have a sensible value of zero" do
34
+ [BinData::Uint8,
35
+ BinData::Uint16le,
36
+ BinData::Uint16be,
37
+ BinData::Uint32le,
38
+ BinData::Uint32be].each do |klass|
39
+ klass.new.value.should == 0
40
+ end
41
+ end
42
+
43
+ specify "should pass these tests" do
44
+ [
45
+ [1, true, BinData::Uint8],
46
+ [2, false, BinData::Uint16le],
47
+ [2, true, BinData::Uint16be],
48
+ [4, false, BinData::Uint32le],
49
+ [4, true, BinData::Uint32be],
50
+ ].each do |nbytes, big_endian, klass|
51
+ gen_uint_test_data(nbytes, big_endian).each do |val, clamped_val, str|
52
+ test_read_write(klass, val, clamped_val, str)
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ # run read / write tests for the given values
59
+ def test_read_write(klass, val, clamped_val, str)
60
+ # set the data and ensure clamping occurs
61
+ data = klass.new
62
+ data.value = val
63
+ data.value.should == clamped_val
64
+
65
+ # write the data
66
+ io = StringIO.new
67
+ data.write(io)
68
+
69
+ # check that we write the expected byte pattern
70
+ io.rewind
71
+ io.read.should == str
72
+
73
+ # check that we read in the same data that was written
74
+ io.rewind
75
+ data = klass.new
76
+ data.read(io)
77
+ data.value.should == clamped_val
78
+ end
79
+
80
+ # return test data for testing unsigned ints
81
+ def gen_uint_test_data(nbytes, big_endian)
82
+ raise "nbytes too big" if nbytes > 8
83
+ tests = []
84
+
85
+ # test the minimum value
86
+ v = 0
87
+ s = "\x00" * nbytes
88
+ tests.push [v, v, big_endian ? s : s.reverse]
89
+
90
+ # values below minimum should be clamped to minimum
91
+ tests.push [v-1, v, big_endian ? s : s.reverse]
92
+
93
+ # test a value within range
94
+ v = 0x123456789abcdef0 >> ((8-nbytes) * 8)
95
+ s = "\x12\x34\x56\x78\x9a\xbc\xde\xf0".slice(0, nbytes)
96
+ tests.push [v, v, big_endian ? s : s.reverse]
97
+
98
+ # test the maximum value
99
+ v = (1 << (nbytes * 8)) - 1
100
+ s = "\xff" * nbytes
101
+ tests.push [v, v, big_endian ? s : s.reverse]
102
+
103
+ # values above maximum should be clamped to maximum
104
+ tests.push [v+1, v, big_endian ? s : s.reverse]
105
+
106
+ tests
107
+ end
108
+
109
+ # return test data for testing signed ints
110
+ def gen_int_test_data(nbytes, big_endian)
111
+ raise "nbytes too big" if nbytes > 8
112
+ tests = []
113
+
114
+ # test the minimum value
115
+ v = -((1 << (nbytes * 8 - 1)) - 1) -1
116
+ s = "\x80\x00\x00\x00\x00\x00\x00\x00".slice(0, nbytes)
117
+ tests.push [v, v, big_endian ? s : s.reverse]
118
+
119
+ # values below minimum should be clamped to minimum
120
+ tests.push [v-1, v, big_endian ? s : s.reverse]
121
+
122
+ # test a -ve value within range
123
+ v = -1
124
+ s = "\xff" * nbytes
125
+ tests.push [v, v, big_endian ? s : s.reverse]
126
+
127
+ # test a +ve value within range
128
+ v = 0x123456789abcdef0 >> ((8-nbytes) * 8)
129
+ s = "\x12\x34\x56\x78\x9a\xbc\xde\xf0".slice(0, nbytes)
130
+ tests.push [v, v, big_endian ? s : s.reverse]
131
+
132
+ # test the maximum value
133
+ v = (1 << (nbytes * 8 - 1)) - 1
134
+ s = "\x7f" + "\xff" * (nbytes - 1)
135
+ tests.push [v, v, big_endian ? s : s.reverse]
136
+
137
+ # values above maximum should be clamped to maximum
138
+ tests.push [v+1, v, big_endian ? s : s.reverse]
139
+
140
+ tests
141
+ end