jbangert-bindata 1.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.
Files changed (71) hide show
  1. data/.gitignore +1 -0
  2. data/BSDL +22 -0
  3. data/COPYING +52 -0
  4. data/ChangeLog.rdoc +204 -0
  5. data/Gemfile +2 -0
  6. data/INSTALL +11 -0
  7. data/NEWS.rdoc +164 -0
  8. data/README.md +54 -0
  9. data/Rakefile +13 -0
  10. data/bindata.gemspec +31 -0
  11. data/doc/manual.haml +407 -0
  12. data/doc/manual.md +1649 -0
  13. data/examples/NBT.txt +149 -0
  14. data/examples/gzip.rb +161 -0
  15. data/examples/ip_address.rb +22 -0
  16. data/examples/list.rb +124 -0
  17. data/examples/nbt.rb +178 -0
  18. data/lib/bindata.rb +33 -0
  19. data/lib/bindata/alignment.rb +83 -0
  20. data/lib/bindata/array.rb +335 -0
  21. data/lib/bindata/base.rb +388 -0
  22. data/lib/bindata/base_primitive.rb +214 -0
  23. data/lib/bindata/bits.rb +87 -0
  24. data/lib/bindata/choice.rb +216 -0
  25. data/lib/bindata/count_bytes_remaining.rb +35 -0
  26. data/lib/bindata/deprecated.rb +50 -0
  27. data/lib/bindata/dsl.rb +312 -0
  28. data/lib/bindata/float.rb +80 -0
  29. data/lib/bindata/int.rb +184 -0
  30. data/lib/bindata/io.rb +274 -0
  31. data/lib/bindata/lazy.rb +105 -0
  32. data/lib/bindata/offset.rb +91 -0
  33. data/lib/bindata/params.rb +135 -0
  34. data/lib/bindata/primitive.rb +135 -0
  35. data/lib/bindata/record.rb +110 -0
  36. data/lib/bindata/registry.rb +92 -0
  37. data/lib/bindata/rest.rb +35 -0
  38. data/lib/bindata/sanitize.rb +290 -0
  39. data/lib/bindata/skip.rb +48 -0
  40. data/lib/bindata/string.rb +145 -0
  41. data/lib/bindata/stringz.rb +96 -0
  42. data/lib/bindata/struct.rb +388 -0
  43. data/lib/bindata/trace.rb +94 -0
  44. data/lib/bindata/version.rb +3 -0
  45. data/setup.rb +1585 -0
  46. data/spec/alignment_spec.rb +61 -0
  47. data/spec/array_spec.rb +331 -0
  48. data/spec/base_primitive_spec.rb +238 -0
  49. data/spec/base_spec.rb +376 -0
  50. data/spec/bits_spec.rb +163 -0
  51. data/spec/choice_spec.rb +263 -0
  52. data/spec/count_bytes_remaining_spec.rb +38 -0
  53. data/spec/deprecated_spec.rb +31 -0
  54. data/spec/example.rb +21 -0
  55. data/spec/float_spec.rb +37 -0
  56. data/spec/int_spec.rb +216 -0
  57. data/spec/io_spec.rb +352 -0
  58. data/spec/lazy_spec.rb +217 -0
  59. data/spec/primitive_spec.rb +202 -0
  60. data/spec/record_spec.rb +530 -0
  61. data/spec/registry_spec.rb +108 -0
  62. data/spec/rest_spec.rb +26 -0
  63. data/spec/skip_spec.rb +27 -0
  64. data/spec/spec_common.rb +58 -0
  65. data/spec/string_spec.rb +300 -0
  66. data/spec/stringz_spec.rb +118 -0
  67. data/spec/struct_spec.rb +350 -0
  68. data/spec/system_spec.rb +380 -0
  69. data/tasks/manual.rake +36 -0
  70. data/tasks/rspec.rake +17 -0
  71. metadata +208 -0
@@ -0,0 +1,376 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(File.join(File.dirname(__FILE__), "spec_common"))
4
+ require 'bindata/base'
5
+
6
+ class BaseStub < BinData::Base
7
+ # Override to avoid NotImplemented errors
8
+ def clear; end
9
+ def clear?; end
10
+ def assign(x); @data = x; end
11
+ def snapshot; @data; end
12
+ def do_read(io) end
13
+ def do_write(io) end
14
+ def do_num_bytes; end
15
+ end
16
+
17
+ describe BinData::Base, "all subclasses" do
18
+ class SubClassOfBase < BinData::Base
19
+ expose_methods_for_testing
20
+ end
21
+
22
+ subject { SubClassOfBase.new }
23
+
24
+ it "raises errors on unimplemented methods" do
25
+ expect { subject.clear }.to raise_error(NotImplementedError)
26
+ expect { subject.clear? }.to raise_error(NotImplementedError)
27
+ expect { subject.assign(nil) }.to raise_error(NotImplementedError)
28
+ expect { subject.snapshot }.to raise_error(NotImplementedError)
29
+ expect { subject.do_read(nil) }.to raise_error(NotImplementedError)
30
+ expect { subject.do_write(nil) }.to raise_error(NotImplementedError)
31
+ expect { subject.do_num_bytes }.to raise_error(NotImplementedError)
32
+ end
33
+ end
34
+
35
+ describe BinData::Base, "with parameters" do
36
+ it "raises error when parameter name is invalid" do
37
+ expect {
38
+ class InvalidParameterNameBase < BinData::Base
39
+ optional_parameter :eval # i.e. Kernel#eval
40
+ end
41
+ }.to raise_error(NameError)
42
+ end
43
+
44
+ it "raises an error when parameter has nil value" do
45
+ expect { BaseStub.new(:a => nil) }.to raise_error(ArgumentError)
46
+ end
47
+
48
+ it "converts parameter keys to symbols" do
49
+ subject = BaseStub.new('a' => 3)
50
+ subject.should have_parameter(:a)
51
+ end
52
+ end
53
+
54
+ describe BinData::Base, "with mandatory parameters" do
55
+ class MandatoryBase < BaseStub
56
+ mandatory_parameter :p1
57
+ mandatory_parameter :p2
58
+ end
59
+
60
+ it "ensures that all mandatory parameters are present" do
61
+ params = {:p1 => "a", :p2 => "b" }
62
+ expect { MandatoryBase.new(params) }.not_to raise_error
63
+ end
64
+
65
+ it "fails when only some mandatory parameters are present" do
66
+ params = {:p1 => "a", :xx => "b" }
67
+ expect { MandatoryBase.new(params) }.to raise_error(ArgumentError)
68
+ end
69
+
70
+ it "fails when no mandatory parameters are present" do
71
+ expect { MandatoryBase.new() }.to raise_error(ArgumentError)
72
+ end
73
+ end
74
+
75
+ describe BinData::Base, "with default parameters" do
76
+ class DefaultBase < BaseStub
77
+ default_parameter :p1 => "a"
78
+ end
79
+
80
+ it "uses default parameters when not specified" do
81
+ subject = DefaultBase.new
82
+ subject.eval_parameter(:p1).should == "a"
83
+ end
84
+
85
+ it "can override default parameters" do
86
+ subject = DefaultBase.new(:p1 => "b")
87
+ subject.eval_parameter(:p1).should == "b"
88
+ end
89
+ end
90
+
91
+ describe BinData::Base, "with mutually exclusive parameters" do
92
+ class MutexParamBase < BaseStub
93
+ optional_parameters :p1, :p2
94
+ mutually_exclusive_parameters :p1, :p2
95
+ end
96
+
97
+ it "does not fail when neither of those parameters are present" do
98
+ expect { MutexParamBase.new }.not_to raise_error
99
+ end
100
+
101
+ it "does not fail when only one of those parameters is present" do
102
+ expect { MutexParamBase.new(:p1 => "a") }.not_to raise_error
103
+ expect { MutexParamBase.new(:p2 => "b") }.not_to raise_error
104
+ end
105
+
106
+ it "fails when both those parameters are present" do
107
+ expect { MutexParamBase.new(:p1 => "a", :p2 => "b") }.to raise_error(ArgumentError)
108
+ end
109
+ end
110
+
111
+ describe BinData::Base, "with multiple parameters" do
112
+ class WithParamBase < BaseStub
113
+ mandatory_parameter :p1
114
+ default_parameter :p2 => 2
115
+ optional_parameter :p3
116
+ end
117
+
118
+ it "identifies internally accepted parameters" do
119
+ accepted = WithParamBase.accepted_parameters.all
120
+ accepted.should include(:p1)
121
+ accepted.should include(:p2)
122
+ accepted.should include(:p3)
123
+ accepted.should_not include(:xx)
124
+ end
125
+
126
+ context "examining parameters" do
127
+ subject {
128
+ params = {:p1 => 1, :p3 => :xx, :p4 => lambda { 4 }}
129
+ WithParamBase.new(params)
130
+ }
131
+
132
+ it "evaluates parameters" do
133
+ subject.eval_parameter(:p1).should == 1
134
+ subject.eval_parameter(:p2).should == 2
135
+ expect { subject.eval_parameter(:p3) }.to raise_error(NoMethodError)
136
+ subject.eval_parameter(:p4).should == 4
137
+ end
138
+
139
+ it "gets parameters without evaluating" do
140
+ subject.get_parameter(:p1).should == 1
141
+ subject.get_parameter(:p2).should == 2
142
+ subject.get_parameter(:p3).should == :xx
143
+ subject.get_parameter(:p4).should respond_to(:arity)
144
+ end
145
+
146
+ it "has parameters" do
147
+ subject.should have_parameter(:p1)
148
+ subject.should have_parameter(:p2)
149
+ subject.should have_parameter(:p3)
150
+ subject.should have_parameter(:p4)
151
+ end
152
+ end
153
+ end
154
+
155
+ describe BinData::Base, "when initializing" do
156
+ class BaseInit < BaseStub
157
+ class << self
158
+ attr_accessor :calls
159
+ def recorded_calls(&block)
160
+ self.calls = []
161
+ block.call
162
+ calls
163
+ end
164
+ end
165
+
166
+ def initialize_instance
167
+ self.class.calls << :initialize_instance
168
+ end
169
+
170
+ def initialize_shared_instance
171
+ self.class.calls << :initialize_shared_instance
172
+ end
173
+ end
174
+
175
+ it "calls both #initialize_xxx methods" do
176
+ BaseInit.recorded_calls {
177
+ BaseInit.new
178
+ }.should == [:initialize_shared_instance, :initialize_instance]
179
+ end
180
+
181
+ context "as a factory" do
182
+ subject { BaseInit.new(:check_offset => 1) }
183
+
184
+ describe "#new" do
185
+ it "calls #initialize_instance" do
186
+ obj = subject
187
+
188
+ BaseInit.recorded_calls {
189
+ obj.new
190
+ }.should == [:initialize_instance]
191
+ end
192
+
193
+ it "copies parameters" do
194
+ obj = subject.new
195
+ obj.eval_parameter(:check_offset).should == 1
196
+ end
197
+
198
+ it "performs action for :check_offset" do
199
+ obj = subject.new
200
+ expect {
201
+ obj.read("abc")
202
+ }.to raise_error(BinData::ValidityError)
203
+ end
204
+
205
+ it "assigns value" do
206
+ obj = subject.new(3)
207
+ obj.snapshot.should == 3
208
+ end
209
+
210
+ it "sets parent" do
211
+ obj = subject.new(3, "p")
212
+ obj.parent.should == "p"
213
+ end
214
+ end
215
+ end
216
+ end
217
+
218
+ describe BinData::Base, "as black box" do
219
+ context "class methods" do
220
+ it "returns bindata_name" do
221
+ BaseStub.bindata_name.should == "base_stub"
222
+ end
223
+
224
+ it "instantiates self for ::read" do
225
+ BaseStub.read("").class.should == BaseStub
226
+ end
227
+ end
228
+
229
+ it "accesses parent" do
230
+ parent = BaseStub.new
231
+ child = BaseStub.new(nil, parent)
232
+ child.parent.should == parent
233
+ end
234
+
235
+ subject { BaseStub.new }
236
+
237
+ it "returns self for #read" do
238
+ subject.read("").should == subject
239
+ end
240
+
241
+ it "returns self for #write" do
242
+ subject.write("").should == subject
243
+ end
244
+
245
+ it "forwards #inspect to snapshot" do
246
+ subject.stub(:snapshot).and_return([1, 2, 3])
247
+ subject.inspect.should == subject.snapshot.inspect
248
+ end
249
+
250
+ it "forwards #to_s to snapshot" do
251
+ subject.stub(:snapshot).and_return([1, 2, 3])
252
+ subject.to_s.should == subject.snapshot.to_s
253
+ end
254
+
255
+ it "pretty prints object as snapshot" do
256
+ subject.stub(:snapshot).and_return([1, 2, 3])
257
+ actual_io = StringIO.new
258
+ expected_io = StringIO.new
259
+
260
+ require 'pp'
261
+ PP.pp(subject, actual_io)
262
+ PP.pp(subject.snapshot, expected_io)
263
+
264
+ actual_io.value.should == expected_io.value
265
+ end
266
+
267
+ it "writes the same as to_binary_s" do
268
+ class WriteToSBase < BaseStub
269
+ def do_write(io) io.writebytes("abc"); end
270
+ end
271
+
272
+ subject = WriteToSBase.new
273
+ io = StringIO.new
274
+ subject.write(io)
275
+ io.value.should == subject.to_binary_s
276
+ end
277
+ end
278
+
279
+ describe BinData::Base, "as white box" do
280
+ subject { BaseStub.new }
281
+
282
+ it "forwards read to do_read" do
283
+ subject.should_receive(:clear).ordered
284
+ subject.should_receive(:do_read).ordered
285
+ subject.read(nil)
286
+ end
287
+
288
+ it "forwards write to do_write" do
289
+ subject.should_receive(:do_write)
290
+ subject.write(nil)
291
+ end
292
+
293
+ it "forwards num_bytes to do_num_bytes" do
294
+ subject.should_receive(:do_num_bytes).and_return(42)
295
+ subject.num_bytes.should == 42
296
+ end
297
+
298
+ it "rounds up fractional num_bytes" do
299
+ subject.should_receive(:do_num_bytes).and_return(42.1)
300
+ subject.num_bytes.should == 43
301
+ end
302
+ end
303
+
304
+ describe BinData::Base, "checking offsets" do
305
+ class TenByteOffsetBase < BaseStub
306
+ def self.create(params)
307
+ obj = self.new
308
+ obj.initialize_child(params)
309
+ obj
310
+ end
311
+
312
+ def initialize_child(params)
313
+ @child = BaseStub.new(params, self)
314
+ end
315
+
316
+ def do_read(io)
317
+ io.seekbytes(10)
318
+ @child.do_read(io)
319
+ end
320
+ end
321
+
322
+ let(:io) { StringIO.new("12345678901234567890") }
323
+
324
+ context "with :check_offset" do
325
+ it "fails when offset is incorrect" do
326
+ io.seek(2)
327
+ subject = TenByteOffsetBase.create(:check_offset => 8)
328
+ expect { subject.read(io) }.to raise_error(BinData::ValidityError)
329
+ end
330
+
331
+ it "succeeds when offset is correct" do
332
+ io.seek(3)
333
+ subject = TenByteOffsetBase.create(:check_offset => 10)
334
+ expect { subject.read(io) }.not_to raise_error
335
+ end
336
+
337
+ it "fails when :check_offset fails" do
338
+ io.seek(4)
339
+ subject = TenByteOffsetBase.create(:check_offset => lambda { offset == 11 } )
340
+ expect { subject.read(io) }.to raise_error(BinData::ValidityError)
341
+ end
342
+
343
+ it "succeeds when :check_offset succeeds" do
344
+ io.seek(5)
345
+ subject = TenByteOffsetBase.create(:check_offset => lambda { offset == 10 } )
346
+ expect { subject.read(io) }.not_to raise_error
347
+ end
348
+ end
349
+
350
+ context "with :adjust_offset" do
351
+ it "is mutually exclusive with :check_offset" do
352
+ params = { :check_offset => 8, :adjust_offset => 8 }
353
+ expect { TenByteOffsetBase.create(params) }.to raise_error(ArgumentError)
354
+ end
355
+
356
+ it "adjust offset when incorrect" do
357
+ io.seek(2)
358
+ subject = TenByteOffsetBase.create(:adjust_offset => 13)
359
+ subject.read(io)
360
+ io.pos.should == (2 + 13)
361
+ end
362
+
363
+ it "succeeds when offset is correct" do
364
+ io.seek(3)
365
+ subject = TenByteOffsetBase.create(:adjust_offset => 10)
366
+ expect { subject.read(io) }.not_to raise_error
367
+ io.pos.should == (3 + 10)
368
+ end
369
+
370
+ it "fails if cannot adjust offset" do
371
+ io.seek(4)
372
+ subject = TenByteOffsetBase.create(:adjust_offset => -5)
373
+ expect { subject.read(io) }.to raise_error(BinData::ValidityError)
374
+ end
375
+ end
376
+ end
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(File.join(File.dirname(__FILE__), "spec_common"))
4
+ require 'bindata/bits'
5
+
6
+ describe "Bits of size 1" do
7
+ let(:bit_classes) { [BinData::Bit1, BinData::Bit1le] }
8
+
9
+ it "accept true as value" do
10
+ bit_classes.each do |bit_class|
11
+ subject = bit_class.new
12
+ subject.assign(true)
13
+ subject.should == 1
14
+ end
15
+ end
16
+
17
+ it "accept false as value" do
18
+ bit_classes.each do |bit_class|
19
+ subject = bit_class.new
20
+ subject.assign(false)
21
+ subject.should == 0
22
+ end
23
+ end
24
+
25
+ it "accept nil as value" do
26
+ bit_classes.each do |bit_class|
27
+ subject = bit_class.new
28
+ subject.assign(nil)
29
+ subject.should == 0
30
+ end
31
+ end
32
+ end
33
+
34
+ shared_examples "All bitfields" do
35
+
36
+ it "have a sensible value of zero" do
37
+ all_classes do |bit_class|
38
+ bit_class.new.should be_zero
39
+ end
40
+ end
41
+
42
+ it "avoid underflow" do
43
+ all_classes do |bit_class|
44
+ subject = bit_class.new
45
+
46
+ subject.assign(min_value - 1)
47
+ subject.should == min_value
48
+ end
49
+ end
50
+
51
+ it "avoid overflow" do
52
+ all_classes do |bit_class|
53
+ subject = bit_class.new
54
+
55
+ subject.assign(max_value + 1)
56
+ subject.should == max_value
57
+ end
58
+ end
59
+
60
+ it "assign values" do
61
+ all_classes do |bit_class|
62
+ some_values_within_range.each do |val|
63
+ subject = bit_class.new
64
+ subject.assign(val)
65
+
66
+ subject.should == val
67
+ end
68
+ end
69
+ end
70
+
71
+ it "assign values from other bit objects" do
72
+ all_classes do |bit_class|
73
+ some_values_within_range.each do |val|
74
+ subject = bit_class.new
75
+ subject.assign(bit_class.new(val))
76
+
77
+ subject.should == val
78
+ end
79
+ end
80
+ end
81
+
82
+ it "symmetrically #read and #write" do
83
+ all_classes do |bit_class|
84
+ some_values_within_range.each do |val|
85
+ subject = bit_class.new
86
+ subject.assign(val)
87
+
88
+ subject.value_read_from_written.should == subject
89
+ end
90
+ end
91
+ end
92
+
93
+ def all_classes(&block)
94
+ @bits.each_pair do |bit_class, nbits|
95
+ @nbits = nbits
96
+ yield bit_class
97
+ end
98
+ end
99
+
100
+ def min_value
101
+ 0
102
+ end
103
+
104
+ def max_value
105
+ (1 << @nbits) - 1
106
+ end
107
+
108
+ def some_values_within_range
109
+ lo = min_value + 1
110
+ mid = (min_value + max_value) / 2
111
+ hi = max_value - 1
112
+
113
+ [lo, mid, hi].select { |val| value_within_range?(val) }
114
+ end
115
+
116
+ def value_within_range?(val)
117
+ (min_value .. max_value).include?(val)
118
+ end
119
+ end
120
+
121
+ def generate_bit_classes_to_test(endian)
122
+ bits = {}
123
+ (1 .. 50).each do |nbits|
124
+ name = (endian == :big) ? "Bit#{nbits}" : "Bit#{nbits}le"
125
+ bit_class = BinData.const_get(name)
126
+ bits[bit_class] = nbits
127
+ end
128
+ bits
129
+ end
130
+
131
+ describe "Big endian bitfields" do
132
+ include_examples "All bitfields"
133
+
134
+ before(:all) do
135
+ @bits = generate_bit_classes_to_test(:big)
136
+ end
137
+
138
+ it "read big endian values" do
139
+ @bits.each_pair do |bit_class, nbits|
140
+ nbytes = (nbits + 7) / 8
141
+ str = [0b1000_0000].pack("C") + "\000" * (nbytes - 1)
142
+
143
+ bit_class.read(str).should == 1 << (nbits - 1)
144
+ end
145
+ end
146
+ end
147
+
148
+ describe "Little endian bitfields" do
149
+ include_examples "All bitfields"
150
+
151
+ before(:all) do
152
+ @bits = generate_bit_classes_to_test(:little)
153
+ end
154
+
155
+ it "read little endian values" do
156
+ @bits.each_pair do |bit_class, nbits|
157
+ nbytes = (nbits + 7) / 8
158
+ str = [0b0000_0001].pack("C") + "\000" * (nbytes - 1)
159
+
160
+ bit_class.read(str).should == 1
161
+ end
162
+ end
163
+ end