diff options
Diffstat (limited to 'spec')
-rw-r--r-- | spec/ffi/async_callback_spec.rb | 23 | ||||
-rw-r--r-- | spec/ffi/buffer_spec.rb | 10 | ||||
-rw-r--r-- | spec/ffi/dynamic_library_spec.rb | 60 | ||||
-rw-r--r-- | spec/ffi/enum_spec.rb | 38 | ||||
-rw-r--r-- | spec/ffi/errno_spec.rb | 8 | ||||
-rw-r--r-- | spec/ffi/fixtures/ClosureTest.c | 12 | ||||
-rw-r--r-- | spec/ffi/fixtures/PipeHelperWindows.c | 4 | ||||
-rw-r--r-- | spec/ffi/fixtures/PointerTest.c | 2 | ||||
-rw-r--r-- | spec/ffi/fixtures/compile.rb | 4 | ||||
-rw-r--r-- | spec/ffi/function_spec.rb | 42 | ||||
-rw-r--r-- | spec/ffi/function_type_spec.rb | 27 | ||||
-rw-r--r-- | spec/ffi/gc_compact_spec.rb | 66 | ||||
-rw-r--r-- | spec/ffi/library_spec.rb | 95 | ||||
-rw-r--r-- | spec/ffi/managed_struct_spec.rb | 2 | ||||
-rw-r--r-- | spec/ffi/memorypointer_spec.rb | 34 | ||||
-rw-r--r-- | spec/ffi/platform_spec.rb | 13 | ||||
-rw-r--r-- | spec/ffi/pointer_spec.rb | 39 | ||||
-rw-r--r-- | spec/ffi/rbx/memory_pointer_spec.rb | 20 | ||||
-rw-r--r-- | spec/ffi/spec_helper.rb | 4 | ||||
-rw-r--r-- | spec/ffi/struct_by_ref_spec.rb | 9 | ||||
-rw-r--r-- | spec/ffi/struct_spec.rb | 109 | ||||
-rw-r--r-- | spec/ffi/type_spec.rb | 76 | ||||
-rw-r--r-- | spec/ffi/variadic_spec.rb | 32 |
23 files changed, 695 insertions, 34 deletions
diff --git a/spec/ffi/async_callback_spec.rb b/spec/ffi/async_callback_spec.rb index 51a4886..7bb9f70 100644 --- a/spec/ffi/async_callback_spec.rb +++ b/spec/ffi/async_callback_spec.rb @@ -50,4 +50,27 @@ describe "async callback" do expect(callback_runner_thread.name).to eq("FFI Callback Runner") end + + it "works in Ractor", :ractor do + skip "not yet supported on TruffleRuby" if RUBY_ENGINE == "truffleruby" + + res = Ractor.new do + v = 0xdeadbeef + correct_ractor = false + correct_thread = false + thread = Thread.current + rac = Ractor.current + cb = Proc.new do |i| + v = i + correct_ractor = rac == Ractor.current + correct_thread = thread != Thread.current + end + LibTest.testAsyncCallback(cb, 0x7fffffff) + + [v, correct_ractor, correct_thread] + end.take + + expect(res).to eq([0x7fffffff, true, true]) + end + end diff --git a/spec/ffi/buffer_spec.rb b/spec/ffi/buffer_spec.rb index 619cb6b..e031f7c 100644 --- a/spec/ffi/buffer_spec.rb +++ b/spec/ffi/buffer_spec.rb @@ -285,3 +285,13 @@ describe "Buffer#initialize" do expect(block_executed).to be true end end + +describe "Buffer#memsize_of" do + it "has a memsize function", skip: RUBY_ENGINE != "ruby" do + base_size = ObjectSpace.memsize_of(Object.new) + + buf = FFI::Buffer.new 14 + size = ObjectSpace.memsize_of(buf) + expect(size).to be > base_size + end +end diff --git a/spec/ffi/dynamic_library_spec.rb b/spec/ffi/dynamic_library_spec.rb new file mode 100644 index 0000000..088a822 --- /dev/null +++ b/spec/ffi/dynamic_library_spec.rb @@ -0,0 +1,60 @@ +# +# 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::DynamicLibrary do + it "should be shareable for Ractor", :ractor do + libtest = FFI::DynamicLibrary.open(TestLibrary::PATH, + FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL) + + res = Ractor.new(libtest) do |libtest2| + libtest2.find_symbol("testClosureVrV").address + end.take + + expect( res ).to be > 0 + end + + it "load a library in a Ractor", :ractor do + res = Ractor.new do + libtest = FFI::DynamicLibrary.open(TestLibrary::PATH, + FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL) + libtest.find_symbol("testClosureVrV") + end.take + + expect(res.address).to be > 0 + end + + it "has a memsize function", skip: RUBY_ENGINE != "ruby" do + base_size = ObjectSpace.memsize_of(Object.new) + + libtest = FFI::DynamicLibrary.open(TestLibrary::PATH, + FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL) + size = ObjectSpace.memsize_of(libtest) + expect(size).to be > base_size + end + + describe Symbol do + before do + @libtest = FFI::DynamicLibrary.open( + TestLibrary::PATH, + FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL, + ) + end + + it "has a memsize function", skip: RUBY_ENGINE != "ruby" do + base_size = ObjectSpace.memsize_of(Object.new) + + symbol = @libtest.find_symbol("gvar_gstruct_set") + size = ObjectSpace.memsize_of(symbol) + expect(size).to be > base_size + end + + it "should be shareable for Ractor", :ractor do + symbol = @libtest.find_symbol("gvar_gstruct_set") + expect(Ractor.shareable?(symbol)).to be true + end + end +end diff --git a/spec/ffi/enum_spec.rb b/spec/ffi/enum_spec.rb index b8c5b57..7a55b07 100644 --- a/spec/ffi/enum_spec.rb +++ b/spec/ffi/enum_spec.rb @@ -26,14 +26,18 @@ module TestEnum3 ffi_lib TestLibrary::PATH enum :enum_type1, [:c1, :c2, :c3, :c4] - enum :enum_type2, [:c5, 42, :c6, :c7, :c8] - enum :enum_type3, [:c9, 42, :c10, :c11, 4242, :c12] - enum :enum_type4, [:c13, 42, :c14, 4242, :c15, 424242, :c16, 42424242] - attach_function :test_tagged_typedef_enum1, [:enum_type1], :enum_type1 + + enum :enum_type2, [:c5, 42, :c6, :c7, :c8] attach_function :test_tagged_typedef_enum2, [:enum_type2], :enum_type2 + + enum :enum_type3, [:c9, 42, :c10, :c11, 4242, :c12] attach_function :test_tagged_typedef_enum3, [:enum_type3], :enum_type3 + + enum :enum_type4, [:c13, 42, :c14, 4242, :c15, 424242, :c16, 42424242] attach_function :test_tagged_typedef_enum4, [:enum_type4], :enum_type4 + + freeze end module TestEnum4 @@ -44,18 +48,22 @@ module TestEnum4 enum :enum_type1, [:c5, 0x42, :c6, :c7, :c8] enum :enum_type2, [:c9, 0x42, :c10, :c11, 0x4242, :c12] enum :enum_type3, [:c13, 0x42, :c14, 0x4242, :c15, 0x42424242, :c16, 0x4242424242424242] - enum FFI::Type::UINT16, :enum_type4, [:c17, 0x42, :c18, :c19, :c20] - enum FFI::Type::UINT32, :enum_type5, [:c21, 0x42, :c22, :c23, 0x4242, :c24] - enum FFI::Type::UINT64, :enum_type6, [:c25, 0x42, :c26, 0x4242, :c27, 0x42424242, :c28, 0x4242424242424242] - enum FFI::Type::UINT64, [:c29, 0x4242424242424242, :c30, :c31, :c32] attach_function :test_untagged_nonint_enum, [:uint8], :uint8 attach_function :test_tagged_nonint_enum1, [:uint16], :uint16 attach_function :test_tagged_nonint_enum2, [:uint32], :uint32 attach_function :test_tagged_nonint_enum3, [:uint64], :uint64 + + enum FFI::Type::UINT16, :enum_type4, [:c17, 0x42, :c18, :c19, :c20] + enum FFI::Type::UINT32, :enum_type5, [:c21, 0x42, :c22, :c23, 0x4242, :c24] + enum FFI::Type::UINT64, :enum_type6, [:c25, 0x42, :c26, 0x4242, :c27, 0x42424242, :c28, 0x4242424242424242] + enum FFI::Type::UINT64, [:c29, 0x4242424242424242, :c30, :c31, :c32] + attach_function :test_tagged_nonint_enum4, :test_tagged_nonint_enum1, [:enum_type4], :enum_type4 attach_function :test_tagged_nonint_enum5, :test_tagged_nonint_enum2, [:enum_type5], :enum_type5 attach_function :test_tagged_nonint_enum6, :test_tagged_nonint_enum3, [:enum_type6], :enum_type6 + + freeze end describe "A library with no enum defined" do @@ -430,4 +438,18 @@ describe "All enums" do end end.to raise_error(ArgumentError, /duplicate/) end + + it "should be usable in Ractor", :ractor do + res = Ractor.new do + [ + TestEnum1.test_untagged_enum(:c1), + TestEnum3.test_tagged_typedef_enum1(:c1), + TestEnum4.test_tagged_nonint_enum4(0x45), + TestEnum3.enum_type(:enum_type1)[0], + TestEnum4.enum_type(:enum_type6)[0x4242424242424242], + TestEnum4.enum_value(:c3) + ] + end.take + expect( res ).to eq( [0, :c1, :c20, :c1, :c28, 2] ) + end end diff --git a/spec/ffi/errno_spec.rb b/spec/ffi/errno_spec.rb index 4170f92..c0207b8 100644 --- a/spec/ffi/errno_spec.rb +++ b/spec/ffi/errno_spec.rb @@ -34,4 +34,12 @@ describe "FFI.errno" do expect(FFI.errno).to eq(0x12345678) end end + + it "works in Ractor", :ractor do + res = Ractor.new do + LibTest.setLastError(0x12345678) + FFI.errno + end.take + expect(res).to eq(0x12345678) + end end diff --git a/spec/ffi/fixtures/ClosureTest.c b/spec/ffi/fixtures/ClosureTest.c index 16f72c4..47273e8 100644 --- a/spec/ffi/fixtures/ClosureTest.c +++ b/spec/ffi/fixtures/ClosureTest.c @@ -16,10 +16,10 @@ double testClosureVrDva(double d, ...) { va_list args; - double (*closure)(void); + typedef double (*closure_fun)(void); va_start(args, d); - closure = va_arg(args, double (*)(void)); + closure_fun closure = va_arg(args, closure_fun); va_end(args); return d + closure(); @@ -27,12 +27,12 @@ double testClosureVrDva(double d, ...) { long testClosureVrILva(int i, long l, ...) { va_list args; - int (*cl1)(int); - long (*cl2)(long); + typedef int (*cl1_fun)(int); + typedef long (*cl2_fun)(long); va_start(args, l); - cl1 = va_arg(args, int (*)(int)); - cl2 = va_arg(args, long (*)(long)); + cl1_fun cl1 = va_arg(args, cl1_fun); + cl2_fun cl2 = va_arg(args, cl2_fun); va_end(args); return cl1(i) + cl2(l); diff --git a/spec/ffi/fixtures/PipeHelperWindows.c b/spec/ffi/fixtures/PipeHelperWindows.c index 0bdbd2e..31b4a2a 100644 --- a/spec/ffi/fixtures/PipeHelperWindows.c +++ b/spec/ffi/fixtures/PipeHelperWindows.c @@ -16,7 +16,7 @@ int pipeHelperCreatePipe(FD_TYPE pipefd[2]) sprintf( name, "\\\\.\\Pipe\\pipeHelper-%u-%i", (unsigned int)GetCurrentProcessId(), pipe_idx++ ); - pipefd[0] = CreateNamedPipe( name, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, + pipefd[0] = CreateNamedPipeA( name, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_WAIT, 1, // Number of pipes 5, // Out buffer size @@ -26,7 +26,7 @@ int pipeHelperCreatePipe(FD_TYPE pipefd[2]) if(pipefd[0] == INVALID_HANDLE_VALUE) return -1; - pipefd[1] = CreateFile( name, GENERIC_WRITE, 0, NULL, + pipefd[1] = CreateFileA( name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); diff --git a/spec/ffi/fixtures/PointerTest.c b/spec/ffi/fixtures/PointerTest.c index a7f392a..02dbc27 100644 --- a/spec/ffi/fixtures/PointerTest.c +++ b/spec/ffi/fixtures/PointerTest.c @@ -5,7 +5,9 @@ */ #include <sys/types.h> +#ifndef _MSC_VER #include <sys/param.h> +#endif #include <stdint.h> #include <stdio.h> #include <stdlib.h> diff --git a/spec/ffi/fixtures/compile.rb b/spec/ffi/fixtures/compile.rb index f2e831a..2be97cb 100644 --- a/spec/ffi/fixtures/compile.rb +++ b/spec/ffi/fixtures/compile.rb @@ -22,6 +22,8 @@ module TestLibrary "powerpc64" when /ppc|powerpc/ "powerpc" + when /sparcv9|sparc64/ + "sparcv9" when /^arm/ if RbConfig::CONFIG['host_os'] =~ /darwin/ "aarch64" @@ -67,5 +69,5 @@ module TestLibrary lib end - PATH = compile_library(".", "libtest.#{FFI::Platform::LIBSUFFIX}") + PATH = FFI.make_shareable(compile_library(".", "libtest.#{FFI::Platform::LIBSUFFIX}")) end diff --git a/spec/ffi/function_spec.rb b/spec/ffi/function_spec.rb index 5bc2421..77d09d8 100644 --- a/spec/ffi/function_spec.rb +++ b/spec/ffi/function_spec.rb @@ -48,6 +48,30 @@ describe FFI::Function do 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 @@ -59,6 +83,16 @@ describe FFI::Function do 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 @@ -103,4 +137,12 @@ describe FFI::Function 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 diff --git a/spec/ffi/function_type_spec.rb b/spec/ffi/function_type_spec.rb new file mode 100644 index 0000000..2f171d8 --- /dev/null +++ b/spec/ffi/function_type_spec.rb @@ -0,0 +1,27 @@ +# +# 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::FunctionType", skip: RUBY_ENGINE != "ruby" do + it 'is initialized with return type and a list of parameter types' do + function_type = FFI::FunctionType.new(:int, [ :char, :ulong ]) + expect(function_type.return_type).to be == FFI::Type::Builtin::INT + expect(function_type.param_types).to be == [ FFI::Type::Builtin::CHAR, FFI::Type::Builtin::ULONG ] + end + + it 'has a memsize function' do + base_size = ObjectSpace.memsize_of(Object.new) + + function_type = FFI::FunctionType.new(:int, []) + size = ObjectSpace.memsize_of(function_type) + expect(size).to be > base_size + + base_size = size + function_type = FFI::FunctionType.new(:int, [:char]) + size = ObjectSpace.memsize_of(function_type) + expect(size).to be > base_size + end +end diff --git a/spec/ffi/gc_compact_spec.rb b/spec/ffi/gc_compact_spec.rb new file mode 100644 index 0000000..974d7f8 --- /dev/null +++ b/spec/ffi/gc_compact_spec.rb @@ -0,0 +1,66 @@ +# -*- rspec -*- +# encoding: utf-8 +# +# Tests to verify correct implementation of compaction callbacks in rb_data_type_t definitions. +# +# Compaction callbacks update moved VALUEs. +# In ruby-2.7 they are invoked only while GC.compact or GC.verify_compaction_references. +# Ruby constants are usually moved, but local variables are not. +# +# Effectiveness of the tests below should be verified by commenting the compact callback out like so: +# +# const rb_data_type_t rbffi_struct_layout_data_type = { +# .wrap_struct_name = "FFI::StructLayout", +# .function = { +# .dmark = struct_layout_mark, +# .dfree = struct_layout_free, +# .dsize = struct_layout_memsize, +# # ffi_compact_callback( struct_layout_compact ) +# }, +# .parent = &rbffi_type_data_type, +# .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED +# }; +# +# This should result in a segmentation fault aborting the whole process. +# Therefore the effectiveness of only one test can be verified per rspec run. + +require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper")) + +describe "GC.compact", if: GC.respond_to?(:compact) do + before :all do + + class St1 < FFI::Struct + layout :i, :int + end + ST1 = St1.new + + class St2 < FFI::Struct + layout :i, :int + end + ST2 = St2.new + ST2[:i] = 6789 + + begin + # Use GC.verify_compaction_references instead of GC.compact . + # This has the advantage that all movable objects are actually moved. + # The downside is that it doubles the heap space of the Ruby process. + # Therefore we call it only once and do several tests afterwards. + GC.verify_compaction_references(toward: :empty, double_heap: true) + rescue NotImplementedError, NoMethodError => err + skip("GC.compact skipped: #{err}") + end + end + + it "should compact FFI::StructLayout without field cache" do + expect( ST1[:i] ).to eq( 0 ) + end + + it "should compact FFI::StructLayout with field cache" do + expect( ST2[:i] ).to eq( 6789 ) + end + + it "should compact FFI::StructLayout::Field" do + l = St1.layout + expect( l.fields.first.type ).to eq( FFI::Type::Builtin::INT32 ) + end +end diff --git a/spec/ffi/library_spec.rb b/spec/ffi/library_spec.rb index ca28974..8691b17 100644 --- a/spec/ffi/library_spec.rb +++ b/spec/ffi/library_spec.rb @@ -5,6 +5,12 @@ 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 @@ -20,6 +26,14 @@ describe "Library" do 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 @@ -88,7 +102,7 @@ describe "Library" do }.to raise_error(LoadError) end - it "interprets INPUT() in loader scripts", unless: FFI::Platform.windows? do + 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") @@ -184,15 +198,49 @@ describe "Library" do 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 + 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) @@ -322,4 +370,35 @@ describe "Library" do 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 diff --git a/spec/ffi/managed_struct_spec.rb b/spec/ffi/managed_struct_spec.rb index b4e80bb..6299bdc 100644 --- a/spec/ffi/managed_struct_spec.rb +++ b/spec/ffi/managed_struct_spec.rb @@ -46,6 +46,8 @@ describe "Managed Struct" do def self.release(_ptr) @@count += 1 end + private_class_method :release + def self.wait_gc(count) loop = 5 while loop > 0 && @@count < count diff --git a/spec/ffi/memorypointer_spec.rb b/spec/ffi/memorypointer_spec.rb index c8d1b42..f29e2cb 100644 --- a/spec/ffi/memorypointer_spec.rb +++ b/spec/ffi/memorypointer_spec.rb @@ -28,6 +28,21 @@ describe "MemoryPointer#total" do expect(MemoryPointer.new(1024).total).to eq 1024 end end +describe "MemoryPointer#clear" do + it "should clear the memory" do + ptr = MemoryPointer.new(:long) + ptr.write_long 1234 + expect(ptr.read_long).to eq(1234) + ptr.clear + expect(ptr.read_long).to eq(0) + end + it "should deny changes when frozen" do + skip "not yet supported on TruffleRuby" if RUBY_ENGINE == "truffleruby" + skip "not yet supported on JRuby" if RUBY_ENGINE == "jruby" + ptr = MemoryPointer.new(:long).freeze + expect{ ptr.clear }.to raise_error(RuntimeError, /memory write/) + end +end describe "MemoryPointer#read_array_of_long" do it "foo" do ptr = MemoryPointer.new(:long, 1024) @@ -76,3 +91,22 @@ describe "MemoryPointer return value" do expect(Stdio.fclose(fp)).to eq 0 unless fp.nil? or fp.null? end end +describe "#autorelease" do + it "should be true by default" do + expect(MemoryPointer.new(8).autorelease?).to be true + end + + it "should return false when autorelease=(false)" do + ptr = MemoryPointer.new(8) + ptr.autorelease = false + expect(ptr.autorelease?).to be false + ptr.free + end + + it "should deny changes when frozen" do + skip "not yet supported on TruffleRuby" if RUBY_ENGINE == "truffleruby" + skip "not yet supported on JRuby" if RUBY_ENGINE == "jruby" + ptr = MemoryPointer.new(8).freeze + expect{ ptr.autorelease = false }.to raise_error(FrozenError) + end +end diff --git a/spec/ffi/platform_spec.rb b/spec/ffi/platform_spec.rb index ad23621..8890b07 100644 --- a/spec/ffi/platform_spec.rb +++ b/spec/ffi/platform_spec.rb @@ -134,4 +134,17 @@ describe "FFI::Platform.unix?" do expect(FFI::Platform::BYTE_ORDER).to eq(order) end end + + it "should have shareable constants for Ractor", :ractor do + res = Ractor.new do + [ + FFI::Platform::OS, + FFI::Platform::CPU, + FFI::Platform::ARCH, + FFI::Platform::OS, + ] + end.take + + expect( res.size ).to be > 0 + end end diff --git a/spec/ffi/pointer_spec.rb b/spec/ffi/pointer_spec.rb index b216a16..8716d30 100644 --- a/spec/ffi/pointer_spec.rb +++ b/spec/ffi/pointer_spec.rb @@ -90,6 +90,13 @@ describe "Pointer" do expect(PointerTestLib.ptr_ret_pointer(memory, 0).address).to eq(0xdeadbeef) end + it "#write_pointer frozen object" do + skip "not yet supported on TruffleRuby" if RUBY_ENGINE == "truffleruby" + skip "not yet supported on JRuby" if RUBY_ENGINE == "jruby" + memory = FFI::MemoryPointer.new(:pointer).freeze + expect{ memory.write_pointer(PointerTestLib.ptr_from_address(0xdeadbeef)) }.to raise_error(RuntimeError, /memory write/) + end + it "#read_array_of_pointer" do values = [0x12345678, 0xfeedf00d, 0xdeadbeef] memory = FFI::MemoryPointer.new :pointer, values.size @@ -237,6 +244,14 @@ describe "Pointer" do expect(FFI::Pointer.new(0).slice(0, 10).size_limit?).to be true end end + + describe "#initialise" do + it 'can use adresses with high bit set' do + max_address = 2**FFI::Platform::ADDRESS_SIZE - 1 + pointer = FFI::Pointer.new(:uint8, max_address) + expect(pointer.address).to eq(max_address) + end + end if (RUBY_ENGINE != "truffleruby" && RUBY_ENGINE != "jruby") end describe "AutoPointer" do @@ -249,6 +264,7 @@ describe "AutoPointer" do def self.release @@count += 1 if @@count > 0 end + private_class_method(:release) def self.reset @@count = 0 end @@ -267,10 +283,11 @@ describe "AutoPointer" do end class AutoPointerSubclass < FFI::AutoPointer def self.release(ptr); end + private_class_method(:release) end # see #427 - it "cleanup via default release method", :broken => true do + it "cleanup via default release method", gc_dependent: true do expect(AutoPointerSubclass).to receive(:release).at_least(loop_count-wiggle_room).times AutoPointerTestHelper.reset loop_count.times do @@ -283,7 +300,7 @@ describe "AutoPointer" do end # see #427 - it "cleanup when passed a proc", :broken => true do + it "cleanup when passed a proc", gc_dependent: true do # NOTE: passing a proc is touchy, because it's so easy to create a memory leak. # # specifically, if we made an inline call to @@ -302,7 +319,7 @@ describe "AutoPointer" do end # see #427 - it "cleanup when passed a method", :broken => true do + it "cleanup when passed a method", gc_dependent: true do expect(AutoPointerTestHelper).to receive(:release).at_least(loop_count-wiggle_room).times AutoPointerTestHelper.reset loop_count.times do @@ -319,6 +336,7 @@ describe "AutoPointer" do ffi_lib TestLibrary::PATH class CustomAutoPointer < FFI::AutoPointer def self.release(ptr); end + private_class_method(:release) end attach_function :ptr_from_address, [ FFI::Platform::ADDRESS_SIZE == 32 ? :uint : :ulong_long ], CustomAutoPointer end @@ -341,6 +359,7 @@ describe "AutoPointer" do describe "#autorelease?" do ptr_class = Class.new(FFI::AutoPointer) do def self.release(ptr); end + private_class_method(:release) end it "should be true by default" do @@ -352,11 +371,17 @@ describe "AutoPointer" do ptr.autorelease = false expect(ptr.autorelease?).to be false end + + it "should deny changes when frozen" do + ptr = ptr_class.new(FFI::Pointer.new(0xdeadbeef)).freeze + expect{ ptr.autorelease = false }.to raise_error(FrozenError) + end end describe "#type_size" do ptr_class = Class.new(FFI::AutoPointer) do def self.release(ptr); end + private_class_method(:release) end it "type_size of AutoPointer should match wrapped Pointer" do @@ -373,5 +398,13 @@ describe "AutoPointer" do expect(mptr[1].read_uint).to eq(0xcafebabe) end end + + it "has a memsize function", skip: RUBY_ENGINE != "ruby" do + base_size = ObjectSpace.memsize_of(Object.new) + + pointer = FFI::Pointer.new(:int, 0xdeadbeef) + size = ObjectSpace.memsize_of(pointer) + expect(size).to be > base_size + end end diff --git a/spec/ffi/rbx/memory_pointer_spec.rb b/spec/ffi/rbx/memory_pointer_spec.rb index 3878973..1270c9b 100644 --- a/spec/ffi/rbx/memory_pointer_spec.rb +++ b/spec/ffi/rbx/memory_pointer_spec.rb @@ -104,6 +104,18 @@ describe "MemoryPointer" do expect(m.read FFI::Type::BOOL).to eq(false) end + it "allows definition of a custom typedef" do + FFI.typedef :uint32, :fubar_t + expect(FFI.find_type(:fubar_t)).to eq(FFI::Type::Builtin::UINT32) + end + + it "allows overwriting of a default typedef" do + FFI.typedef :uint32, :char + expect(FFI.find_type(:char)).to eq(FFI::Type::Builtin::UINT32) + ensure + FFI.typedef FFI::Type::Builtin::CHAR, :char + end + it "allows writing a custom typedef" do FFI.typedef :uint, :fubar_t FFI.typedef :size_t, :fubar2_t @@ -189,4 +201,12 @@ describe "MemoryPointer" do end expect(block_executed).to be true end + + it "has a memsize function", skip: RUBY_ENGINE != "ruby" do + base_size = ObjectSpace.memsize_of(Object.new) + + pointer = FFI::MemoryPointer.from_string("FFI is Awesome") + size = ObjectSpace.memsize_of(pointer) + expect(size).to be > base_size + end end diff --git a/spec/ffi/spec_helper.rb b/spec/ffi/spec_helper.rb index bb12050..22d1c47 100644 --- a/spec/ffi/spec_helper.rb +++ b/spec/ffi/spec_helper.rb @@ -5,9 +5,11 @@ require_relative 'fixtures/compile' require 'timeout' +require 'objspace' RSpec.configure do |c| - c.filter_run_excluding :broken => true + c.filter_run_excluding gc_dependent: true unless ENV['FFI_TEST_GC'] == 'true' + c.filter_run_excluding( :ractor ) unless defined?(Ractor) && RUBY_VERSION >= "3.1" end module TestLibrary diff --git a/spec/ffi/struct_by_ref_spec.rb b/spec/ffi/struct_by_ref_spec.rb index 0858423..a775f92 100644 --- a/spec/ffi/struct_by_ref_spec.rb +++ b/spec/ffi/struct_by_ref_spec.rb @@ -39,5 +39,14 @@ describe FFI::Struct, ' by_ref' do expect { @api.struct_test(other_class.new) }.to raise_error(TypeError) end + + it "can reveal the mapped type converter" 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' + + param_type = @api.attached_functions[:struct_test].param_types[0] + expect(param_type).to be_a(FFI::Type::Mapped) + expect(param_type.converter).to be_a(FFI::StructByReference) + end end diff --git a/spec/ffi/struct_spec.rb b/spec/ffi/struct_spec.rb index fb574d2..ab62da1 100644 --- a/spec/ffi/struct_spec.rb +++ b/spec/ffi/struct_spec.rb @@ -410,6 +410,7 @@ module StructSpecsStructTests s.pointer.put_double(0, 1.0) expect(s.pointer.get_double(0)).to eq(1.0) end + module EnumFields extend FFI::Library TestEnum = enum :test_enum, [:c1, 10, :c2, 20, :c3, 30, :c4, 40] @@ -456,13 +457,24 @@ module StructSpecsStructTests end it "Can have CallbackInfo struct field" do + s = CallbackMember::TestStruct.new + add_proc = lambda { |a, b| a + b } + sub_proc = lambda { |a, b| a - b } + s[:add] = add_proc + s[:sub] = sub_proc + expect(CallbackMember.struct_call_add_cb(s, 40, 2)).to eq(42) + expect(CallbackMember.struct_call_sub_cb(s, 44, 2)).to eq(42) + end + + it "Can use CallbackInfo struct field in Ractor", :ractor do + res = Ractor.new do s = CallbackMember::TestStruct.new - add_proc = lambda { |a, b| a+b } - sub_proc = lambda { |a, b| a-b } + add_proc = lambda { |a, b| a + b } s[:add] = add_proc - s[:sub] = sub_proc - expect(CallbackMember.struct_call_add_cb(s, 40, 2)).to eq(42) - expect(CallbackMember.struct_call_sub_cb(s, 44, 2)).to eq(42) + CallbackMember.struct_call_add_cb(s, 40, 2) + end.take + + expect( res ).to eq(42) end it "Can return its members as a list" do @@ -532,6 +544,34 @@ module StructSpecsStructTests expect(a.members).to eq([:a]) expect(b.members).to eq([:a, :b]) end + + it "can be made shareable for Ractor", :ractor do + a = Class.new(FFI::Struct) do + layout :a, :char + end.new + a[:a] = -34 + Ractor.make_shareable(a) + + res = Ractor.new(a) do |a2| + a2[:a] + end.take + + expect( res ).to eq(-34) + end + + it "should be usable with Ractor", :ractor do + class TestStructRactor < FFI::Struct + layout :i, :int + end + + res = Ractor.new do + s = TestStructRactor.new + s[:i] = 0x14 + LibTest.ptr_ret_int32_t(s, 0) + end.take + + expect( res ).to eq(0x14) + end end end @@ -1027,6 +1067,65 @@ describe "variable-length arrays" do end end +describe "Struct memsize functions", skip: RUBY_ENGINE != "ruby" do + it "has a memsize function" do + base_size = ObjectSpace.memsize_of(Object.new) + + c = Class.new(FFI::Struct) do + layout :b, :bool + end + struct = c.new + size = ObjectSpace.memsize_of(struct) + expect(size).to be > base_size + end + + class SmallCustomStruct < FFI::Struct + layout :pointer, :pointer + end + + class LargerCustomStruct < FFI::Struct + layout :pointer, :pointer, + :c, :char, + :i, :int + end + + it "StructLayout has a memsize function" do + base_size = ObjectSpace.memsize_of(Object.new) + + layout = SmallCustomStruct.layout + size = ObjectSpace.memsize_of(layout) + expect(size).to be > base_size + base_size = size + + layout = LargerCustomStruct.layout + size = ObjectSpace.memsize_of(layout) + expect(size).to be > base_size + end + + it "StructField has a memsize function" do + base_size = ObjectSpace.memsize_of(Object.new) + + layout = SmallCustomStruct.layout + size = ObjectSpace.memsize_of(layout[:pointer]) + expect(size).to be > base_size + end + + it "StructLayout should be shareable with Ractor", :ractor do + kl = Class.new(FFI::Struct) do + layout :ptr, :pointer + end + expect(Ractor.shareable?(kl.layout)).to eq(true) + end + + it "StructField should be shareable with Ractor", :ractor do + kl = Class.new(FFI::Struct) do + layout :ptr, :pointer + end + expect(Ractor.shareable?(kl.layout[:ptr])).to eq(true) + end +end + + describe "Struct order" do before :all do @struct = Class.new(FFI::Struct) do diff --git a/spec/ffi/type_spec.rb b/spec/ffi/type_spec.rb new file mode 100644 index 0000000..e880c83 --- /dev/null +++ b/spec/ffi/type_spec.rb @@ -0,0 +1,76 @@ +# +# 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::Type do + it 'has a memsize function', skip: RUBY_ENGINE != "ruby" do + base_size = ObjectSpace.memsize_of(Object.new) + + size = ObjectSpace.memsize_of(FFI::Type.new(42)) + expect(size).to be > base_size + base_size = size + + converter = Module.new do + extend FFI::DataConverter + + def self.native_type + @native_type_called = true + FFI::Type::INT32 + end + + def self.to_native(val, ctx) + @to_native_called = true + ToNativeMap[val] + end + + def self.from_native(val, ctx) + @from_native_called = true + FromNativeMap[val] + end + end + + size = ObjectSpace.memsize_of(FFI::Type::Mapped.new(converter)) + expect(size).to be > base_size + base_size = size + + # Builtin types are larger as they also have a name and own ffi_type + size = ObjectSpace.memsize_of(FFI::Type::Builtin::CHAR) + expect(size).to be > base_size + end + + it "should be shareable with Ractor", :ractor do + expect(Ractor.shareable?(FFI::Type.new(5))).to eq(true) + end + + describe :Builtin do + it "should be shareable with Ractor", :ractor do + expect(Ractor.shareable?(FFI::Type::INT32)).to eq(true) + end + end + + describe :Mapped do + it "should be shareable with Ractor", :ractor do + converter = Module.new do + extend FFI::DataConverter + + def self.native_type + FFI::Type::INT32 + end + + def self.to_native(val, ctx) + ToNativeMap[val] + end + + def self.from_native(val, ctx) + FromNativeMap[val] + end + end + expect(Ractor.shareable?(converter)).to eq(true) + type = FFI::Type::Mapped.new(converter) + expect(Ractor.shareable?(type)).to eq(true) + end + end +end diff --git a/spec/ffi/variadic_spec.rb b/spec/ffi/variadic_spec.rb index f379ed4..b528c09 100644 --- a/spec/ffi/variadic_spec.rb +++ b/spec/ffi/variadic_spec.rb @@ -23,6 +23,8 @@ describe "Function with variadic arguments" do attach_function :testBlockingClose, [ :pointer ], :void attach_function :testCallbackVrDva, :testClosureVrDva, [ :double, :varargs ], :double attach_function :testCallbackVrILva, :testClosureVrILva, [ :int, :long, :varargs ], :long + + freeze end it "takes enum arguments" do @@ -37,6 +39,15 @@ describe "Function with variadic arguments" do expect(LibTest.pack_varargs2(buf, :c1, "ii", :int, :c3, :int, :c4)).to eq(:c2) end + it "can reveal its return and parameters" 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' + + fun = LibTest.attached_functions[:testBlockingWRva] + expect(fun.param_types).to eq([FFI::Type::POINTER, FFI::Type::CHAR, FFI::Type::VARARGS]) + expect(fun.return_type).to eq(FFI::Type::INT8) + end + it 'can wrap a blocking function with varargs' do handle = LibTest.testBlockingOpen expect(handle).not_to be_null @@ -87,12 +98,33 @@ describe "Function with variadic arguments" do expect(LibTest.testCallbackVrDva(3.0, :cbVrD, pr)).to be_within(0.0000001).of(45.0) end + it "can be called as instance method" do + kl = Class.new do + include LibTest + def call + pr = proc { 42.0 } + testCallbackVrDva(3.0, :cbVrD, pr) + end + end + expect(kl.new.call).to be_within(0.0000001).of(45.0) + end + it "call variadic with several callback arguments" do pr1 = proc { |i| i + 1 } pr2 = proc { |l| l + 2 } expect(LibTest.testCallbackVrILva(5, 6, :cbVrI, pr1, :cbVrL, pr2)).to eq(14) end + it "should be usable with Ractor", :ractor do + res = Ractor.new do + pr = proc { 42.0 } + LibTest.testCallbackVrDva(3.0, :cbVrD, pr) + end.take + + expect(res).to be_within(0.0000001).of(45.0) + end + + module Varargs PACK_VALUES = { 'c' => [ 0x12 ], |