From 2519338e0c14c5ab6ba88be2e55be1a3eb2549f6 Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Tue, 7 Jul 2015 16:55:35 -0500 Subject: [PATCH 1/3] Add nested structure support to array/hash methods --- lib/nice-ffi/struct.rb | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/lib/nice-ffi/struct.rb b/lib/nice-ffi/struct.rb index 20de4d3..20b96c6 100644 --- a/lib/nice-ffi/struct.rb +++ b/lib/nice-ffi/struct.rb @@ -368,16 +368,29 @@ def initialize( val, options={} ) def init_from_hash( val ) # :nodoc: - members.each do |member| - self[ member ] = val[ member ] + clear + val.each do |sym, value| + raise NoMethodError unless self.members.member?( sym ) + if self[ sym ].is_a?( FFI::Struct ) + self[ sym ] = self[ sym ].class.new( value ) + else + self[ sym ] = value + end end end private :init_from_hash def init_from_array( val ) # :nodoc: + unless val.length == members.length + raise IndexError, "expected #{members.length} items, got #{val.length}" + end members.each_with_index do |member, i| - self[ member ] = val[ i ] + if self[ member ].is_a?( FFI::Struct ) + self[ member ] = self[ member ].class.new( val[ i ] ) + else + self[ member ] = val[ i ] + end end end private :init_from_array @@ -402,7 +415,13 @@ def init_from_bytes( val ) # :nodoc: # # => [1,2,3,4] # def to_ary - members.collect{ |m| self[m] } + members.collect do |m| + if self[ m ].is_a?( FFI::Struct ) + self[ m ].to_ary + else + self[ m ] + end + end end @@ -422,8 +441,15 @@ def to_bytes # # => {:h=>4, :w=>3, :x=>1, :y=>2} # def to_hash - return {} if members.empty? - Hash[ *(members.collect{ |m| [m, self[m]] }.flatten!) ] + m_list = {} + members.collect do |m| + if self[ m ].is_a?( FFI::Struct ) + m_list[ m ] = self[ m ].to_hash + else + m_list[ m ] = self[ m ] + end + end + return m_list end From 182005a2d0cb58f914021bee8c7634552368e25b Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Tue, 7 Jul 2015 16:56:49 -0500 Subject: [PATCH 2/3] Add spec for hash import/export --- spec/struct_spec.rb | 145 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 spec/struct_spec.rb diff --git a/spec/struct_spec.rb b/spec/struct_spec.rb new file mode 100644 index 0000000..bfba0bc --- /dev/null +++ b/spec/struct_spec.rb @@ -0,0 +1,145 @@ +require 'spec_helper.rb' + +# Define some sample structs for test purposes +class Inner < NiceFFI::Struct + layout :one, :uint8, + :two, :uint32, + :three, :uint16 +end +class Outer < NiceFFI::Struct + layout :header, :uint8, + :nested, Inner, + :footer, :uint16 +end + +describe NiceFFI::Struct do + + describe "to_hash" do + before :all do + @x = Inner.new [ 3, 2, 1 ] + @h = @x.to_hash + end + + it "generates the correct number of elements" do + expect( @h.length ).to eql @x.members.length + end + + it "generates the correct sequence of keys" do + expect( @h.keys ).to match_array @x.members + end + + it "generates the correct values" do + @h.each {|k,v| + expect( v ).to eql @x[k] + } + end + + it "recursively encodes nested structures" do + o = Outer.new "" + h = o.to_hash + expect( h[:nested] ).to be_a( Hash ) + end + end + + describe "#init_from_hash" do + before :all do + @hash = { :one => 1, :two => 2, :three => 3 } + end + + it "loads the correct values" do + x = Inner.new @hash + + expect( x[:one] ).to eql 1 + expect( x[:two] ).to eql 2 + expect( x[:three] ).to eql 3 + end + + it "only alters the specified fields" do + x = Inner.new @hash.reject{|x| x == :two} + + expect( x[:one] ).to eql 1 + expect( x[:two] ).to eql 0 + expect( x[:three] ).to eql 3 + end + + it "rejects unknown fields" do + expect { + h = @hash.merge( { :dummy => 0 } ) + x = Inner.new h + }.to raise_error( NoMethodError ) + end + + it "can be used to re-constitute a nested structure" do + x = Outer.new [rand(250), [rand(250), rand(250), rand(250)], rand(250)] + + y = Outer.new x.to_hash + + expect( y[:header] ).to eql x[:header] + expect( y[:footer] ).to eql x[:footer] + expect( y[:nested][:one] ).to eql x[:nested][:one] + expect( y[:nested][:two] ).to eql x[:nested][:two] + expect( y[:nested][:three] ).to eql x[:nested][:three] + end + end + + describe "to_ary" do + before :all do + @init_data = [ 1, 2, 3 ] + @x = Inner.new @init_data + @a = @x.to_ary + end + + it "generates the correct number of elements" do + expect( @a.length ).to eql @x.members.length + end + + it "generates the correct sequence of values" do + expect( @a ).to match_array @init_data + end + + it "recursively encodes nested structures" do + o = Outer.new "" + a = o.to_ary + expect( a[1] ).to be_a( ::Array ) + end + end + + describe "#init_from_ary" do + before :all do + @init_data = [ 1, 2, 3 ] + end + + it "loads the correct values" do + x = Inner.new @init_data + + expect( x[:one] ).to eql 1 + expect( x[:two] ).to eql 2 + expect( x[:three] ).to eql 3 + end + + it "errors if array is too small" do + expect { + x = Inner.new @init_data[0...-1] + }.to raise_error( IndexError ) + end + + it "errors if array is too large" do + expect { + x = Inner.new @init_data + [5] + }.to raise_error( IndexError ) + end + + it "can be used to re-constitute a nested structure" do + x = Outer.new [rand(250), [rand(250), rand(250), rand(250)], rand(250)] + + y = Outer.new x.to_ary + + expect( y[:header] ).to eql x[:header] + expect( y[:footer] ).to eql x[:footer] + expect( y[:nested][:one] ).to eql x[:nested][:one] + expect( y[:nested][:two] ).to eql x[:nested][:two] + expect( y[:nested][:three] ).to eql x[:nested][:three] + end + end + +end From 0059dcee2c151f5a58ee2215e863a67bb5e55855 Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Tue, 7 Jul 2015 18:21:33 -0500 Subject: [PATCH 3/3] Add more test cases for nested structures --- spec/struct_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spec/struct_spec.rb b/spec/struct_spec.rb index bfba0bc..4d2ee91 100644 --- a/spec/struct_spec.rb +++ b/spec/struct_spec.rb @@ -14,6 +14,13 @@ class Outer < NiceFFI::Struct describe NiceFFI::Struct do + it "generates accessors for nested structures" do + x = Outer.new "" + expect( x ).to respond_to :nested + expect( x.nested ).to be_a( Inner ) + expect( x.nested ).to respond_to :one + end + describe "to_hash" do before :all do @x = Inner.new [ 3, 2, 1 ]