spec/int_spec.rb in bindata-0.9.3 vs spec/int_spec.rb in bindata-0.10.0

- old
+ new

@@ -1,163 +1,253 @@ #!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__)) + '/spec_common' -require 'bindata/int' +require 'bindata' -describe "All signed integers" do +share_examples_for "All Integers" do + + it "should have correct num_bytes" do + all_classes do |int_class| + int_class.new.num_bytes.should == @nbytes + end + end + it "should have a sensible value of zero" do - [BinData::Int8, - BinData::Int16le, - BinData::Int16be, - BinData::Int32le, - BinData::Int32be, - BinData::Int64le, - BinData::Int64be, - BinData::Int128le, - BinData::Int128be].each do |klass| - klass.new.value.should be_zero + all_classes do |int_class| + int_class.new.value.should be_zero end end - it "should pass these tests" do - [ - [ 1, true, BinData::Int8], - [ 2, false, BinData::Int16le], - [ 2, true, BinData::Int16be], - [ 4, false, BinData::Int32le], - [ 4, true, BinData::Int32be], - [ 8, false, BinData::Int64le], - [ 8, true, BinData::Int64be], - [16, false, BinData::Int128le], - [16, true, BinData::Int128be], - ].each do |nbytes, big_endian, klass| - gen_int_test_data(nbytes, big_endian).each do |val, clamped_val, str| - test_read_write(klass, val, clamped_val, str) - end + it "should avoid underflow" do + all_classes do |int_class| + obj = int_class.new + obj.value = min_value - 1 + + obj.value.should == min_value end end -end -describe "All unsigned integers" do - it "should have a sensible value of zero" do - [BinData::Uint8, - BinData::Uint16le, - BinData::Uint16be, - BinData::Uint32le, - BinData::Uint32be, - BinData::Uint64le, - BinData::Uint64be, - BinData::Uint128le, - BinData::Uint128be].each do |klass| - klass.new.value.should be_zero + it "should avoid overflow" do + all_classes do |int_class| + obj = int_class.new + obj.value = max_value + 1 + + obj.value.should == max_value end end - it "should pass these tests" do - [ - [ 1, true, BinData::Uint8], - [ 2, false, BinData::Uint16le], - [ 2, true, BinData::Uint16be], - [ 4, false, BinData::Uint32le], - [ 4, true, BinData::Uint32be], - [ 8, false, BinData::Uint64le], - [ 8, true, BinData::Uint64be], - [16, false, BinData::Uint128le], - [16, true, BinData::Uint128be], - ].each do |nbytes, big_endian, klass| - gen_uint_test_data(nbytes, big_endian).each do |val, clamped_val, str| - test_read_write(klass, val, clamped_val, str) + it "should assign values" do + all_classes do |int_class| + obj = int_class.new + test_int = gen_test_int + obj.assign(test_int) + obj.value.should == test_int + end + end + + it "should assign values from other int objects" do + all_classes do |int_class| + src = int_class.new + src.assign(gen_test_int) + + obj = int_class.new + obj.assign(src) + obj.value.should == src.value + end + end + + it "should symmetrically read and write a +ve number" do + all_classes do |int_class| + obj = int_class.new + obj.value = gen_test_int + + str = obj.to_binary_s + int_class.read(str).should == obj.value + end + end + + it "should symmetrically read and write a -ve number" do + all_classes do |int_class| + if @signed + obj = int_class.new + obj.value = -gen_test_int + + str = obj.to_binary_s + int_class.read(str).should == obj.value end end end -end -# run read / write tests for the given values -def test_read_write(klass, val, clamped_val, str) - # set the data and ensure clamping occurs - data = klass.new - data.value = val - data.value.should == clamped_val - - # write the data - io = StringIO.new - data.write(io) + it "should convert a +ve number to string" do + all_classes do |int_class| + val = gen_test_int - # check that we write the expected byte pattern - io.rewind - io.read.should == str + obj = int_class.new + obj.value = val - # check that we read in the same data that was written - io.rewind - data = klass.new - data.read(io) - data.value.should == clamped_val -end + obj.to_binary_s.should == int_to_binary_str(val) + end + end -# return test data for testing unsigned ints -def gen_uint_test_data(nbytes, big_endian) - raise "nbytes too large" if nbytes > 16 - tests = [] + it "should convert a -ve number to string" do + all_classes do |int_class| + if @signed + val = -gen_test_int - max_value = (1 << (nbytes * 8)) - 1 - min_value = 0 + obj = int_class.new + obj.value = val - # test the minimum value - v = min_value - s = "\x00" * nbytes - tests.push [v, v, big_endian ? s : s.reverse] + obj.to_binary_s.should == int_to_binary_str(val) + end + end + end - # values below minimum should be clamped to minimum - tests.push [v-1, v, big_endian ? s : s.reverse] + def all_classes(&block) + @ints.each_pair do |int_class, nbytes| + @nbytes = nbytes + yield int_class + end + end - # test a value within range - v = 0x123456789abcdef0123456789abcdef0 >> ((16-nbytes) * 8) - s = "\x12\x34\x56\x78\x9a\xbc\xde\xf0\x12\x34\x56\x78\x9a\xbc\xde\xf0".slice(0, nbytes) - tests.push [v, v, big_endian ? s : s.reverse] + def min_value + if @signed + -max_value - 1 + else + 0 + end + end - # test the maximum value - v = max_value - s = "\xff" * nbytes - tests.push [v, v, big_endian ? s : s.reverse] + def max_value + if @signed + (1 << (@nbytes * 8 - 1)) - 1 + else + (1 << (@nbytes * 8)) - 1 + end + end - # values above maximum should be clamped to maximum - tests.push [v+1, v, big_endian ? s : s.reverse] + def gen_test_int + # resulting int is guaranteed to be +ve for signed or unsigned integers + (0 ... @nbytes).inject(0) { |val, i| (val << 8) | ((val + 0x11) % 0x100) } + end - tests + def int_to_binary_str(val) + str = "" + v = val & ((1 << (@nbytes * 8)) - 1) + @nbytes.times do + str.concat(v & 0xff) + v >>= 8 + end + (@endian == :little) ? str : str.reverse + end end -# return test data for testing signed ints -def gen_int_test_data(nbytes, big_endian) - raise "nbytes too large" if nbytes > 16 - tests = [] +describe "All signed big endian integers" do + it_should_behave_like "All Integers" - max_value = (1 << (nbytes * 8 - 1)) - 1 - min_value = -max_value - 1 + before(:all) do + BinData::Integer.define_class(24, :big, :signed) + BinData::Integer.define_class(48, :big, :signed) + BinData::Integer.define_class(96, :big, :signed) + @endian = :big + @signed = true + @ints = { + BinData::Int8 => 1, + BinData::Int8be => 1, + BinData::Int16be => 2, + BinData::Int24be => 3, + BinData::Int32be => 4, + BinData::Int48be => 6, + BinData::Int64be => 8, + BinData::Int96be => 12, + BinData::Int128be => 16, + } + end +end - # test the minimum value - v = min_value - s = "\x80" + "\x00" * (nbytes - 1) - tests.push [v, v, big_endian ? s : s.reverse] +describe "All unsigned big endian integers" do + it_should_behave_like "All Integers" - # values below minimum should be clamped to minimum - tests.push [v-1, v, big_endian ? s : s.reverse] + before(:all) do + BinData::Integer.define_class(24, :big, :unsigned) + BinData::Integer.define_class(48, :big, :unsigned) + BinData::Integer.define_class(96, :big, :unsigned) + @endian = :big + @signed = false + @ints = { + BinData::Uint8 => 1, + BinData::Uint8be => 1, + BinData::Uint16be => 2, + BinData::Uint24be => 3, + BinData::Uint32be => 4, + BinData::Uint48be => 6, + BinData::Uint64be => 8, + BinData::Uint96be => 12, + BinData::Uint128be => 16, + } + end +end - # test a -ve value within range - v = -1 - s = "\xff" * nbytes - tests.push [v, v, big_endian ? s : s.reverse] +describe "All signed little endian integers" do + it_should_behave_like "All Integers" - # test a +ve value within range - v = 0x123456789abcdef0123456789abcdef0 >> ((16-nbytes) * 8) - s = "\x12\x34\x56\x78\x9a\xbc\xde\xf0\x12\x34\x56\x78\x9a\xbc\xde\xf0".slice(0, nbytes) - tests.push [v, v, big_endian ? s : s.reverse] + before(:all) do + BinData::Integer.define_class(24, :little, :signed) + BinData::Integer.define_class(48, :little, :signed) + BinData::Integer.define_class(96, :little, :signed) + @endian = :little + @signed = true + @ints = { + BinData::Int8 => 1, + BinData::Int8le => 1, + BinData::Int16le => 2, + BinData::Int24le => 3, + BinData::Int32le => 4, + BinData::Int48le => 6, + BinData::Int64le => 8, + BinData::Int96le => 12, + BinData::Int128le => 16, + } + end +end - # test the maximum value - v = max_value - s = "\x7f" + "\xff" * (nbytes - 1) - tests.push [v, v, big_endian ? s : s.reverse] +describe "All unsigned little endian integers" do + it_should_behave_like "All Integers" - # values above maximum should be clamped to maximum - tests.push [v+1, v, big_endian ? s : s.reverse] + before(:all) do + BinData::Integer.define_class(24, :little, :unsigned) + BinData::Integer.define_class(48, :little, :unsigned) + BinData::Integer.define_class(96, :little, :unsigned) + @endian = :little + @signed = false + @ints = { + BinData::Uint8 => 1, + BinData::Uint8le => 1, + BinData::Uint16le => 2, + BinData::Uint24le => 3, + BinData::Uint32le => 4, + BinData::Uint48le => 6, + BinData::Uint64le => 8, + BinData::Uint96le => 12, + BinData::Uint128le => 16, + } + end +end - tests +describe "Custom defined integers" do + it "should fail unless bits are a multiple of 8" do + lambda { + BinData::Integer.define_class(7, :little, :unsigned) + }.should raise_error + + lambda { + BinData::Integer.define_class(7, :big, :unsigned) + }.should raise_error + + lambda { + BinData::Integer.define_class(7, :little, :signed) + }.should raise_error + + lambda { + BinData::Integer.define_class(7, :big, :signed) + }.should raise_error + end end