# # This file is part of ruby-ffi. # For licensing, see LICENSE.SPECS # require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper")) module TestEnumValueRactor extend FFI::Library enum :something, [:one, :two] freeze end describe "Library" do describe ".enum_value" do m = Module.new do extend FFI::Library enum :something, [:one, :two] end it "should return a value for a valid key" do expect(m.enum_value(:one)).to eq(0) expect(m.enum_value(:two)).to eq(1) end it "should return nil for an invalid key" do expect(m.enum_value(:three)).to be nil end it "should be queryable in Ractor", :ractor do res = Ractor.new do TestEnumValueRactor.enum_value(:one) end.take expect( res ).to eq(0) end end describe "#ffi_convention" do it "defaults to :default" do m = Module.new do extend FFI::Library end expect(m.ffi_convention).to eq(:default) end it "should be settable" do m = Module.new do extend FFI::Library end expect(m.ffi_convention).to eq(:default) m.ffi_convention :stdcall expect(m.ffi_convention).to eq(:stdcall) end end if FFI::Platform::OS =~ /windows|cygwin/ && FFI::Platform::ARCH == 'i386' module LibTestStdcall extend FFI::Library ffi_lib TestLibrary::PATH ffi_convention :stdcall class StructUCDP < FFI::Struct layout :a1, :uchar, :a2, :double, :a3, :pointer end attach_function :testStdcallManyParams, [ :pointer, :int8, :int16, :int32, :int64, StructUCDP.by_value, StructUCDP.by_ref, :float, :double ], :void end it "adds stdcall decoration: testStdcallManyParams@64" do s = LibTestStdcall::StructUCDP.new po = FFI::MemoryPointer.new :long LibTestStdcall.testStdcallManyParams po, 1, 2, 3, 4, s, s, 1.0, 2.0 end end if RbConfig::CONFIG['host_os'] =~ /mingw/ # See https://github.com/ffi/ffi/issues/788 it "libc functions shouldn't call an invalid parameter handler" do mod = Module.new do extend FFI::Library ffi_lib 'c' attach_function(:get_osfhandle, :_get_osfhandle, [:int], :intptr_t) end expect( mod.get_osfhandle(42) ).to eq(-1) end end describe "ffi_lib" do it "empty name list should raise error" do expect { Module.new do |m| m.extend FFI::Library ffi_lib end }.to raise_error(LoadError) end it "interprets INPUT() in linker scripts", unless: FFI::Platform.windows? || FFI::Platform.mac? do path = File.dirname(TestLibrary::PATH) file = File.basename(TestLibrary::PATH) script = File.join(path, "ldscript.so") File.write script, "INPUT(#{file});\n" m = Module.new do |m| m.extend FFI::Library ffi_lib script end expect(m.ffi_libraries.map(&:name)).to eq([file]) end it "raises LoadError on garbage in library file" do path = File.dirname(TestLibrary::PATH) garbage = File.join(path, "garbage.so") File.binwrite garbage, "\xDE\xAD\xBE\xEF" expect { Module.new do |m| m.extend FFI::Library ffi_lib garbage end }.to raise_error(LoadError) end end unless RbConfig::CONFIG['target_os'] =~ /mswin|mingw/ it "attach_function with no library specified" do expect { Module.new do |m| m.extend FFI::Library attach_function :getpid, [ ], :uint end }.to raise_error(LoadError) end it "attach_function :getpid from this process" do expect { expect(Module.new do |m| m.extend FFI::Library ffi_lib FFI::Library::CURRENT_PROCESS attach_function :getpid, [ ], :uint end.getpid).to eq(Process.pid) }.not_to raise_error end it "loads library using symbol" do expect { expect(Module.new do |m| m.extend FFI::Library ffi_lib :c attach_function :getpid, [ ], :uint end.getpid).to eq(Process.pid) }.not_to raise_error end it "attach_function :getpid from [ 'c', 'libc.so.6'] " do expect { expect(Module.new do |m| m.extend FFI::Library ffi_lib [ 'c', 'libc.so.6' ] attach_function :getpid, [ ], :uint end.getpid).to eq(Process.pid) }.not_to raise_error end it "attach_function :getpid from [ 'libc.so.6', 'c' ] " do expect { expect(Module.new do |m| m.extend FFI::Library ffi_lib [ 'libc.so.6', 'c' ] attach_function :getpid, [ ], :uint end.getpid).to eq(Process.pid) }.not_to raise_error end it "attach_function :getpid from [ 'libfubar.so.0xdeadbeef', nil, 'c' ] " do expect { expect(Module.new do |m| m.extend FFI::Library ffi_lib [ 'libfubar.so.0xdeadbeef', nil, 'c' ] attach_function :getpid, [ ], :uint end.getpid).to eq(Process.pid) }.not_to raise_error end it "attach_function :getpid from [ 'libfubar.so.0xdeadbeef' ] " do expect { expect(Module.new do |m| m.extend FFI::Library ffi_lib 'libfubar.so.0xdeadbeef' attach_function :getpid, [ ], :uint end.getpid).to eq(Process.pid) }.to raise_error(LoadError) end end it "attach_function :bool_return_true from [ File.expand_path(#{TestLibrary::PATH.inspect}) ]" do mod = Module.new do |m| m.extend FFI::Library ffi_lib File.expand_path(TestLibrary::PATH) attach_function :bool_return_true, [ ], :bool end expect(mod.bool_return_true).to be true end it "can define a foo! function" do mod = Module.new do |m| m.extend FFI::Library ffi_lib File.expand_path(TestLibrary::PATH) attach_function :foo!, :bool_return_true, [], :bool end expect(mod.foo!).to be true end it "can define a foo? function" do mod = Module.new do |m| m.extend FFI::Library ffi_lib File.expand_path(TestLibrary::PATH) attach_function :foo?, :bool_return_true, [], :bool end expect(mod.foo?).to be true end it "can reveal the function type" do skip 'this is not yet implemented on JRuby' if RUBY_ENGINE == 'jruby' skip 'this is not yet implemented on Truffleruby' if RUBY_ENGINE == 'truffleruby' mod = Module.new do |m| m.extend FFI::Library ffi_lib File.expand_path(TestLibrary::PATH) attach_function :bool_return_true, [ :string ], :bool end fun = mod.attached_functions expect(fun.keys).to eq([:bool_return_true]) expect(fun[:bool_return_true].param_types).to eq([FFI::Type::STRING]) expect(fun[:bool_return_true].return_type).to eq(FFI::Type::BOOL) end def gvar_lib(name, type) Module.new do |m| m.extend FFI::Library ffi_lib TestLibrary::PATH attach_variable :gvar, "gvar_#{name}", type attach_function :get, "gvar_#{name}_get", [], type attach_function :set, "gvar_#{name}_set", [ type ], :void end end def gvar_test(name, type, val) lib = gvar_lib(name, type) lib.set(val) expect(lib.gvar).to eq(val) lib.set(0) lib.gvar = val expect(lib.get).to eq(val) end [ 0, 127, -128, -1 ].each do |i| it ":char variable" do gvar_test("s8", :char, i) end end [ 0, 0x7f, 0x80, 0xff ].each do |i| it ":uchar variable" do gvar_test("u8", :uchar, i) end end [ 0, 0x7fff, -0x8000, -1 ].each do |i| it ":short variable" do gvar_test("s16", :short, i) end end [ 0, 0x7fff, 0x8000, 0xffff ].each do |i| it ":ushort variable" do gvar_test("u16", :ushort, i) end end [ 0, 0x7fffffff, -0x80000000, -1 ].each do |i| it ":int variable" do gvar_test("s32", :int, i) end end [ 0, 0x7fffffff, 0x80000000, 0xffffffff ].each do |i| it ":uint variable" do gvar_test("u32", :uint, i) end end [ 0, 0x7fffffffffffffff, -0x8000000000000000, -1 ].each do |i| it ":long_long variable" do gvar_test("s64", :long_long, i) end end [ 0, 0x7fffffffffffffff, 0x8000000000000000, 0xffffffffffffffff ].each do |i| it ":ulong_long variable" do gvar_test("u64", :ulong_long, i) end end if FFI::Platform::LONG_SIZE == 32 [ 0, 0x7fffffff, -0x80000000, -1 ].each do |i| it ":long variable" do gvar_test("long", :long, i) end end [ 0, 0x7fffffff, 0x80000000, 0xffffffff ].each do |i| it ":ulong variable" do gvar_test("ulong", :ulong, i) end end else [ 0, 0x7fffffffffffffff, -0x8000000000000000, -1 ].each do |i| it ":long variable" do gvar_test("long", :long, i) end end [ 0, 0x7fffffffffffffff, 0x8000000000000000, 0xffffffffffffffff ].each do |i| it ":ulong variable" do gvar_test("ulong", :ulong, i) end end end it "Pointer variable" do lib = gvar_lib("pointer", :pointer) val = FFI::MemoryPointer.new :long lib.set(val) expect(lib.gvar).to eq(val) lib.set(nil) lib.gvar = val expect(lib.get).to eq(val) end class GlobalStruct < FFI::Struct layout :data, :long end [ 0, 0x7fffffff, -0x80000000, -1 ].each do |i| it "structure" do lib = Module.new do |m| m.extend FFI::Library ffi_lib TestLibrary::PATH attach_variable :gvar, "gvar_gstruct", GlobalStruct attach_function :get, "gvar_gstruct_get", [], GlobalStruct attach_function :set, "gvar_gstruct_set", [ GlobalStruct ], :void end val = GlobalStruct.new val[:data] = i lib.set(val) expect(lib.gvar[:data]).to eq(i) val[:data] = 0 lib.gvar[:data] = i val = GlobalStruct.new(lib.get) expect(val[:data]).to eq(i) end end it "can reveal its attached global struct based variables" do lib = Module.new do |m| m.extend FFI::Library ffi_lib TestLibrary::PATH attach_variable :gvari, "gvar_gstruct", GlobalStruct end expect(lib.attached_variables).to eq({ gvari: GlobalStruct }) end it "can reveal its attached global variables" do lib = Module.new do |m| m.extend FFI::Library ffi_lib TestLibrary::PATH attach_variable "gvaro", "gvar_u32", :uint32 end expect(lib.attached_variables).to eq({ gvaro: FFI::Type::UINT32 }) end it "should have shareable constants for Ractor", :ractor do res = Ractor.new do [ FFI::Library::LIBC, FFI::Library::CURRENT_PROCESS, FFI::CURRENT_PROCESS, FFI::USE_THIS_PROCESS_AS_LIBRARY, ] end.take expect( res.size ).to be > 0 end end