spec/base_spec.rb in bindata-0.8.1 vs spec/base_spec.rb in bindata-0.9.0
- old
+ new
@@ -1,223 +1,345 @@
#!/usr/bin/env ruby
require File.expand_path(File.dirname(__FILE__)) + '/spec_common'
require 'bindata/base'
-describe "A data object with mandatory option" do
+class BaseStub < BinData::Base
+ def clear; end
+ def _do_read(io) end
+ def done_read; end
+ def _write(io) end
+ def _num_bytes; end
+ def snapshot; end
+ def single_value?; end
+ def field_names; end
+end
+
+describe BinData::Base, "with mandatory parameters" do
before(:all) do
eval <<-END
- class Mandatory < BinData::Base
+ class MandatoryBase < BinData::Base
mandatory_parameter :p1
end
END
end
- it "should ensure that those options are present" do
- lambda { Mandatory.new(:p1 => "a") }.should_not raise_error
+
+ it "should ensure that those parameters are present" do
+ lambda { MandatoryBase.new(:p1 => "a") }.should_not raise_error
end
- it "should fail when those options are not present" do
- lambda { Mandatory.new(:p2 => "a") }.should raise_error(ArgumentError)
+ it "should fail when those parameters are not present" do
+ lambda { MandatoryBase.new(:p2 => "a") }.should raise_error(ArgumentError)
end
end
-describe "A data object with mutually exclusive options" do
+describe BinData::Base, "with default parameters" do
before(:all) do
eval <<-END
- class MutexParam < BinData::Base
+ class DefaultBase < BinData::Base
+ default_parameter :p1 => "a"
+ public :has_param?, :param
+ end
+ END
+ end
+
+ it "should set default parameters if they are not specified" do
+ obj = DefaultBase.new
+ obj.should have_param(:p1)
+ obj.param(:p1).should == "a"
+ end
+
+ it "should be able to override default parameters" do
+ obj = DefaultBase.new(:p1 => "b")
+ obj.should have_param(:p1)
+ obj.param(:p1).should == "b"
+ end
+end
+
+describe BinData::Base, "with mutually exclusive parameters" do
+ before(:all) do
+ eval <<-END
+ class MutexParamBase < BinData::Base
optional_parameters :p1, :p2
- def initialize(params = {}, env = nil)
- super(params, env)
- ensure_mutual_exclusion(:p1, :p2)
- end
+ mutually_exclusive_parameters :p1, :p2
end
END
end
- it "should not fail when neither of those options is present" do
- lambda { MutexParam.new }.should_not raise_error
+ it "should not fail when neither of those parameters is present" do
+ lambda { MutexParamBase.new }.should_not raise_error
end
- it "should not fail when only one of those options is present" do
- lambda { MutexParam.new(:p1 => "a") }.should_not raise_error
- lambda { MutexParam.new(:p2 => "a") }.should_not raise_error
+ it "should not fail when only one of those parameters is present" do
+ lambda { MutexParamBase.new(:p1 => "a") }.should_not raise_error
+ lambda { MutexParamBase.new(:p2 => "a") }.should_not raise_error
end
- it "should fail when both those options are present" do
- lambda { MutexParam.new(:p1 => "a", :p2 => "b") }.should raise_error(ArgumentError)
+ it "should fail when both those parameters are present" do
+ lambda { MutexParamBase.new(:p1 => "a", :p2 => "b") }.should raise_error(ArgumentError)
end
end
-describe "A data object with parameters" do
+describe BinData::Base, "with multiple parameters" do
before(:all) do
eval <<-END
- class WithParam < BinData::Base
+ class WithParamBase < BinData::Base
mandatory_parameter :p1
- optional_parameters :p2, :p3
+ optional_parameter :p2
+ default_parameter :p3 => '3'
public :has_param?, :eval_param, :param
end
END
end
- it "should not allow nil parameters" do
- lambda { WithParam.new(:p1 => 1, :p2 => nil) }.should raise_error(ArgumentError)
+ it "should not allow parameters with nil values" do
+ lambda { WithParamBase.new(:p1 => 1, :p2 => nil) }.should raise_error(ArgumentError)
end
it "should identify extra parameters" do
env = mock("env")
env.should_receive(:params=).with(:p4 => 4, :p5 => 5)
env.should_receive(:data_object=)
- obj = WithParam.new({:p1 => 1, :p3 => 3, :p4 => 4, :p5 => 5}, env)
+ obj = WithParamBase.new({:p1 => 1, :p3 => 3, :p4 => 4, :p5 => 5}, env)
end
- it "should only recall mandatory and optional parameters" do
- obj = WithParam.new(:p1 => 1, :p3 => 3, :p4 => 4, :p5 => 5)
+ it "should only recall mandatory, default and optional parameters" do
+ obj = WithParamBase.new(:p1 => 1, :p3 => 3, :p4 => 4, :p5 => 5)
obj.should have_param(:p1)
obj.should_not have_param(:p2)
obj.should have_param(:p3)
obj.should_not have_param(:p4)
obj.should_not have_param(:p5)
end
- it "should evaluate mandatory and optional parameters" do
- obj = WithParam.new(:p1 => 1, :p3 => lambda {1 + 2}, :p4 => 4, :p5 => 5)
- obj.eval_param(:p1).should eql(1)
+ it "should evaluate mandatory, default and optional parameters" do
+ obj = WithParamBase.new(:p1 => 1, :p3 => lambda {1 + 2}, :p4 => 4, :p5 => 5)
+ obj.eval_param(:p1).should == 1
obj.eval_param(:p2).should be_nil
- obj.eval_param(:p3).should eql(3)
+ obj.eval_param(:p3).should == 3
obj.eval_param(:p4).should be_nil
obj.eval_param(:p5).should be_nil
end
it "should be able to access without evaluating" do
- obj = WithParam.new(:p1 => :asym, :p3 => lambda {1 + 2})
- obj.param(:p1).should eql(:asym)
+ obj = WithParamBase.new(:p1 => :asym, :p3 => lambda {1 + 2})
+ obj.param(:p1).should == :asym
obj.param(:p2).should be_nil
obj.param(:p3).should respond_to(:arity)
end
it "should identify accepted parameters" do
- obj = WithParam.new(:p1 => 1, :p3 => 3, :p4 => 4, :p5 => 5)
- obj.accepted_parameters.should include(:p1)
- obj.accepted_parameters.should include(:p2)
- obj.accepted_parameters.should include(:p3)
- obj.accepted_parameters.should_not include(:p4)
+ accepted_parameters = WithParamBase.accepted_parameters
+ accepted_parameters.should include(:p1)
+ accepted_parameters.should include(:p2)
+ accepted_parameters.should include(:p3)
+ accepted_parameters.should_not include(:p4)
end
end
-describe "A data object with :check_offset" do
+describe BinData::Base, "with :check_offset" do
before(:all) do
eval <<-END
- class TenByteOffset < BinData::Base
+ class TenByteOffsetBase < BaseStub
def do_read(io)
# advance the io position before checking offset
io.seek(10, IO::SEEK_CUR)
super(io)
end
- def _do_read(io) end
- def done_read; end
- def clear; end
end
END
end
it "should fail if offset is incorrect" do
io = StringIO.new("12345678901234567890")
io.seek(2)
- obj = TenByteOffset.new(:check_offset => 8)
+ obj = TenByteOffsetBase.new(:check_offset => 8)
lambda { obj.read(io) }.should raise_error(BinData::ValidityError)
end
it "should succeed if offset is correct" do
io = StringIO.new("12345678901234567890")
io.seek(3)
- obj = TenByteOffset.new(:check_offset => 10)
+ obj = TenByteOffsetBase.new(:check_offset => 10)
lambda { obj.read(io) }.should_not raise_error
end
it "should fail if :check_offset fails" do
io = StringIO.new("12345678901234567890")
io.seek(4)
- obj = TenByteOffset.new(:check_offset => lambda { offset == 11 } )
+ obj = TenByteOffsetBase.new(:check_offset => lambda { offset == 11 } )
lambda { obj.read(io) }.should raise_error(BinData::ValidityError)
end
it "should succeed if :check_offset succeeds" do
io = StringIO.new("12345678901234567890")
io.seek(5)
- obj = TenByteOffset.new(:check_offset => lambda { offset == 10 } )
+ obj = TenByteOffsetBase.new(:check_offset => lambda { offset == 10 } )
lambda { obj.read(io) }.should_not raise_error
end
end
-describe "A data object with :readwrite => false" do
+describe BinData::Base, "with :adjust_offset" do
before(:all) do
eval <<-END
- class NoIO < BinData::Base
- def _do_read(io)
- @_do_read = true
+ class TenByteAdjustingOffsetBase < BaseStub
+ def do_read(io)
+ # advance the io position before checking offset
+ io.seek(10, IO::SEEK_CUR)
+ super(io)
end
- def _write(io)
- @_do_write = true
- end
- def _num_bytes
- 5
- end
- def done_read; end
- def clear; end
- attr_reader :_do_read, :_do_write
end
END
- @obj = NoIO.new :readwrite => false
end
- it "should not read" do
+ it "should be mutually exclusive with :check_offset" do
+ params = { :check_offset => 8, :adjust_offset => 8 }
+ lambda { TenByteAdjustingOffsetBase.new(params) }.should raise_error(ArgumentError)
+ end
+
+ it "should adjust if offset is incorrect" do
io = StringIO.new("12345678901234567890")
- @obj.read(io)
- @obj._do_read.should_not eql(true)
+ io.seek(2)
+ obj = TenByteAdjustingOffsetBase.new(:adjust_offset => 13)
+ obj.read(io)
+ io.pos.should == (2 + 13)
end
- it "should not write" do
- io = StringIO.new
- @obj.write(io)
- @obj._do_write.should_not eql(true)
+ it "should succeed if offset is correct" do
+ io = StringIO.new("12345678901234567890")
+ io.seek(3)
+ obj = TenByteAdjustingOffsetBase.new(:adjust_offset => 10)
+ lambda { obj.read(io) }.should_not raise_error
+ io.pos.should == (3 + 10)
end
- it "should have zero num_bytes" do
- @obj.num_bytes.should eql(0)
+ it "should fail if cannot adjust offset" do
+ io = StringIO.new("12345678901234567890")
+ io.seek(3)
+ obj = TenByteAdjustingOffsetBase.new(:adjust_offset => -4)
+ lambda { obj.read(io) }.should raise_error(BinData::ValidityError)
end
end
-describe "A data object defining a value method" do
+describe BinData::Base, "with :readwrite => false" do
before(:all) do
eval <<-END
- class SingleValueObject < BinData::Base
- def value; end
+ class NoIOBase < BaseStub
+ attr_accessor :mock
+ def _do_read(io) mock._do_read(io); end
+ def _write(io) mock._write(io); end
+ def _num_bytes; mock._num_bytes; end
end
END
end
- it "should be a single value object" do
- obj = SingleValueObject.new
- obj.should be_a_single_value
+ before(:each) do
+ @obj = NoIOBase.new :readwrite => false
+ @obj.mock = mock('mock')
end
+
+ it "should not read" do
+ io = StringIO.new("12345678901234567890")
+ @obj.mock.should_not_receive(:_do_read)
+ @obj.read(io)
+ end
+
+ it "should not write" do
+ io = StringIO.new
+ @obj.mock.should_not_receive(:_do_write)
+ @obj.write(io)
+ end
+
+ it "should have zero num_bytes" do
+ @obj.mock.should_not_receive(:_num_bytes)
+ @obj.num_bytes.should be_zero
+ end
end
-describe "A subclass of Base" do
+describe BinData::Base, "when subclassing" do
before(:all) do
eval <<-END
class SubClassOfBase < BinData::Base
public :_do_read, :_write, :_num_bytes
end
END
+ end
+
+ before(:each) do
@obj = SubClassOfBase.new
end
it "should raise errors on unimplemented methods" do
+ lambda {
+ SubClassOfBase.all_possible_field_names(nil)
+ }.should raise_error(NotImplementedError)
lambda { @obj.clear }.should raise_error(NotImplementedError)
- lambda { @obj.done_read }.should raise_error(NotImplementedError)
- lambda { @obj.snapshot }.should raise_error(NotImplementedError)
- lambda { @obj.field_names }.should raise_error(NotImplementedError)
lambda { @obj._do_read(nil) }.should raise_error(NotImplementedError)
+ lambda { @obj.done_read }.should raise_error(NotImplementedError)
lambda { @obj._write(nil) }.should raise_error(NotImplementedError)
lambda { @obj._num_bytes }.should raise_error(NotImplementedError)
+ lambda { @obj.snapshot }.should raise_error(NotImplementedError)
+ lambda { @obj.single_value? }.should raise_error(NotImplementedError)
+ lambda { @obj.field_names }.should raise_error(NotImplementedError)
+ end
+end
+
+describe BinData::Base, "when subclassing as a single value" do
+ before(:all) do
+ eval <<-END
+ class SingleValueSubClassOfBase < BaseStub
+ def single_value?; true; end
+ def value; 123; end
+ end
+ END
+ end
+
+ before(:each) do
+ @obj = SingleValueSubClassOfBase.new
+ end
+
+ it "should return value when reading" do
+ SingleValueSubClassOfBase.read("").should == 123
+ end
+end
+
+describe BinData::Base, "when subclassing as multi values" do
+ before(:all) do
+ eval <<-END
+ class MultiValueSubClassOfBase < BaseStub
+ def single_value?; false; end
+ def value; 123; end
+ end
+ END
+ end
+
+ it "should return self when reading" do
+ obj = MultiValueSubClassOfBase.read("")
+ obj.class.should == MultiValueSubClassOfBase
+ end
+end
+
+describe BinData::Base do
+ before(:all) do
+ eval <<-END
+ class InstanceOfBase < BaseStub
+ def snapshot; 123; end
+ def _write(io); io.write('456'); end
+ end
+ END
+ end
+
+ before(:each) do
+ @obj = InstanceOfBase.new
+ end
+
+ it "should forward #inspect to snapshot" do
+ @obj.inspect.should == 123.inspect
+ end
+
+ it "should write the same as to_s" do
+ io = StringIO.new
+ @obj.write(io)
+ io.rewind
+ io.read.should == @obj.to_s
end
end