summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/ffi/async_callback_spec.rb23
-rw-r--r--spec/ffi/buffer_spec.rb10
-rw-r--r--spec/ffi/dynamic_library_spec.rb60
-rw-r--r--spec/ffi/enum_spec.rb38
-rw-r--r--spec/ffi/errno_spec.rb8
-rw-r--r--spec/ffi/fixtures/ClosureTest.c12
-rw-r--r--spec/ffi/fixtures/PipeHelperWindows.c4
-rw-r--r--spec/ffi/fixtures/PointerTest.c2
-rw-r--r--spec/ffi/fixtures/compile.rb4
-rw-r--r--spec/ffi/function_spec.rb42
-rw-r--r--spec/ffi/function_type_spec.rb27
-rw-r--r--spec/ffi/gc_compact_spec.rb66
-rw-r--r--spec/ffi/library_spec.rb95
-rw-r--r--spec/ffi/managed_struct_spec.rb2
-rw-r--r--spec/ffi/memorypointer_spec.rb34
-rw-r--r--spec/ffi/platform_spec.rb13
-rw-r--r--spec/ffi/pointer_spec.rb39
-rw-r--r--spec/ffi/rbx/memory_pointer_spec.rb20
-rw-r--r--spec/ffi/spec_helper.rb4
-rw-r--r--spec/ffi/struct_by_ref_spec.rb9
-rw-r--r--spec/ffi/struct_spec.rb109
-rw-r--r--spec/ffi/type_spec.rb76
-rw-r--r--spec/ffi/variadic_spec.rb32
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 ],