diff options
Diffstat (limited to 'test/fiddle')
-rw-r--r-- | test/fiddle/helper.rb | 30 | ||||
-rw-r--r-- | test/fiddle/test_c_struct_builder.rb | 69 | ||||
-rw-r--r-- | test/fiddle/test_c_struct_entry.rb | 8 | ||||
-rw-r--r-- | test/fiddle/test_closure.rb | 118 | ||||
-rw-r--r-- | test/fiddle/test_cparser.rb | 98 | ||||
-rw-r--r-- | test/fiddle/test_fiddle.rb | 41 | ||||
-rw-r--r-- | test/fiddle/test_func.rb | 74 | ||||
-rw-r--r-- | test/fiddle/test_function.rb | 92 | ||||
-rw-r--r-- | test/fiddle/test_handle.rb | 45 | ||||
-rw-r--r-- | test/fiddle/test_import.rb | 23 | ||||
-rw-r--r-- | test/fiddle/test_memory_view.rb | 36 | ||||
-rw-r--r-- | test/fiddle/test_pack.rb | 37 | ||||
-rw-r--r-- | test/fiddle/test_pinned.rb | 1 | ||||
-rw-r--r-- | test/fiddle/test_pointer.rb | 40 |
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) |