Tag unstable CGI specs.
[rbx.git] / spec / ffi / attach_foreign_spec.rb
blobc9e0930845a248a1fde0034a9f865ce52b52b610
1 require File.dirname(__FILE__) + '/../spec_helper'
4 module FFISpecs
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.
12   #
13   # If your system has none of these libraries or they reside in a
14   # different location, add that information here.
15   #
16   # TODO: Add more paths/libraries.
18   KNOWN_LIBS = [{:name    => 'libcurl',
19                  :symbol  => 'curl_version',
20                  :args    => [],
21                  :ret     => :string,
22                  :check   => lambda {|ret| ret.kind_of? String and ret =~ /curl/ }
23                 }
24                ]
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
30   def self.known_lib()
31     @lib ||=  KNOWN_LIBS.find {|lib|
32                 LIB_PATHS.find {|path|
33                   LIB_EXTS.find {|ext|
34                     candidate = "#{path}/#{lib[:name]}.#{ext}"
35                     if File.file? candidate
36                       lib[:path] = candidate
37                       lib[:full_name] = "#{lib[:name]}.#{ext}"
38                       true
39                     end
40                   }
41                 }
42               }
43   end
44 end
46 describe "Function signature parsing in Module#attach_foreign" do
47   # TBD
48 end
50 describe "Module#attach_foreign" do
51   before :each do
52     @mod = Module.new
53   end
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)
57   end
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)
61   end
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)
65   end
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
71   end
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
76   end
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
81   end
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
86   end
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
91   end
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
98   end
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)
102   end
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
111   end
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
120   end
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
131   end
133   it "creates the method even if the return type given is incorrect for the function" do
134     # The real declaration is
135     #
136     #   char* ffi_tzname(int dst);
137     @mod.attach_foreign :double, 'ffi_tzname', [:int]
138     @mod.methods.include?('ffi_tzname').should == true
139   end
141   it "creates the method even if any of the argument types given are incorrect for the function" do
142     # The real declaration is
143     #
144     #   char* ffi_tzname(int dst);
145     @mod.attach_foreign :string, 'ffi_tzname', [:string]
146     @mod.methods.include?('ffi_tzname').should == true
147   end
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
159   before :each do
160     @mod = Module.new
161     @known = FFISpecs.known_lib
162   end
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
167   end
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
172   end
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
178   end
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)
185   end
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
190   end
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)
195   end
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)
200   end
204 describe "Method created by attaching to a library function" do
205   before :each do
206     @mod = Module.new
207     @mod.attach_foreign :string, 'ffi_tzname', [:int]
208   end
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
214   end
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
219   end
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)
223   end
226 # Not sure what the correct behaviour should be anyway.
227 # These actually do not horribly crash (except for the
228 # last one.)
229 quarantine! do
230   describe "Method created by attaching to a library function but with an incorrect return value type" do
231     before :each do
232       @mod = Module.new
234       # The real declaration is
235       #
236       #   char* ffi_tzname(int dst);
237       @mod.attach_foreign :double, 'ffi_tzname', [:int]
238     end
240     it "raises error when called and does not crash interpreter" do
241       fail
242     end
243   end
245   describe "Method created by attaching to a library function but with incorrect argument type(s)" do
246     before :each do
247       @mod = Module.new
248       @mod.attach_foreign :string, 'ffi_tzname', [:string]
249     end
251     it "raises error when called with given argument type and does not crash interpreter" do
252       fail
253     end
255     it "raises error when called with the correct not-given argument type and does not crash interpreter" do
256       fail
257     end
258   end