1 require File.dirname(__FILE__) + '/../spec_helper'
5 LIB_EXTS = [Rubinius::LIBSUFFIX]
6 LIB_EXTS << Rubinius::ALT_LIBSUFFIX rescue nil
8 # To deterministically verify behaviour, we use relatively common
9 # libraries and the likely paths in which they would reside. This
10 # allows checking that an external library can be loaded and its
11 # symbols become accessible.
13 # If your system has none of these libraries or they reside in a
14 # different location, add that information here.
16 # TODO: Add more paths/libraries.
18 KNOWN_LIBS = [{:name => 'libcurl',
19 :symbol => 'curl_version',
22 :check => lambda {|ret| ret.kind_of? String and ret =~ /curl/ }
26 LIB_PATHS = %w[/usr/lib /usr/local/lib /opt/lib /opt/local/lib]
27 LIB_NAMES = KNOWN_LIBS.map {|l| l[:name] }
29 # Find a library that we can reliably check against
31 @lib ||= KNOWN_LIBS.find {|lib|
32 LIB_PATHS.find {|path|
34 candidate = "#{path}/#{lib[:name]}.#{ext}"
35 if File.file? candidate
36 lib[:path] = candidate
37 lib[:full_name] = "#{lib[:name]}.#{ext}"
46 describe "Function signature parsing in Module#attach_foreign" do
50 describe "Module#attach_foreign" do
55 it "raises FFI::SignatureError if function signature is given with more than 6 arguments" do
56 lambda { @mod.attach_foreign(:int, 'foo', ([:int] * 7))}.should raise_error(FFI::SignatureError)
59 it "raises FFI::TypeError if any of the argument types is invalid (not in FFI::TypeDefs)" do
60 lambda { @mod.attach_foreign :int, 'foo', [:bar] }.should raise_error(FFI::TypeError)
63 it "raises FFI::TypeError if the return type is invalid (not in FFI::TypeDefs)" do
64 lambda { @mod.attach_foreign :foo, 'foo', [:int] }.should raise_error(FFI::TypeError)
67 it "accepts a hash of additional options as its fourth argument" do
68 lambda { @mod.attach_foreign :string, 'ffi_tzname', [:int] }.should_not raise_error
69 lambda { @mod.attach_foreign :string, 'ffi_tzname', [:int], {} }.should_not raise_error
70 lambda { @mod.attach_foreign :string, 'ffi_tzname', [:int], :foo => :bar }.should_not raise_error
73 it "creates class method named as the function if function found in current process" do
74 @mod.attach_foreign :string, 'ffi_tzname', [:int]
75 @mod.methods.include?('ffi_tzname').should == true
78 it "accepts function name given as a Symbol" do
79 @mod.attach_foreign :string, :ffi_tzname, [:int]
80 @mod.methods.include?('ffi_tzname').should == true
83 it "accepts function name given as a String" do
84 @mod.attach_foreign :string, 'ffi_tzname', [:int]
85 @mod.methods.include?('ffi_tzname').should == true
88 it "creates the method for functions in current process that were linked in, not part of Rubinius' codebase" do
89 @mod.attach_foreign :int, 'atoi', [:string]
90 @mod.methods.include?('atoi').should == true
93 it "uses :as key of fourth argument as the method name if present" do
94 @mod.attach_foreign :string, 'ffi_tzname', [:int], :as => 'my_tzname'
96 @mod.methods.include?('ffi_tzname').should == false
97 @mod.methods.include?('my_tzname').should == true
100 it "raises FFI::NotFoundError if function is not found in current process when no library name given" do
101 lambda { @mod.attach_foreign :string, '__nOnEs_uChF_unC___', [] }.should raise_error(FFI::NotFoundError)
104 it "overwrites an existing method with same name" do
105 def @mod.ffi_tzname; 'old'; end
106 old = @mod.metaclass.method_table[:ffi_tzname]
108 @mod.attach_foreign :string, 'ffi_tzname', [:int]
110 @mod.metaclass.method_table[:ffi_tzname].eql?(old).should == false
113 it "overwrites an existing method with same name when name given in :as key of fourth argument" do
114 def @mod.my_tzname; 'old'; end
115 old = @mod.metaclass.method_table[:my_tzname]
117 @mod.attach_foreign :string, 'ffi_tzname', [:int], :as => 'my_tzname'
119 @mod.metaclass.method_table[:my_tzname].eql?(old).should == false
122 it "allows attaching the same function multiple times to different names" do
123 @mod.attach_foreign :string, 'ffi_tzname', [:int]
124 @mod.attach_foreign :string, 'ffi_tzname', [:int], :as => 'my_tzname'
125 @mod.attach_foreign :string, 'ffi_tzname', [:int], :as => 'my_other_tzname'
128 @mod.methods.include?('ffi_tzname').should == true
129 @mod.methods.include?('my_tzname').should == true
130 @mod.methods.include?('my_other_tzname').should == true
133 it "creates the method even if the return type given is incorrect for the function" do
134 # The real declaration is
136 # char* ffi_tzname(int dst);
137 @mod.attach_foreign :double, 'ffi_tzname', [:int]
138 @mod.methods.include?('ffi_tzname').should == true
141 it "creates the method even if any of the argument types given are incorrect for the function" do
142 # The real declaration is
144 # char* ffi_tzname(int dst);
145 @mod.attach_foreign :string, 'ffi_tzname', [:string]
146 @mod.methods.include?('ffi_tzname').should == true
151 # Attempt to verify correct behaviour of external library access
152 # through some known, common libraries. If needed, add more libs
153 # at the top of the file.
154 describe "Module#attach_foreign additionally, when given library name in :from key of fourth parameter" do
156 # Ungraceful but there are no better solutions right now
157 raise "No known libraries found to verify FFI behaviour.\nPlease see #{__FILE__} to fix." unless FFISpecs.known_lib
161 @known = FFISpecs.known_lib
164 it "creates method for function found in library named in the :from key of option hash" do
165 @mod.attach_foreign @known[:ret], @known[:symbol], @known[:args], :from => @known[:name]
166 @mod.methods.include?(@known[:symbol].to_s).should == true
169 it "accepts library name without a file extension" do
170 @mod.attach_foreign @known[:ret], @known[:symbol], @known[:args], :from => @known[:name]
171 @mod.methods.include?(@known[:symbol].to_s).should == true
174 it "accepts library name with valid shared lib extension included" do
175 @mod.attach_foreign @known[:ret], @known[:symbol], @known[:args],
176 :from => @known[:full_name]
177 @mod.methods.include?(@known[:symbol].to_s).should == true
180 it "does not strip a non-shared lib file extension" do
181 l = lambda { @mod.attach_foreign @known[:ret], @known[:symbol], @known[:args], :from => "#{@known[:name]}.ext" }
182 ll = lambda { @mod.attach_foreign @known[:ret], @known[:symbol], @known[:args], :from => "#{@known[:full_name]}.ext" }
183 l.should raise_error(FFI::NotFoundError)
184 ll.should raise_error(FFI::NotFoundError)
187 it "accepts library name given as full path" do
188 @mod.attach_foreign @known[:ret], @known[:symbol], @known[:args], :from => @known[:path]
189 @mod.methods.include?(@known[:symbol].to_s).should == true
192 it "raises FFI::NotFoundError if function does exist in current process but not in the library given" do
193 l = lambda { @mod.attach_foreign :object, 'ffi_function_create', [:state, :object, :object, :object, :object], :from => @known[:name] }
194 l.should raise_error(FFI::NotFoundError)
197 it "raises FFI::NotFoundError if function is not found in named library nor exists in the current process" do
198 l = lambda { @mod.attach_foreign :string, 'rbx__nOnEs_uChF_unC___', [], :from => @known[:name] }
199 l.should raise_error(FFI::NotFoundError)
204 describe "Method created by attaching to a library function" do
207 @mod.attach_foreign :string, 'ffi_tzname', [:int]
210 it "returns a Ruby object representation of the function's return value when called" do
211 obj = @mod.ffi_tzname 0
212 obj.class.should == String
213 obj.length.should == 3
216 it "returns Ruby object of the return value of a function that was linked to Rubinius' codebase at buildtime" do
217 @mod.attach_foreign :int, 'atoi', [:string]
218 @mod.atoi('345').should == 345
221 it "raises TypeError if given an argument whose type does not correspond to type given in signature" do
222 lambda { obj = @mod.ffi_tzname 'hello' }.should raise_error(TypeError)
226 # Not sure what the correct behaviour should be anyway.
227 # These actually do not horribly crash (except for the
230 describe "Method created by attaching to a library function but with an incorrect return value type" do
234 # The real declaration is
236 # char* ffi_tzname(int dst);
237 @mod.attach_foreign :double, 'ffi_tzname', [:int]
240 it "raises error when called and does not crash interpreter" do
245 describe "Method created by attaching to a library function but with incorrect argument type(s)" do
248 @mod.attach_foreign :string, 'ffi_tzname', [:string]
251 it "raises error when called with given argument type and does not crash interpreter" do
255 it "raises error when called with the correct not-given argument type and does not crash interpreter" do