1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
|
#
# This file is part of ruby-ffi.
# For licensing, see LICENSE.SPECS
#
require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper"))
describe FFI::Function do
module LibTest
extend FFI::Library
ffi_lib TestLibrary::PATH
attach_function :testFunctionAdd, [:int, :int, :pointer], :int
end
before do
@libtest = FFI::DynamicLibrary.open(TestLibrary::PATH,
FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL)
end
it 'is initialized with a signature and a block' do
fn = FFI::Function.new(:int, []) { 5 }
expect(fn.call).to eql 5
end
context 'when called with a block' do
it 'creates a thread for dispatching callbacks and sets its name' do
skip 'this is MRI-specific' if RUBY_ENGINE == 'truffleruby' || RUBY_ENGINE == 'jruby'
FFI::Function.new(:int, []) { 5 } # Trigger initialization
expect(Thread.list.map(&:name)).to include('FFI Callback Dispatcher')
end
end
it 'raises an error when passing a wrong signature' do
expect { FFI::Function.new([], :int).new { } }.to raise_error TypeError
end
it 'returns a native pointer' do
expect(FFI::Function.new(:int, []) { }).to be_a_kind_of FFI::Pointer
end
it 'can be used as callback from C passing to it a block' do
function_add = FFI::Function.new(:int, [:int, :int]) { |a, b| a + b }
expect(LibTest.testFunctionAdd(10, 10, function_add)).to eq(20)
end
it 'can be used as callback from C passing to it a Proc object' do
function_add = FFI::Function.new(:int, [:int, :int], Proc.new { |a, b| a + b })
expect(LibTest.testFunctionAdd(10, 10, function_add)).to eq(20)
end
def adder(a, b)
a + b
end
it "can be made shareable for Ractor", :ractor do
add = FFI::Function.new(:int, [:int, :int], &method(:adder))
Ractor.make_shareable(add)
res = Ractor.new(add) do |add2|
LibTest.testFunctionAdd(10, 10, add2)
end.take
expect( res ).to eq(20)
end
it "should be usable with Ractor", :ractor do
res = Ractor.new do
function_add = FFI::Function.new(:int, [:int, :int]) { |a, b| a + b }
LibTest.testFunctionAdd(10, 10, function_add)
end.take
expect( res ).to eq(20)
end
it 'can be used to wrap an existing function pointer' do
expect(FFI::Function.new(:int, [:int, :int], @libtest.find_function('testAdd')).call(10, 10)).to eq(20)
end
it 'can be attached to a module' do
module Foo; end
fp = FFI::Function.new(:int, [:int, :int], @libtest.find_function('testAdd'))
fp.attach(Foo, 'add')
expect(Foo.add(10, 10)).to eq(20)
end
it 'can be attached to two modules' do
module Foo1; end
module Foo2; end
fp = FFI::Function.new(:int, [:int, :int], @libtest.find_function('testAdd'))
fp.attach(Foo1, 'add')
fp.attach(Foo2, 'add')
expect(Foo1.add(11, 11)).to eq(22)
expect(Foo2.add(12, 12)).to eq(24)
end
it 'can be used to extend an object' do
fp = FFI::Function.new(:int, [:int, :int], @libtest.find_function('testAdd'))
foo = Object.new
class << foo
def singleton_class
class << self; self; end
end
end
fp.attach(foo.singleton_class, 'add')
expect(foo.add(10, 10)).to eq(20)
end
it 'can wrap a blocking function' do
fpOpen = FFI::Function.new(:pointer, [ ], @libtest.find_function('testBlockingOpen'))
fpRW = FFI::Function.new(:char, [ :pointer, :char ], @libtest.find_function('testBlockingRW'), :blocking => true)
fpWR = FFI::Function.new(:char, [ :pointer, :char ], @libtest.find_function('testBlockingWR'), :blocking => true)
fpClose = FFI::Function.new(:void, [ :pointer ], @libtest.find_function('testBlockingClose'))
handle = fpOpen.call
expect(handle).not_to be_null
begin
thWR = Thread.new { fpWR.call(handle, 63) }
thRW = Thread.new { fpRW.call(handle, 64) }
expect(thWR.value).to eq(64)
expect(thRW.value).to eq(63)
ensure
fpClose.call(handle)
end
end
it 'autorelease flag is set to true by default' do
fp = FFI::Function.new(:int, [:int, :int], @libtest.find_function('testAdd'))
expect(fp.autorelease?).to be true
end
it 'can explicity free itself' do
fp = FFI::Function.new(:int, []) { }
fp.free
expect { fp.free }.to raise_error RuntimeError
end
it 'can\'t explicity free itself if not previously allocated' do
fp = FFI::Function.new(:int, [:int, :int], @libtest.find_function('testAdd'))
expect { fp.free }.to raise_error RuntimeError
end
it 'has a memsize function', skip: RUBY_ENGINE != "ruby" do
base_size = ObjectSpace.memsize_of(Object.new)
function = FFI::Function.new(:int, [:int, :int], @libtest.find_function('testAdd'))
size = ObjectSpace.memsize_of(function)
expect(size).to be > base_size
end
end
|