summaryrefslogtreecommitdiff
path: root/test/fiddle
diff options
context:
space:
mode:
Diffstat (limited to 'test/fiddle')
-rw-r--r--test/fiddle/helper.rb30
-rw-r--r--test/fiddle/test_c_struct_builder.rb69
-rw-r--r--test/fiddle/test_c_struct_entry.rb8
-rw-r--r--test/fiddle/test_closure.rb118
-rw-r--r--test/fiddle/test_cparser.rb98
-rw-r--r--test/fiddle/test_fiddle.rb41
-rw-r--r--test/fiddle/test_func.rb74
-rw-r--r--test/fiddle/test_function.rb92
-rw-r--r--test/fiddle/test_handle.rb45
-rw-r--r--test/fiddle/test_import.rb23
-rw-r--r--test/fiddle/test_memory_view.rb36
-rw-r--r--test/fiddle/test_pack.rb37
-rw-r--r--test/fiddle/test_pinned.rb1
-rw-r--r--test/fiddle/test_pointer.rb40
14 files changed, 614 insertions, 98 deletions
diff --git a/test/fiddle/helper.rb b/test/fiddle/helper.rb
index f38f9036a3..e470f5a276 100644
--- a/test/fiddle/helper.rb
+++ b/test/fiddle/helper.rb
@@ -1,4 +1,6 @@
# frozen_string_literal: true
+
+require 'rbconfig/sizeof'
require 'test/unit'
require 'fiddle'
@@ -47,8 +49,14 @@ when /linux/
libm_so = libc_so
else
# glibc
- libc_so = File.join(libdir, "libc.so.6")
- libm_so = File.join(libdir, "libm.so.6")
+ case RUBY_PLATFORM
+ when /alpha-linux/, /ia64-linux/
+ libc_so = "libc.so.6.1"
+ libm_so = "libm.so.6.1"
+ else
+ libc_so = "libc.so.6"
+ libm_so = "libm.so.6"
+ end
end
when /mingw/, /mswin/
require "rbconfig"
@@ -56,6 +64,8 @@ when /mingw/, /mswin/
libc_so = libm_so = "#{crtname}.dll"
when /darwin/
libc_so = libm_so = "/usr/lib/libSystem.B.dylib"
+ # macOS 11.0+ removed libSystem.B.dylib from /usr/lib. But It works with dlopen.
+ rigid_path = true
when /kfreebsd/
libc_so = "/lib/libc.so.0.1"
libm_so = "/lib/libm.so.1"
@@ -131,12 +141,9 @@ else
end
end
-libc_so = nil if !libc_so || (libc_so[0] == ?/ && !File.file?(libc_so))
-libm_so = nil if !libm_so || (libm_so[0] == ?/ && !File.file?(libm_so))
-
-# macOS 11.0+ removed libSystem.B.dylib from /usr/lib. But It works with dlopen.
-if RUBY_PLATFORM =~ /darwin/
- libc_so = libm_so = "/usr/lib/libSystem.B.dylib"
+unless rigid_path
+ libc_so = nil if libc_so && libc_so[0] == ?/ && !File.file?(libc_so)
+ libm_so = nil if libm_so && libm_so[0] == ?/ && !File.file?(libm_so)
end
if !libc_so || !libm_so
@@ -166,5 +173,12 @@ module Fiddle
GC.start
end
end
+
+ def under_gc_stress
+ stress, GC.stress = GC.stress, true
+ yield
+ ensure
+ GC.stress = stress
+ end
end
end
diff --git a/test/fiddle/test_c_struct_builder.rb b/test/fiddle/test_c_struct_builder.rb
new file mode 100644
index 0000000000..ca44c6cf7a
--- /dev/null
+++ b/test/fiddle/test_c_struct_builder.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+begin
+ require_relative 'helper'
+ require 'fiddle/struct'
+ require 'fiddle/cparser'
+ require 'fiddle/import'
+rescue LoadError
+end
+
+module Fiddle
+ class TestCStructBuilder < TestCase
+ include Fiddle::CParser
+ extend Fiddle::Importer
+
+ RBasic = struct ['void * flags',
+ 'void * klass' ]
+
+
+ RObject = struct [
+ { 'basic' => RBasic },
+ { 'as' => union([
+ { 'heap'=> struct([ 'uint32_t numiv',
+ 'void * ivptr',
+ 'void * iv_index_tbl' ]) },
+ 'void *ary[3]' ])}
+ ]
+
+
+ def test_basic_embedded_members
+ assert_equal 0, RObject.offsetof("basic.flags")
+ assert_equal Fiddle::SIZEOF_VOIDP, RObject.offsetof("basic.klass")
+ end
+
+ def test_embedded_union_members
+ assert_equal 2 * Fiddle::SIZEOF_VOIDP, RObject.offsetof("as")
+ assert_equal 2 * Fiddle::SIZEOF_VOIDP, RObject.offsetof("as.heap")
+ assert_equal 2 * Fiddle::SIZEOF_VOIDP, RObject.offsetof("as.heap.numiv")
+ assert_equal 3 * Fiddle::SIZEOF_VOIDP, RObject.offsetof("as.heap.ivptr")
+ assert_equal 4 * Fiddle::SIZEOF_VOIDP, RObject.offsetof("as.heap.iv_index_tbl")
+ end
+
+ def test_as_ary
+ assert_equal 2 * Fiddle::SIZEOF_VOIDP, RObject.offsetof("as.ary")
+ end
+
+ def test_offsetof
+ types, members = parse_struct_signature(['int64_t i','char c'])
+ my_struct = Fiddle::CStructBuilder.create(Fiddle::CStruct, types, members)
+ assert_equal 0, my_struct.offsetof("i")
+ assert_equal Fiddle::SIZEOF_INT64_T, my_struct.offsetof("c")
+ end
+
+ def test_offset_with_gap
+ types, members = parse_struct_signature(['void *p', 'char c', 'long x'])
+ my_struct = Fiddle::CStructBuilder.create(Fiddle::CStruct, types, members)
+
+ assert_equal PackInfo.align(0, ALIGN_VOIDP), my_struct.offsetof("p")
+ assert_equal PackInfo.align(SIZEOF_VOIDP, ALIGN_CHAR), my_struct.offsetof("c")
+ assert_equal SIZEOF_VOIDP + PackInfo.align(SIZEOF_CHAR, ALIGN_LONG), my_struct.offsetof("x")
+ end
+
+ def test_union_offsetof
+ types, members = parse_struct_signature(['int64_t i','char c'])
+ my_struct = Fiddle::CStructBuilder.create(Fiddle::CUnion, types, members)
+ assert_equal 0, my_struct.offsetof("i")
+ assert_equal 0, my_struct.offsetof("c")
+ end
+ end
+end if defined?(Fiddle)
diff --git a/test/fiddle/test_c_struct_entry.rb b/test/fiddle/test_c_struct_entry.rb
index 9fd16d7101..45de2efe21 100644
--- a/test/fiddle/test_c_struct_entry.rb
+++ b/test/fiddle/test_c_struct_entry.rb
@@ -8,7 +8,7 @@ end
module Fiddle
class TestCStructEntity < TestCase
def test_class_size
- types = [TYPE_DOUBLE, TYPE_CHAR]
+ types = [TYPE_DOUBLE, TYPE_CHAR, TYPE_DOUBLE, TYPE_BOOL]
size = CStructEntity.size types
@@ -20,6 +20,12 @@ module Fiddle
expected = PackInfo.align expected, alignments[1]
expected += PackInfo::SIZE_MAP[TYPE_CHAR]
+ expected = PackInfo.align expected, alignments[2]
+ expected += PackInfo::SIZE_MAP[TYPE_DOUBLE]
+
+ expected = PackInfo.align expected, alignments[3]
+ expected += PackInfo::SIZE_MAP[TYPE_BOOL]
+
expected = PackInfo.align expected, alignments.max
assert_equal expected, size
diff --git a/test/fiddle/test_closure.rb b/test/fiddle/test_closure.rb
index 2de0660725..abb6bdbd32 100644
--- a/test/fiddle/test_closure.rb
+++ b/test/fiddle/test_closure.rb
@@ -6,6 +6,17 @@ end
module Fiddle
class TestClosure < Fiddle::TestCase
+ def teardown
+ super
+ # Ensure freeing all closures.
+ # See https://github.com/ruby/fiddle/issues/102#issuecomment-1241763091 .
+ not_freed_closures = []
+ ObjectSpace.each_object(Fiddle::Closure) do |closure|
+ not_freed_closures << closure unless closure.freed?
+ end
+ assert_equal([], not_freed_closures)
+ end
+
def test_argument_errors
assert_raise(TypeError) do
Closure.new(TYPE_INT, TYPE_INT)
@@ -20,41 +31,97 @@ module Fiddle
end
end
+ def test_type_symbol
+ Closure.create(:int, [:void]) do |closure|
+ assert_equal([
+ TYPE_INT,
+ [TYPE_VOID],
+ ],
+ [
+ closure.instance_variable_get(:@ctype),
+ closure.instance_variable_get(:@args),
+ ])
+ end
+ end
+
def test_call
- closure = Class.new(Closure) {
+ closure_class = Class.new(Closure) do
def call
10
end
- }.new(TYPE_INT, [])
-
- func = Function.new(closure, [], TYPE_INT)
- assert_equal 10, func.call
+ end
+ closure_class.create(TYPE_INT, []) do |closure|
+ func = Function.new(closure, [], TYPE_INT)
+ assert_equal 10, func.call
+ end
end
def test_returner
- closure = Class.new(Closure) {
+ closure_class = Class.new(Closure) do
def call thing
thing
end
- }.new(TYPE_INT, [TYPE_INT])
+ end
+ closure_class.create(TYPE_INT, [TYPE_INT]) do |closure|
+ func = Function.new(closure, [TYPE_INT], TYPE_INT)
+ assert_equal 10, func.call(10)
+ end
+ end
- func = Function.new(closure, [TYPE_INT], TYPE_INT)
- assert_equal 10, func.call(10)
+ def test_const_string
+ closure_class = Class.new(Closure) do
+ def call(string)
+ @return_string = "Hello! #{string}"
+ @return_string
+ end
+ end
+ closure_class.create(:const_string, [:const_string]) do |closure|
+ func = Function.new(closure, [:const_string], :const_string)
+ assert_equal("Hello! World!", func.call("World!"))
+ end
+ end
+
+ def test_bool
+ closure_class = Class.new(Closure) do
+ def call(bool)
+ not bool
+ end
+ end
+ closure_class.create(:bool, [:bool]) do |closure|
+ func = Function.new(closure, [:bool], :bool)
+ assert_equal(false, func.call(true))
+ end
+ end
+
+ def test_free
+ Closure.create(:int, [:void]) do |closure|
+ assert(!closure.freed?)
+ closure.free
+ assert(closure.freed?)
+ closure.free
+ end
end
def test_block_caller
cb = Closure::BlockCaller.new(TYPE_INT, [TYPE_INT]) do |one|
one
end
- func = Function.new(cb, [TYPE_INT], TYPE_INT)
- assert_equal 11, func.call(11)
+ begin
+ func = Function.new(cb, [TYPE_INT], TYPE_INT)
+ assert_equal 11, func.call(11)
+ ensure
+ cb.free
+ end
end
- def test_memsize
+ def test_memsize_ruby_dev_42480
require 'objspace'
- bug = '[ruby-dev:42480]'
n = 10000
- assert_equal(n, n.times {ObjectSpace.memsize_of(Closure.allocate)}, bug)
+ n.times do
+ Closure.create(:int, [:void]) do |closure|
+ ObjectSpace.memsize_of(closure)
+ end
+ end
end
%w[INT SHORT CHAR LONG LONG_LONG].each do |name|
@@ -64,20 +131,21 @@ module Fiddle
define_method("test_conversion_#{n.downcase}") do
arg = nil
- clos = Class.new(Closure) do
+ closure_class = Class.new(Closure) do
define_method(:call) {|x| arg = x}
- end.new(t, [t])
+ end
+ closure_class.create(t, [t]) do |closure|
+ v = ~(~0 << (8*s))
- v = ~(~0 << (8*s))
+ arg = nil
+ assert_equal(v, closure.call(v))
+ assert_equal(arg, v, n)
- arg = nil
- assert_equal(v, clos.call(v))
- assert_equal(arg, v, n)
-
- arg = nil
- func = Function.new(clos, [t], t)
- assert_equal(v, func.call(v))
- assert_equal(arg, v, n)
+ arg = nil
+ func = Function.new(closure, [t], t)
+ assert_equal(v, func.call(v))
+ assert_equal(arg, v, n)
+ end
end
end
end
diff --git a/test/fiddle/test_cparser.rb b/test/fiddle/test_cparser.rb
index ef8cec5daa..f1b67476ba 100644
--- a/test/fiddle/test_cparser.rb
+++ b/test/fiddle/test_cparser.rb
@@ -12,53 +12,117 @@ module Fiddle
def test_char_ctype
assert_equal(TYPE_CHAR, parse_ctype('char'))
+ assert_equal(TYPE_CHAR, parse_ctype('const char'))
assert_equal(TYPE_CHAR, parse_ctype('signed char'))
+ assert_equal(TYPE_CHAR, parse_ctype('const signed char'))
assert_equal(-TYPE_CHAR, parse_ctype('unsigned char'))
+ assert_equal(-TYPE_CHAR, parse_ctype('const unsigned char'))
end
def test_short_ctype
assert_equal(TYPE_SHORT, parse_ctype('short'))
+ assert_equal(TYPE_SHORT, parse_ctype('const short'))
assert_equal(TYPE_SHORT, parse_ctype('short int'))
+ assert_equal(TYPE_SHORT, parse_ctype('const short int'))
+ assert_equal(TYPE_SHORT, parse_ctype('int short'))
+ assert_equal(TYPE_SHORT, parse_ctype('const int short'))
assert_equal(TYPE_SHORT, parse_ctype('signed short'))
+ assert_equal(TYPE_SHORT, parse_ctype('const signed short'))
+ assert_equal(TYPE_SHORT, parse_ctype('short signed'))
+ assert_equal(TYPE_SHORT, parse_ctype('const short signed'))
assert_equal(TYPE_SHORT, parse_ctype('signed short int'))
+ assert_equal(TYPE_SHORT, parse_ctype('const signed short int'))
+ assert_equal(TYPE_SHORT, parse_ctype('signed int short'))
+ assert_equal(TYPE_SHORT, parse_ctype('const signed int short'))
+ assert_equal(TYPE_SHORT, parse_ctype('int signed short'))
+ assert_equal(TYPE_SHORT, parse_ctype('const int signed short'))
+ assert_equal(TYPE_SHORT, parse_ctype('int short signed'))
+ assert_equal(TYPE_SHORT, parse_ctype('const int short signed'))
assert_equal(-TYPE_SHORT, parse_ctype('unsigned short'))
+ assert_equal(-TYPE_SHORT, parse_ctype('const unsigned short'))
assert_equal(-TYPE_SHORT, parse_ctype('unsigned short int'))
+ assert_equal(-TYPE_SHORT, parse_ctype('const unsigned short int'))
+ assert_equal(-TYPE_SHORT, parse_ctype('unsigned int short'))
+ assert_equal(-TYPE_SHORT, parse_ctype('const unsigned int short'))
+ assert_equal(-TYPE_SHORT, parse_ctype('short int unsigned'))
+ assert_equal(-TYPE_SHORT, parse_ctype('const short int unsigned'))
+ assert_equal(-TYPE_SHORT, parse_ctype('int unsigned short'))
+ assert_equal(-TYPE_SHORT, parse_ctype('const int unsigned short'))
+ assert_equal(-TYPE_SHORT, parse_ctype('int short unsigned'))
+ assert_equal(-TYPE_SHORT, parse_ctype('const int short unsigned'))
end
def test_int_ctype
assert_equal(TYPE_INT, parse_ctype('int'))
+ assert_equal(TYPE_INT, parse_ctype('const int'))
assert_equal(TYPE_INT, parse_ctype('signed int'))
+ assert_equal(TYPE_INT, parse_ctype('const signed int'))
assert_equal(-TYPE_INT, parse_ctype('uint'))
+ assert_equal(-TYPE_INT, parse_ctype('const uint'))
assert_equal(-TYPE_INT, parse_ctype('unsigned int'))
+ assert_equal(-TYPE_INT, parse_ctype('const unsigned int'))
end
def test_long_ctype
assert_equal(TYPE_LONG, parse_ctype('long'))
+ assert_equal(TYPE_LONG, parse_ctype('const long'))
assert_equal(TYPE_LONG, parse_ctype('long int'))
+ assert_equal(TYPE_LONG, parse_ctype('const long int'))
+ assert_equal(TYPE_LONG, parse_ctype('int long'))
+ assert_equal(TYPE_LONG, parse_ctype('const int long'))
assert_equal(TYPE_LONG, parse_ctype('signed long'))
+ assert_equal(TYPE_LONG, parse_ctype('const signed long'))
assert_equal(TYPE_LONG, parse_ctype('signed long int'))
+ assert_equal(TYPE_LONG, parse_ctype('const signed long int'))
+ assert_equal(TYPE_LONG, parse_ctype('signed int long'))
+ assert_equal(TYPE_LONG, parse_ctype('const signed int long'))
+ assert_equal(TYPE_LONG, parse_ctype('long signed'))
+ assert_equal(TYPE_LONG, parse_ctype('const long signed'))
+ assert_equal(TYPE_LONG, parse_ctype('long int signed'))
+ assert_equal(TYPE_LONG, parse_ctype('const long int signed'))
+ assert_equal(TYPE_LONG, parse_ctype('int long signed'))
+ assert_equal(TYPE_LONG, parse_ctype('const int long signed'))
assert_equal(-TYPE_LONG, parse_ctype('unsigned long'))
+ assert_equal(-TYPE_LONG, parse_ctype('const unsigned long'))
assert_equal(-TYPE_LONG, parse_ctype('unsigned long int'))
+ assert_equal(-TYPE_LONG, parse_ctype('const unsigned long int'))
+ assert_equal(-TYPE_LONG, parse_ctype('long int unsigned'))
+ assert_equal(-TYPE_LONG, parse_ctype('const long int unsigned'))
+ assert_equal(-TYPE_LONG, parse_ctype('unsigned int long'))
+ assert_equal(-TYPE_LONG, parse_ctype('const unsigned int long'))
+ assert_equal(-TYPE_LONG, parse_ctype('int unsigned long'))
+ assert_equal(-TYPE_LONG, parse_ctype('const int unsigned long'))
+ assert_equal(-TYPE_LONG, parse_ctype('int long unsigned'))
+ assert_equal(-TYPE_LONG, parse_ctype('const int long unsigned'))
end
def test_size_t_ctype
assert_equal(TYPE_SIZE_T, parse_ctype("size_t"))
+ assert_equal(TYPE_SIZE_T, parse_ctype("const size_t"))
end
def test_ssize_t_ctype
assert_equal(TYPE_SSIZE_T, parse_ctype("ssize_t"))
+ assert_equal(TYPE_SSIZE_T, parse_ctype("const ssize_t"))
end
def test_ptrdiff_t_ctype
assert_equal(TYPE_PTRDIFF_T, parse_ctype("ptrdiff_t"))
+ assert_equal(TYPE_PTRDIFF_T, parse_ctype("const ptrdiff_t"))
end
def test_intptr_t_ctype
assert_equal(TYPE_INTPTR_T, parse_ctype("intptr_t"))
+ assert_equal(TYPE_INTPTR_T, parse_ctype("const intptr_t"))
end
def test_uintptr_t_ctype
assert_equal(TYPE_UINTPTR_T, parse_ctype("uintptr_t"))
+ assert_equal(TYPE_UINTPTR_T, parse_ctype("const uintptr_t"))
+ end
+
+ def test_bool_ctype
+ assert_equal(TYPE_BOOL, parse_ctype('bool'))
end
def test_undefined_ctype
@@ -66,7 +130,10 @@ module Fiddle
end
def test_undefined_ctype_with_type_alias
- assert_equal(-TYPE_LONG, parse_ctype('DWORD', {"DWORD" => "unsigned long"}))
+ assert_equal(-TYPE_LONG,
+ parse_ctype('DWORD', {"DWORD" => "unsigned long"}))
+ assert_equal(-TYPE_LONG,
+ parse_ctype('const DWORD', {"DWORD" => "unsigned long"}))
end
def expand_struct_types(types)
@@ -83,11 +150,21 @@ module Fiddle
end
def test_struct_basic
- assert_equal [[TYPE_INT, TYPE_CHAR], ['i', 'c']], parse_struct_signature(['int i', 'char c'])
+ assert_equal([[TYPE_INT, TYPE_CHAR], ['i', 'c']],
+ parse_struct_signature(['int i', 'char c']))
+ assert_equal([[TYPE_INT, TYPE_CHAR], ['i', 'c']],
+ parse_struct_signature(['const int i', 'const char c']))
end
def test_struct_array
- assert_equal [[[TYPE_CHAR,80],[TYPE_INT,5]], ['buffer','x']], parse_struct_signature(['char buffer[80]', 'int[5] x'])
+ assert_equal([[[TYPE_CHAR, 80], [TYPE_INT, 5]],
+ ['buffer', 'x']],
+ parse_struct_signature(['char buffer[80]',
+ 'int[5] x']))
+ assert_equal([[[TYPE_CHAR, 80], [TYPE_INT, 5]],
+ ['buffer', 'x']],
+ parse_struct_signature(['const char buffer[80]',
+ 'const int[5] x']))
end
def test_struct_nested_struct
@@ -178,15 +255,22 @@ module Fiddle
end
def test_struct_array_str
- assert_equal [[[TYPE_CHAR,80],[TYPE_INT,5]], ['buffer','x']], parse_struct_signature('char buffer[80], int[5] x')
+ assert_equal([[[TYPE_CHAR, 80], [TYPE_INT, 5]],
+ ['buffer', 'x']],
+ parse_struct_signature('char buffer[80], int[5] x'))
+ assert_equal([[[TYPE_CHAR, 80], [TYPE_INT, 5]],
+ ['buffer', 'x']],
+ parse_struct_signature('const char buffer[80], const int[5] x'))
end
def test_struct_function_pointer
- assert_equal [[TYPE_VOIDP], ['cb']], parse_struct_signature(['void (*cb)(const char*)'])
+ assert_equal([[TYPE_VOIDP], ['cb']],
+ parse_struct_signature(['void (*cb)(const char*)']))
end
def test_struct_function_pointer_str
- assert_equal [[TYPE_VOIDP,TYPE_VOIDP], ['cb', 'data']], parse_struct_signature('void (*cb)(const char*), const char* data')
+ assert_equal([[TYPE_VOIDP, TYPE_VOIDP], ['cb', 'data']],
+ parse_struct_signature('void (*cb)(const char*), const char* data'))
end
def test_struct_string
@@ -282,7 +366,7 @@ module Fiddle
def test_signature_variadic_arguments
unless Fiddle.const_defined?("TYPE_VARIADIC")
- skip "libffi doesn't support variadic arguments"
+ omit "libffi doesn't support variadic arguments"
end
assert_equal([
"printf",
diff --git a/test/fiddle/test_fiddle.rb b/test/fiddle/test_fiddle.rb
index 8751d96920..9bddb056c9 100644
--- a/test/fiddle/test_fiddle.rb
+++ b/test/fiddle/test_fiddle.rb
@@ -5,6 +5,13 @@ rescue LoadError
end
class TestFiddle < Fiddle::TestCase
+ def test_nil_true_etc
+ assert_equal Fiddle::Qtrue, Fiddle.dlwrap(true)
+ assert_equal Fiddle::Qfalse, Fiddle.dlwrap(false)
+ assert_equal Fiddle::Qnil, Fiddle.dlwrap(nil)
+ assert Fiddle::Qundef
+ end
+
def test_windows_constant
require 'rbconfig'
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
@@ -14,4 +21,38 @@ class TestFiddle < Fiddle::TestCase
end
end
+ def test_dlopen_linker_script_input_linux
+ omit("This is only for Linux") unless RUBY_PLATFORM.match?("linux")
+ if Dir.glob("/usr/lib/*/libncurses.so").empty?
+ omit("libncurses.so is needed")
+ end
+ # libncurses.so uses INPUT() on Debian GNU/Linux
+ # $ cat /usr/lib/x86_64-linux-gnu/libncurses.so
+ # INPUT(libncurses.so.6 -ltinfo)
+ handle = Fiddle.dlopen("libncurses.so")
+ begin
+ assert_equal("libncurses.so",
+ File.basename(handle.file_name, ".*"))
+ ensure
+ handle.close
+ end
+ end
+
+ def test_dlopen_linker_script_group_linux
+ omit("This is only for Linux") unless RUBY_PLATFORM.match?("linux")
+ # libc.so uses GROUP() on Debian GNU/Linux
+ # $ cat /usr/lib/x86_64-linux-gnu/libc.so
+ # /* GNU ld script
+ # Use the shared library, but some functions are only in
+ # the static library, so try that secondarily. */
+ # OUTPUT_FORMAT(elf64-x86-64)
+ # GROUP ( /lib/x86_64-linux-gnu/libc.so.6 /usr/lib/x86_64-linux-gnu/libc_nonshared.a AS_NEEDED ( /lib64/ld-linux-x86-64.so.2 ) )
+ handle = Fiddle.dlopen("libc.so")
+ begin
+ assert_equal("libc.so",
+ File.basename(handle.file_name, ".*"))
+ ensure
+ handle.close
+ end
+ end
end if defined?(Fiddle)
diff --git a/test/fiddle/test_func.rb b/test/fiddle/test_func.rb
index d3604c79c3..df79539e76 100644
--- a/test/fiddle/test_func.rb
+++ b/test/fiddle/test_func.rb
@@ -15,7 +15,7 @@ module Fiddle
begin
f = Function.new(@libm['sinf'], [TYPE_FLOAT], TYPE_FLOAT)
rescue Fiddle::DLError
- skip "libm may not have sinf()"
+ omit "libm may not have sinf()"
end
assert_in_delta 1.0, f.call(90 * Math::PI / 180), 0.0001
end
@@ -26,14 +26,13 @@ module Fiddle
end
def test_string
- stress, GC.stress = GC.stress, true
- f = Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP)
- buff = +"000"
- str = f.call(buff, "123")
- assert_equal("123", buff)
- assert_equal("123", str.to_s)
- ensure
- GC.stress = stress
+ under_gc_stress do
+ f = Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP)
+ buff = +"000"
+ str = f.call(buff, "123")
+ assert_equal("123", buff)
+ assert_equal("123", str.to_s)
+ end
end
def test_isdigit
@@ -61,28 +60,40 @@ module Fiddle
end
def test_qsort1
- cb = Class.new(Closure) {
+ closure_class = Class.new(Closure) do
def call(x, y)
Pointer.new(x)[0] <=> Pointer.new(y)[0]
end
- }.new(TYPE_INT, [TYPE_VOIDP, TYPE_VOIDP])
+ end
- qsort = Function.new(@libc['qsort'],
- [TYPE_VOIDP, TYPE_SIZE_T, TYPE_SIZE_T, TYPE_VOIDP],
- TYPE_VOID)
- buff = "9341"
- qsort.call(buff, buff.size, 1, cb)
- assert_equal("1349", buff)
+ closure_class.create(TYPE_INT, [TYPE_VOIDP, TYPE_VOIDP]) do |callback|
+ qsort = Function.new(@libc['qsort'],
+ [TYPE_VOIDP, TYPE_SIZE_T, TYPE_SIZE_T, TYPE_VOIDP],
+ TYPE_VOID)
+ buff = "9341"
+ qsort.call(buff, buff.size, 1, callback)
+ assert_equal("1349", buff)
- bug4929 = '[ruby-core:37395]'
- buff = "9341"
- EnvUtil.under_gc_stress {qsort.call(buff, buff.size, 1, cb)}
- assert_equal("1349", buff, bug4929)
+ bug4929 = '[ruby-core:37395]'
+ buff = "9341"
+ under_gc_stress do
+ qsort.call(buff, buff.size, 1, callback)
+ end
+ assert_equal("1349", buff, bug4929)
+ end
+ ensure
+ # Ensure freeing all closures.
+ # See https://github.com/ruby/fiddle/issues/102#issuecomment-1241763091 .
+ not_freed_closures = []
+ ObjectSpace.each_object(Fiddle::Closure) do |closure|
+ not_freed_closures << closure unless closure.freed?
+ end
+ assert_equal([], not_freed_closures)
end
def test_snprintf
unless Fiddle.const_defined?("TYPE_VARIADIC")
- skip "libffi doesn't support variadic arguments"
+ omit "libffi doesn't support variadic arguments"
end
if Fiddle::WINDOWS
snprintf_name = "_snprintf"
@@ -92,7 +103,7 @@ module Fiddle
begin
snprintf_pointer = @libc[snprintf_name]
rescue Fiddle::DLError
- skip "Can't find #{snprintf_name}: #{$!.message}"
+ omit "Can't find #{snprintf_name}: #{$!.message}"
end
snprintf = Function.new(snprintf_pointer,
[
@@ -134,5 +145,22 @@ module Fiddle
assert_equal("string: He, const string: World, uint: 29\n",
output_buffer[0, written])
end
+
+ def test_rb_memory_view_available_p
+ omit "MemoryView is unavailable" unless defined? Fiddle::MemoryView
+ libruby = Fiddle.dlopen(nil)
+ case Fiddle::SIZEOF_VOIDP
+ when Fiddle::SIZEOF_LONG_LONG
+ value_type = -Fiddle::TYPE_LONG_LONG
+ else
+ value_type = -Fiddle::TYPE_LONG
+ end
+ rb_memory_view_available_p =
+ Function.new(libruby["rb_memory_view_available_p"],
+ [value_type],
+ :bool,
+ need_gvl: true)
+ assert_equal(false, rb_memory_view_available_p.call(Fiddle::Qnil))
+ end
end
end if defined?(Fiddle)
diff --git a/test/fiddle/test_function.rb b/test/fiddle/test_function.rb
index a5284e093a..847df3793a 100644
--- a/test/fiddle/test_function.rb
+++ b/test/fiddle/test_function.rb
@@ -9,6 +9,20 @@ module Fiddle
def setup
super
Fiddle.last_error = nil
+ if WINDOWS
+ Fiddle.win32_last_error = nil
+ Fiddle.win32_last_socket_error = nil
+ end
+ end
+
+ def teardown
+ # Ensure freeing all closures.
+ # See https://github.com/ruby/fiddle/issues/102#issuecomment-1241763091 .
+ not_freed_closures = []
+ ObjectSpace.each_object(Fiddle::Closure) do |closure|
+ not_freed_closures << closure unless closure.freed?
+ end
+ assert_equal([], not_freed_closures)
end
def test_default_abi
@@ -71,18 +85,20 @@ module Fiddle
end
def test_argument_count
- closure = Class.new(Closure) {
+ closure_class = Class.new(Closure) do
def call one
10 + one
end
- }.new(TYPE_INT, [TYPE_INT])
- func = Function.new(closure, [TYPE_INT], TYPE_INT)
-
- assert_raise(ArgumentError) do
- func.call(1,2,3)
end
- assert_raise(ArgumentError) do
- func.call
+ closure_class.create(TYPE_INT, [TYPE_INT]) do |closure|
+ func = Function.new(closure, [TYPE_INT], TYPE_INT)
+
+ assert_raise(ArgumentError) do
+ func.call(1,2,3)
+ end
+ assert_raise(ArgumentError) do
+ func.call
+ end
end
end
@@ -94,6 +110,30 @@ module Fiddle
refute_nil Fiddle.last_error
end
+ if WINDOWS
+ def test_win32_last_error
+ kernel32 = Fiddle.dlopen("kernel32")
+ args = [kernel32["SetLastError"], [-TYPE_LONG], TYPE_VOID]
+ args << Function::STDCALL if Function.const_defined?(:STDCALL)
+ set_last_error = Function.new(*args)
+ assert_nil(Fiddle.win32_last_error)
+ n = 1 << 29 | 1
+ set_last_error.call(n)
+ assert_equal(n, Fiddle.win32_last_error)
+ end
+
+ def test_win32_last_socket_error
+ ws2_32 = Fiddle.dlopen("ws2_32")
+ args = [ws2_32["WSASetLastError"], [TYPE_INT], TYPE_VOID]
+ args << Function::STDCALL if Function.const_defined?(:STDCALL)
+ wsa_set_last_error = Function.new(*args)
+ assert_nil(Fiddle.win32_last_socket_error)
+ n = 1 << 29 | 1
+ wsa_set_last_error.call(n)
+ assert_equal(n, Fiddle.win32_last_socket_error)
+ end
+ end
+
def test_strcpy
f = Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP)
buff = +"000"
@@ -136,7 +176,7 @@ module Fiddle
begin
poll = @libc['poll']
rescue Fiddle::DLError
- skip 'poll(2) not available'
+ omit 'poll(2) not available'
end
f = Function.new(poll, [TYPE_VOIDP, TYPE_INT, TYPE_INT], TYPE_INT)
@@ -152,9 +192,37 @@ module Fiddle
end
def test_no_memory_leak
- prep = 'r = Fiddle::Function.new(Fiddle.dlopen(nil)["rb_obj_frozen_p"], [Fiddle::TYPE_UINTPTR_T], Fiddle::TYPE_UINTPTR_T); a = "a"'
- code = 'begin r.call(a); rescue TypeError; end'
- assert_no_memory_leak(%w[-W0 -rfiddle], "#{prep}\n1000.times{#{code}}", "10_000.times {#{code}}", limit: 1.2)
+ if respond_to?(:assert_nothing_leaked_memory)
+ rb_obj_frozen_p_symbol = Fiddle.dlopen(nil)["rb_obj_frozen_p"]
+ rb_obj_frozen_p = Fiddle::Function.new(rb_obj_frozen_p_symbol,
+ [Fiddle::TYPE_UINTPTR_T],
+ Fiddle::TYPE_UINTPTR_T)
+ a = "a"
+ n_tries = 100_000
+ n_tries.times do
+ begin
+ a + 1
+ rescue TypeError
+ end
+ end
+ n_arguments = 1
+ sizeof_fiddle_generic = Fiddle::SIZEOF_VOIDP # Rough
+ size_per_try =
+ (sizeof_fiddle_generic * n_arguments) +
+ (Fiddle::SIZEOF_VOIDP * (n_arguments + 1))
+ assert_nothing_leaked_memory(size_per_try * n_tries) do
+ n_tries.times do
+ begin
+ rb_obj_frozen_p.call(a)
+ rescue TypeError
+ end
+ end
+ end
+ else
+ prep = 'r = Fiddle::Function.new(Fiddle.dlopen(nil)["rb_obj_frozen_p"], [Fiddle::TYPE_UINTPTR_T], Fiddle::TYPE_UINTPTR_T); a = "a"'
+ code = 'begin r.call(a); rescue TypeError; end'
+ assert_no_memory_leak(%w[-W0 -rfiddle], "#{prep}\n1000.times{#{code}}", "10_000.times {#{code}}", limit: 1.2)
+ end
end
private
diff --git a/test/fiddle/test_handle.rb b/test/fiddle/test_handle.rb
index 17f9c92a11..412c10e09d 100644
--- a/test/fiddle/test_handle.rb
+++ b/test/fiddle/test_handle.rb
@@ -13,15 +13,23 @@ module Fiddle
assert_kind_of Integer, handle.to_i
end
+ def test_to_ptr
+ handle = Fiddle::Handle.new(LIBC_SO)
+ ptr = handle.to_ptr
+ assert_equal ptr.to_i, handle.to_i
+ end
+
def test_static_sym_unknown
assert_raise(DLError) { Fiddle::Handle.sym('fooo') }
assert_raise(DLError) { Fiddle::Handle['fooo'] }
+ refute Fiddle::Handle.sym_defined?('fooo')
end
def test_static_sym
begin
# Linux / Darwin / FreeBSD
refute_nil Fiddle::Handle.sym('dlopen')
+ assert Fiddle::Handle.sym_defined?('dlopen')
assert_equal Fiddle::Handle.sym('dlopen'), Fiddle::Handle['dlopen']
return
rescue
@@ -48,6 +56,7 @@ module Fiddle
handle = Fiddle::Handle.new(LIBC_SO)
assert_raise(DLError) { handle.sym('fooo') }
assert_raise(DLError) { handle['fooo'] }
+ refute handle.sym_defined?('fooo')
end
def test_sym_with_bad_args
@@ -60,6 +69,7 @@ module Fiddle
handle = Handle.new(LIBC_SO)
refute_nil handle.sym('calloc')
refute_nil handle['calloc']
+ assert handle.sym_defined?('calloc')
end
def test_handle_close
@@ -106,6 +116,24 @@ module Fiddle
assert !handle.close_enabled?, 'close is enabled'
end
+ def test_file_name
+ file_name = Handle.new(LIBC_SO).file_name
+ if file_name
+ assert_kind_of String, file_name
+ expected = [File.basename(LIBC_SO)]
+ begin
+ expected << File.basename(File.realpath(LIBC_SO, File.dirname(file_name)))
+ rescue Errno::ENOENT
+ end
+ basename = File.basename(file_name)
+ unless File::FNM_SYSCASE.zero?
+ basename.downcase!
+ expected.each(&:downcase!)
+ end
+ assert_include expected, basename
+ end
+ end
+
def test_NEXT
begin
# Linux / Darwin
@@ -155,13 +183,28 @@ module Fiddle
# it calls _nss_cache_cycle_prevention_function with dlsym(3).
# So our Fiddle::Handle#sym must call dlerror(3) before call dlsym.
# In general uses of dlerror(3) should call it before use it.
+ verbose, $VERBOSE = $VERBOSE, nil
require 'socket'
Socket.gethostbyname("localhost")
Fiddle.dlopen("/lib/libc.so.7").sym('strcpy')
+ ensure
+ $VERBOSE = verbose
end if /freebsd/=~ RUBY_PLATFORM
def test_no_memory_leak
- assert_no_memory_leak(%w[-W0 -rfiddle.so], '', '100_000.times {Fiddle::Handle.allocate}; GC.start', rss: true)
+ # https://github.com/ruby/fiddle/actions/runs/3202406059/jobs/5231356410
+ omit if RUBY_VERSION >= '3.2'
+
+ if respond_to?(:assert_nothing_leaked_memory)
+ n_tries = 100_000
+ assert_nothing_leaked_memory(SIZEOF_VOIDP * (n_tries / 100)) do
+ n_tries.times do
+ Fiddle::Handle.allocate
+ end
+ end
+ else
+ assert_no_memory_leak(%w[-W0 -rfiddle.so], '', '100_000.times {Fiddle::Handle.allocate}; GC.start', rss: true)
+ end
end
if /cygwin|mingw|mswin/ =~ RUBY_PLATFORM
diff --git a/test/fiddle/test_import.rb b/test/fiddle/test_import.rb
index afa8df9e00..090ace620d 100644
--- a/test/fiddle/test_import.rb
+++ b/test/fiddle/test_import.rb
@@ -22,7 +22,6 @@ module Fiddle
extern "int fprintf(FILE*, char*)" rescue nil
extern "int gettimeofday(timeval*, timezone*)" rescue nil
- BoundQsortCallback = bind("void *bound_qsort_callback(void*, void*)"){|ptr1,ptr2| ptr1[0] <=> ptr2[0]}
Timeval = struct [
"long tv_sec",
"long tv_usec",
@@ -59,11 +58,6 @@ module Fiddle
]
}
]
-
- CallCallback = bind("void call_callback(void*, void*)"){ | ptr1, ptr2|
- f = Function.new(ptr1.to_i, [TYPE_VOIDP], TYPE_VOID)
- f.call(ptr2)
- }
end
class TestImport < TestCase
@@ -130,11 +124,28 @@ module Fiddle
name = $1.sub(/P\z/,"*").gsub(/_(?!T\z)/, " ").downcase
type_name = name
end
+ type_name = "unsigned #{$1}" if type_name =~ /\Au(long|short|char|int|long long)\z/
+
define_method("test_sizeof_#{name}") do
assert_equal(size, Fiddle::Importer.sizeof(type_name), type)
end
end
+ # Assert that the unsigned constants are equal to the "negative" signed ones
+ # for backwards compatibility
+ def test_unsigned_equals_negative_signed
+ Fiddle.constants.grep(/\ATYPE_(?!VOID|VARIADIC\z)(U.*)/) do |unsigned|
+ assert_equal(-Fiddle.const_get(unsigned.to_s.sub(/U/, '')),
+ Fiddle.const_get(unsigned))
+ end
+ end
+
+ def test_type_constants
+ Fiddle::Types.constants.each do |const|
+ assert_equal Fiddle::Types.const_get(const), Fiddle.const_get("TYPE_#{const}")
+ end
+ end
+
def test_unsigned_result()
d = (2 ** 31) + 1
diff --git a/test/fiddle/test_memory_view.rb b/test/fiddle/test_memory_view.rb
index c673d2633a..240cda37df 100644
--- a/test/fiddle/test_memory_view.rb
+++ b/test/fiddle/test_memory_view.rb
@@ -2,17 +2,19 @@
begin
require_relative 'helper'
rescue LoadError
+ return
end
begin
require '-test-/memory_view'
rescue LoadError
+ return
end
module Fiddle
class TestMemoryView < TestCase
def setup
- skip "MemoryView is unavailable" unless defined? Fiddle::MemoryView
+ omit "MemoryView is unavailable" unless defined? Fiddle::MemoryView
end
def test_null_ptr
@@ -47,7 +49,7 @@ module Fiddle
end
def test_memory_view_multi_dimensional
- skip "MemoryViewTestUtils is unavailable" unless defined? MemoryViewTestUtils
+ omit "MemoryViewTestUtils is unavailable" unless defined? MemoryViewTestUtils
buf = [ 1, 2, 3, 4,
5, 6, 7, 8,
@@ -69,7 +71,7 @@ module Fiddle
end
def test_memory_view_multi_dimensional_with_strides
- skip "MemoryViewTestUtils is unavailable" unless defined? MemoryViewTestUtils
+ omit "MemoryViewTestUtils is unavailable" unless defined? MemoryViewTestUtils
buf = [ 1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11, 12, 13, 14, 15, 16 ].pack("l!*")
@@ -91,7 +93,7 @@ module Fiddle
end
def test_memory_view_multi_dimensional_with_multiple_members
- skip "MemoryViewTestUtils is unavailable" unless defined? MemoryViewTestUtils
+ omit "MemoryViewTestUtils is unavailable" unless defined? MemoryViewTestUtils
buf = [ 1, 2, 3, 4, 5, 6, 7, 8,
-1, -2, -3, -4, -5, -6, -7, -8].pack("s*")
@@ -111,5 +113,31 @@ module Fiddle
assert_equal([-1, -2], mview[1, 0])
assert_equal([-7, -8], mview[1, 3])
end
+
+ def test_export
+ str = "hello world"
+ mview_str = MemoryView.export(Pointer[str]) do |mview|
+ mview.to_s
+ end
+ assert_equal(str, mview_str)
+ end
+
+ def test_release
+ ptr = Pointer["hello world"]
+ mview = MemoryView.new(ptr)
+ assert_same(ptr, mview.obj)
+ mview.release
+ assert_nil(mview.obj)
+ end
+
+ def test_to_s
+ # U+3042 HIRAGANA LETTER A
+ data = "\u{3042}"
+ ptr = Pointer[data]
+ mview = MemoryView.new(ptr)
+ string = mview.to_s
+ assert_equal([data.b, true],
+ [string, string.frozen?])
+ end
end
end
diff --git a/test/fiddle/test_pack.rb b/test/fiddle/test_pack.rb
new file mode 100644
index 0000000000..ade1dd5040
--- /dev/null
+++ b/test/fiddle/test_pack.rb
@@ -0,0 +1,37 @@
+begin
+ require_relative 'helper'
+ require 'fiddle/pack'
+rescue LoadError
+ return
+end
+
+module Fiddle
+ class TestPack < TestCase
+ def test_pack_map
+ if defined?(TYPE_LONG_LONG)
+ assert_equal [0xffff_ffff_ffff_ffff], [0xffff_ffff_ffff_ffff].pack(PackInfo::PACK_MAP[-TYPE_LONG_LONG]).unpack(PackInfo::PACK_MAP[-TYPE_LONG_LONG])
+ end
+
+ case Fiddle::SIZEOF_VOIDP
+ when 8
+ assert_equal [0xffff_ffff_ffff_ffff], [0xffff_ffff_ffff_ffff].pack(PackInfo::PACK_MAP[TYPE_VOIDP]).unpack(PackInfo::PACK_MAP[TYPE_VOIDP])
+ when 4
+ assert_equal [0xffff_ffff], [0xffff_ffff].pack(PackInfo::PACK_MAP[TYPE_VOIDP]).unpack(PackInfo::PACK_MAP[TYPE_VOIDP])
+ end
+
+ case Fiddle::SIZEOF_LONG
+ when 8
+ assert_equal [0xffff_ffff_ffff_ffff], [0xffff_ffff_ffff_ffff].pack(PackInfo::PACK_MAP[-TYPE_LONG]).unpack(PackInfo::PACK_MAP[-TYPE_LONG])
+ when 4
+ assert_equal [0xffff_ffff], [0xffff_ffff].pack(PackInfo::PACK_MAP[-TYPE_LONG]).unpack(PackInfo::PACK_MAP[-TYPE_LONG])
+ end
+
+ if Fiddle::SIZEOF_INT == 4
+ assert_equal [0xffff_ffff], [0xffff_ffff].pack(PackInfo::PACK_MAP[-TYPE_INT]).unpack(PackInfo::PACK_MAP[-TYPE_INT])
+ end
+
+ assert_equal [0xffff], [0xffff].pack(PackInfo::PACK_MAP[-TYPE_SHORT]).unpack(PackInfo::PACK_MAP[-TYPE_SHORT])
+ assert_equal [0xff], [0xff].pack(PackInfo::PACK_MAP[-TYPE_CHAR]).unpack(PackInfo::PACK_MAP[-TYPE_CHAR])
+ end
+ end
+end
diff --git a/test/fiddle/test_pinned.rb b/test/fiddle/test_pinned.rb
index 5bfae2172a..f0d375b1cc 100644
--- a/test/fiddle/test_pinned.rb
+++ b/test/fiddle/test_pinned.rb
@@ -2,6 +2,7 @@
begin
require_relative 'helper'
rescue LoadError
+ return
end
module Fiddle
diff --git a/test/fiddle/test_pointer.rb b/test/fiddle/test_pointer.rb
index 88dad75138..f2c1d285ad 100644
--- a/test/fiddle/test_pointer.rb
+++ b/test/fiddle/test_pointer.rb
@@ -10,6 +10,22 @@ module Fiddle
Fiddle.dlwrap arg
end
+ def test_can_read_write_memory
+ # Allocate some memory
+ address = Fiddle.malloc(Fiddle::SIZEOF_VOIDP)
+
+ bytes_to_write = Fiddle::SIZEOF_VOIDP.times.to_a.pack("C*")
+
+ # Write to the memory
+ Fiddle::Pointer.write(address, bytes_to_write)
+
+ # Read the bytes out again
+ bytes = Fiddle::Pointer.read(address, Fiddle::SIZEOF_VOIDP)
+ assert_equal bytes_to_write, bytes
+ ensure
+ Fiddle.free address
+ end
+
def test_cptr_to_int
null = Fiddle::NULL
assert_equal(null.to_i, null.to_int)
@@ -179,16 +195,6 @@ module Fiddle
end
def test_free=
- assert_normal_exit(<<-"End", '[ruby-dev:39269]')
- require 'fiddle'
- include Fiddle
- free = Fiddle::Function.new(Fiddle::RUBY_FREE, [TYPE_VOIDP], TYPE_VOID)
- ptr = Fiddle::Pointer.malloc(4)
- ptr.free = free
- free.ptr
- ptr.free.ptr
- End
-
free = Function.new(Fiddle::RUBY_FREE, [TYPE_VOIDP], TYPE_VOID)
ptr = Pointer.malloc(4)
ptr.free = free
@@ -282,7 +288,19 @@ module Fiddle
end
def test_no_memory_leak
- assert_no_memory_leak(%w[-W0 -rfiddle.so], '', '100_000.times {Fiddle::Pointer.allocate}', rss: true)
+ # https://github.com/ruby/fiddle/actions/runs/3202406059/jobs/5231356410
+ omit if RUBY_VERSION >= '3.2'
+
+ if respond_to?(:assert_nothing_leaked_memory)
+ n_tries = 100_000
+ assert_nothing_leaked_memory(SIZEOF_VOIDP * (n_tries / 100)) do
+ n_tries.times do
+ Fiddle::Pointer.allocate
+ end
+ end
+ else
+ assert_no_memory_leak(%w[-W0 -rfiddle.so], '', '100_000.times {Fiddle::Pointer.allocate}', rss: true)
+ end
end
end
end if defined?(Fiddle)