summaryrefslogtreecommitdiff
path: root/test/fiddle
diff options
context:
space:
mode:
Diffstat (limited to 'test/fiddle')
-rw-r--r--test/fiddle/helper.rb178
-rw-r--r--test/fiddle/test_c_struct_builder.rb69
-rw-r--r--test/fiddle/test_c_struct_entry.rb165
-rw-r--r--test/fiddle/test_c_union_entity.rb36
-rw-r--r--test/fiddle/test_closure.rb110
-rw-r--r--test/fiddle/test_cparser.rb374
-rw-r--r--test/fiddle/test_fiddle.rb17
-rw-r--r--test/fiddle/test_func.rb139
-rw-r--r--test/fiddle/test_function.rb227
-rw-r--r--test/fiddle/test_handle.rb208
-rw-r--r--test/fiddle/test_import.rb479
-rw-r--r--test/fiddle/test_memory_view.rb143
-rw-r--r--test/fiddle/test_pinned.rb28
-rw-r--r--test/fiddle/test_pointer.rb287
14 files changed, 2460 insertions, 0 deletions
diff --git a/test/fiddle/helper.rb b/test/fiddle/helper.rb
new file mode 100644
index 0000000000..0ea3bf57f4
--- /dev/null
+++ b/test/fiddle/helper.rb
@@ -0,0 +1,178 @@
+# frozen_string_literal: true
+
+require 'rbconfig/sizeof'
+require 'test/unit'
+require 'fiddle'
+
+# FIXME: this is stolen from DL and needs to be refactored.
+
+libc_so = libm_so = nil
+
+case RUBY_PLATFORM
+when /cygwin/
+ libc_so = "cygwin1.dll"
+ libm_so = "cygwin1.dll"
+when /android/
+ libdir = '/system/lib'
+ if [0].pack('L!').size == 8
+ libdir = '/system/lib64'
+ end
+ libc_so = File.join(libdir, "libc.so")
+ libm_so = File.join(libdir, "libm.so")
+when /linux-musl/
+ Dir.glob('/lib/ld-musl-*.so.1') do |ld|
+ libc_so = libm_so = ld
+ end
+when /linux/
+ libdir = '/lib'
+ case RbConfig::SIZEOF['void*']
+ when 4
+ # 32-bit ruby
+ case RUBY_PLATFORM
+ when /armv\w+-linux/
+ # In the ARM 32-bit libc package such as libc6:armhf libc6:armel,
+ # libc.so and libm.so are installed to /lib/arm-linux-gnu*.
+ # It's not installed to /lib32.
+ dir, = Dir.glob('/lib/arm-linux-gnu*')
+ libdir = dir if dir && File.directory?(dir)
+ else
+ libdir = '/lib32' if File.directory? '/lib32'
+ end
+ when 8
+ # 64-bit ruby
+ libdir = '/lib64' if File.directory? '/lib64'
+ end
+
+ # Handle musl libc
+ libc_so, = Dir.glob(File.join(libdir, "libc.musl*.so*"))
+ if libc_so
+ libm_so = libc_so
+ else
+ # glibc
+ libc_so = "libc.so.6"
+ libm_so = "libm.so.6"
+ end
+when /mingw/, /mswin/
+ require "rbconfig"
+ crtname = RbConfig::CONFIG["RUBY_SO_NAME"][/msvc\w+/] || 'ucrtbase'
+ 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"
+when /gnu/ #GNU/Hurd
+ libc_so = "/lib/libc.so.0.3"
+ libm_so = "/lib/libm.so.6"
+when /mirbsd/
+ libc_so = "/usr/lib/libc.so.41.10"
+ libm_so = "/usr/lib/libm.so.7.0"
+when /freebsd/
+ libc_so = "/lib/libc.so.7"
+ libm_so = "/lib/libm.so.5"
+when /bsd|dragonfly/
+ libc_so = "/usr/lib/libc.so"
+ libm_so = "/usr/lib/libm.so"
+when /solaris/
+ libdir = '/lib'
+ case RbConfig::SIZEOF['void*']
+ when 4
+ # 32-bit ruby
+ libdir = '/lib' if File.directory? '/lib'
+ when 8
+ # 64-bit ruby
+ libdir = '/lib/64' if File.directory? '/lib/64'
+ end
+ libc_so = File.join(libdir, "libc.so")
+ libm_so = File.join(libdir, "libm.so")
+when /aix/
+ pwd=Dir.pwd
+ libc_so = libm_so = "#{pwd}/libaixdltest.so"
+ unless File.exist? libc_so
+ cobjs=%w!strcpy.o!
+ mobjs=%w!floats.o sin.o!
+ funcs=%w!sin sinf strcpy strncpy!
+ expfile='dltest.exp'
+ require 'tmpdir'
+ Dir.mktmpdir do |_dir|
+ begin
+ Dir.chdir _dir
+ %x!/usr/bin/ar x /usr/lib/libc.a #{cobjs.join(' ')}!
+ %x!/usr/bin/ar x /usr/lib/libm.a #{mobjs.join(' ')}!
+ %x!echo "#{funcs.join("\n")}\n" > #{expfile}!
+ require 'rbconfig'
+ if RbConfig::CONFIG["GCC"] = 'yes'
+ lflag='-Wl,'
+ else
+ lflag=''
+ end
+ flags="#{lflag}-bE:#{expfile} #{lflag}-bnoentry -lm"
+ %x!#{RbConfig::CONFIG["LDSHARED"]} -o #{libc_so} #{(cobjs+mobjs).join(' ')} #{flags}!
+ ensure
+ Dir.chdir pwd
+ end
+ end
+ end
+when /haiku/
+ libdir = '/system/lib'
+ case [0].pack('L!').size
+ when 4
+ # 32-bit ruby
+ libdir = '/system/lib/x86' if File.directory? '/system/lib/x86'
+ when 8
+ # 64-bit ruby
+ libdir = '/system/lib/' if File.directory? '/system/lib/'
+ end
+ libc_so = File.join(libdir, "libroot.so")
+ libm_so = File.join(libdir, "libroot.so")
+else
+ libc_so = ARGV[0] if ARGV[0] && ARGV[0][0] == ?/
+ libm_so = ARGV[1] if ARGV[1] && ARGV[1][0] == ?/
+ if( !(libc_so && libm_so) )
+ $stderr.puts("libc and libm not found: #{$0} <libc> <libm>")
+ end
+end
+
+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
+ ruby = EnvUtil.rubybin
+ # When the ruby binary is 32-bit and the host is 64-bit,
+ # `ldd ruby` outputs "not a dynamic executable" message.
+ # libc_so and libm_so are not set.
+ ldd = `ldd #{ruby}`
+ #puts ldd
+ libc_so = $& if !libc_so && %r{/\S*/libc\.so\S*} =~ ldd
+ libm_so = $& if !libm_so && %r{/\S*/libm\.so\S*} =~ ldd
+ #p [libc_so, libm_so]
+end
+
+Fiddle::LIBC_SO = libc_so
+Fiddle::LIBM_SO = libm_so
+
+module Fiddle
+ class TestCase < Test::Unit::TestCase
+ def setup
+ @libc = Fiddle.dlopen(LIBC_SO)
+ @libm = Fiddle.dlopen(LIBM_SO)
+ end
+
+ def teardown
+ if /linux/ =~ RUBY_PLATFORM
+ 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
new file mode 100644
index 0000000000..9fd16d7101
--- /dev/null
+++ b/test/fiddle/test_c_struct_entry.rb
@@ -0,0 +1,165 @@
+# frozen_string_literal: true
+begin
+ require_relative 'helper'
+ require 'fiddle/struct'
+rescue LoadError
+end
+
+module Fiddle
+ class TestCStructEntity < TestCase
+ def test_class_size
+ types = [TYPE_DOUBLE, TYPE_CHAR]
+
+ size = CStructEntity.size types
+
+ alignments = types.map { |type| PackInfo::ALIGN_MAP[type] }
+
+ expected = PackInfo.align 0, alignments[0]
+ expected += PackInfo::SIZE_MAP[TYPE_DOUBLE]
+
+ expected = PackInfo.align expected, alignments[1]
+ expected += PackInfo::SIZE_MAP[TYPE_CHAR]
+
+ expected = PackInfo.align expected, alignments.max
+
+ assert_equal expected, size
+ end
+
+ def test_class_size_with_count
+ size = CStructEntity.size([[TYPE_DOUBLE, 2], [TYPE_CHAR, 20]])
+
+ types = [TYPE_DOUBLE, TYPE_CHAR]
+ alignments = types.map { |type| PackInfo::ALIGN_MAP[type] }
+
+ expected = PackInfo.align 0, alignments[0]
+ expected += PackInfo::SIZE_MAP[TYPE_DOUBLE] * 2
+
+ expected = PackInfo.align expected, alignments[1]
+ expected += PackInfo::SIZE_MAP[TYPE_CHAR] * 20
+
+ expected = PackInfo.align expected, alignments.max
+
+ assert_equal expected, size
+ end
+
+ def test_set_ctypes
+ CStructEntity.malloc([TYPE_INT, TYPE_LONG], Fiddle::RUBY_FREE) do |struct|
+ struct.assign_names %w[int long]
+
+ # this test is roundabout because the stored ctypes are not accessible
+ struct['long'] = 1
+ struct['int'] = 2
+
+ assert_equal 1, struct['long']
+ assert_equal 2, struct['int']
+ end
+ end
+
+ def test_aref_pointer_array
+ CStructEntity.malloc([[TYPE_VOIDP, 2]], Fiddle::RUBY_FREE) do |team|
+ team.assign_names(["names"])
+ Fiddle::Pointer.malloc(6, Fiddle::RUBY_FREE) do |alice|
+ alice[0, 6] = "Alice\0"
+ Fiddle::Pointer.malloc(4, Fiddle::RUBY_FREE) do |bob|
+ bob[0, 4] = "Bob\0"
+ team["names"] = [alice, bob]
+ assert_equal(["Alice", "Bob"], team["names"].map(&:to_s))
+ end
+ end
+ end
+ end
+
+ def test_aref_pointer
+ CStructEntity.malloc([TYPE_VOIDP], Fiddle::RUBY_FREE) do |user|
+ user.assign_names(["name"])
+ Fiddle::Pointer.malloc(6, Fiddle::RUBY_FREE) do |alice|
+ alice[0, 6] = "Alice\0"
+ user["name"] = alice
+ assert_equal("Alice", user["name"].to_s)
+ end
+ end
+ end
+
+ def test_new_double_free
+ types = [TYPE_INT]
+ Pointer.malloc(CStructEntity.size(types), Fiddle::RUBY_FREE) do |pointer|
+ assert_raise ArgumentError do
+ CStructEntity.new(pointer, types, Fiddle::RUBY_FREE)
+ end
+ end
+ end
+
+ def test_malloc_block
+ escaped_struct = nil
+ returned = CStructEntity.malloc([TYPE_INT], Fiddle::RUBY_FREE) do |struct|
+ assert_equal Fiddle::SIZEOF_INT, struct.size
+ assert_equal Fiddle::RUBY_FREE, struct.free.to_i
+ escaped_struct = struct
+ :returned
+ end
+ assert_equal :returned, returned
+ assert escaped_struct.freed?
+ end
+
+ def test_malloc_block_no_free
+ assert_raise ArgumentError do
+ CStructEntity.malloc([TYPE_INT]) { |struct| }
+ end
+ end
+
+ def test_free
+ struct = CStructEntity.malloc([TYPE_INT])
+ begin
+ assert_nil struct.free
+ ensure
+ Fiddle.free struct
+ end
+ end
+
+ def test_free_with_func
+ struct = CStructEntity.malloc([TYPE_INT], Fiddle::RUBY_FREE)
+ refute struct.freed?
+ struct.call_free
+ assert struct.freed?
+ struct.call_free # you can safely run it again
+ assert struct.freed?
+ GC.start # you can safely run the GC routine
+ assert struct.freed?
+ end
+
+ def test_free_with_no_func
+ struct = CStructEntity.malloc([TYPE_INT])
+ refute struct.freed?
+ struct.call_free
+ refute struct.freed?
+ struct.call_free # you can safely run it again
+ refute struct.freed?
+ end
+
+ def test_freed?
+ struct = CStructEntity.malloc([TYPE_INT], Fiddle::RUBY_FREE)
+ refute struct.freed?
+ struct.call_free
+ assert struct.freed?
+ end
+
+ def test_null?
+ struct = CStructEntity.malloc([TYPE_INT], Fiddle::RUBY_FREE)
+ refute struct.null?
+ end
+
+ def test_size
+ CStructEntity.malloc([TYPE_INT], Fiddle::RUBY_FREE) do |struct|
+ assert_equal Fiddle::SIZEOF_INT, struct.size
+ end
+ end
+
+ def test_size=
+ CStructEntity.malloc([TYPE_INT], Fiddle::RUBY_FREE) do |struct|
+ assert_raise NoMethodError do
+ struct.size = 1
+ end
+ end
+ end
+ end
+end if defined?(Fiddle)
diff --git a/test/fiddle/test_c_union_entity.rb b/test/fiddle/test_c_union_entity.rb
new file mode 100644
index 0000000000..e0a3757562
--- /dev/null
+++ b/test/fiddle/test_c_union_entity.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+begin
+ require_relative 'helper'
+ require 'fiddle/struct'
+rescue LoadError
+end
+
+
+module Fiddle
+ class TestCUnionEntity < TestCase
+ def test_class_size
+ size = CUnionEntity.size([TYPE_DOUBLE, TYPE_CHAR])
+
+ assert_equal SIZEOF_DOUBLE, size
+ end
+
+ def test_class_size_with_count
+ size = CUnionEntity.size([[TYPE_DOUBLE, 2], [TYPE_CHAR, 20]])
+
+ assert_equal SIZEOF_CHAR * 20, size
+ end
+
+ def test_set_ctypes
+ CUnionEntity.malloc([TYPE_INT, TYPE_LONG], Fiddle::RUBY_FREE) do |union|
+ union.assign_names %w[int long]
+
+ # this test is roundabout because the stored ctypes are not accessible
+ union['long'] = 1
+ assert_equal 1, union['long']
+
+ union['int'] = 1
+ assert_equal 1, union['int']
+ end
+ end
+ end
+end if defined?(Fiddle)
diff --git a/test/fiddle/test_closure.rb b/test/fiddle/test_closure.rb
new file mode 100644
index 0000000000..9e748bf5ee
--- /dev/null
+++ b/test/fiddle/test_closure.rb
@@ -0,0 +1,110 @@
+# frozen_string_literal: true
+begin
+ require_relative 'helper'
+rescue LoadError
+end
+
+module Fiddle
+ class TestClosure < Fiddle::TestCase
+ def test_argument_errors
+ assert_raise(TypeError) do
+ Closure.new(TYPE_INT, TYPE_INT)
+ end
+
+ assert_raise(TypeError) do
+ Closure.new('foo', [TYPE_INT])
+ end
+
+ assert_raise(TypeError) do
+ Closure.new(TYPE_INT, ['meow!'])
+ end
+ end
+
+ def test_type_symbol
+ closure = Closure.new(:int, [:void])
+ assert_equal([
+ TYPE_INT,
+ [TYPE_VOID],
+ ],
+ [
+ closure.instance_variable_get(:@ctype),
+ closure.instance_variable_get(:@args),
+ ])
+ end
+
+ def test_call
+ closure = Class.new(Closure) {
+ def call
+ 10
+ end
+ }.new(TYPE_INT, [])
+
+ func = Function.new(closure, [], TYPE_INT)
+ assert_equal 10, func.call
+ end
+
+ def test_returner
+ closure = Class.new(Closure) {
+ def call thing
+ thing
+ end
+ }.new(TYPE_INT, [TYPE_INT])
+
+ func = Function.new(closure, [TYPE_INT], TYPE_INT)
+ assert_equal 10, func.call(10)
+ end
+
+ def test_const_string
+ closure_class = Class.new(Closure) do
+ def call(string)
+ @return_string = "Hello! #{string}"
+ @return_string
+ end
+ end
+ closure = closure_class.new(:const_string, [:const_string])
+
+ func = Function.new(closure, [:const_string], :const_string)
+ assert_equal("Hello! World!", func.call("World!"))
+ 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)
+ end
+
+ def test_memsize
+ require 'objspace'
+ bug = '[ruby-dev:42480]'
+ n = 10000
+ assert_equal(n, n.times {ObjectSpace.memsize_of(Closure.allocate)}, bug)
+ end
+
+ %w[INT SHORT CHAR LONG LONG_LONG].each do |name|
+ type = Fiddle.const_get("TYPE_#{name}") rescue next
+ size = Fiddle.const_get("SIZEOF_#{name}")
+ [[type, size-1, name], [-type, size, "unsigned_"+name]].each do |t, s, n|
+ define_method("test_conversion_#{n.downcase}") do
+ arg = nil
+
+ clos = Class.new(Closure) do
+ define_method(:call) {|x| arg = x}
+ end.new(t, [t])
+
+ v = ~(~0 << (8*s))
+
+ 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)
+ end
+ end
+ end
+ end
+end if defined?(Fiddle)
diff --git a/test/fiddle/test_cparser.rb b/test/fiddle/test_cparser.rb
new file mode 100644
index 0000000000..ae319197a4
--- /dev/null
+++ b/test/fiddle/test_cparser.rb
@@ -0,0 +1,374 @@
+# frozen_string_literal: true
+begin
+ require_relative 'helper'
+ require 'fiddle/cparser'
+ require 'fiddle/import'
+rescue LoadError
+end
+
+module Fiddle
+ class TestCParser < TestCase
+ include CParser
+
+ 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('signed short'))
+ assert_equal(TYPE_SHORT, parse_ctype('const signed short'))
+ 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('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'))
+ 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('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('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'))
+ 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_undefined_ctype
+ assert_raise(DLError) { parse_ctype('DWORD') }
+ end
+
+ def test_undefined_ctype_with_type_alias
+ 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)
+ types.collect do |type|
+ case type
+ when Class
+ [expand_struct_types(type.types)]
+ when Array
+ [expand_struct_types([type[0]])[0][0], type[1]]
+ else
+ type
+ end
+ end
+ 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(['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(['const char buffer[80]',
+ 'const int[5] x']))
+ end
+
+ def test_struct_nested_struct
+ types, members = parse_struct_signature([
+ 'int x',
+ {inner: ['int i', 'char c']},
+ ])
+ assert_equal([[TYPE_INT, [[TYPE_INT, TYPE_CHAR]]],
+ ['x', ['inner', ['i', 'c']]]],
+ [expand_struct_types(types),
+ members])
+ end
+
+ def test_struct_nested_defined_struct
+ inner = Fiddle::Importer.struct(['int i', 'char c'])
+ assert_equal([[TYPE_INT, inner],
+ ['x', ['inner', ['i', 'c']]]],
+ parse_struct_signature([
+ 'int x',
+ {inner: inner},
+ ]))
+ end
+
+ def test_struct_double_nested_struct
+ types, members = parse_struct_signature([
+ 'int x',
+ {
+ outer: [
+ 'int y',
+ {inner: ['int i', 'char c']},
+ ],
+ },
+ ])
+ assert_equal([[TYPE_INT, [[TYPE_INT, [[TYPE_INT, TYPE_CHAR]]]]],
+ ['x', ['outer', ['y', ['inner', ['i', 'c']]]]]],
+ [expand_struct_types(types),
+ members])
+ end
+
+ def test_struct_nested_struct_array
+ types, members = parse_struct_signature([
+ 'int x',
+ {
+ 'inner[2]' => [
+ 'int i',
+ 'char c',
+ ],
+ },
+ ])
+ assert_equal([[TYPE_INT, [[TYPE_INT, TYPE_CHAR], 2]],
+ ['x', ['inner', ['i', 'c']]]],
+ [expand_struct_types(types),
+ members])
+ end
+
+ def test_struct_double_nested_struct_inner_array
+ types, members = parse_struct_signature(outer: [
+ 'int x',
+ {
+ 'inner[2]' => [
+ 'int i',
+ 'char c',
+ ],
+ },
+ ])
+ assert_equal([[[[TYPE_INT, [[TYPE_INT, TYPE_CHAR], 2]]]],
+ [['outer', ['x', ['inner', ['i', 'c']]]]]],
+ [expand_struct_types(types),
+ members])
+ end
+
+ def test_struct_double_nested_struct_outer_array
+ types, members = parse_struct_signature([
+ 'int x',
+ {
+ 'outer[2]' => {
+ inner: [
+ 'int i',
+ 'char c',
+ ],
+ },
+ },
+ ])
+ assert_equal([[TYPE_INT, [[[[TYPE_INT, TYPE_CHAR]]], 2]],
+ ['x', ['outer', [['inner', ['i', 'c']]]]]],
+ [expand_struct_types(types),
+ members])
+ 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('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*)']))
+ 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'))
+ end
+
+ def test_struct_string
+ assert_equal [[TYPE_INT,TYPE_VOIDP,TYPE_VOIDP], ['x', 'cb', 'name']], parse_struct_signature('int x; void (*cb)(); const char* name')
+ end
+
+ def test_struct_undefined
+ assert_raise(DLError) { parse_struct_signature(['int i', 'DWORD cb']) }
+ end
+
+ def test_struct_undefined_with_type_alias
+ assert_equal [[TYPE_INT,-TYPE_LONG], ['i', 'cb']], parse_struct_signature(['int i', 'DWORD cb'], {"DWORD" => "unsigned long"})
+ end
+
+ def test_signature_basic
+ func, ret, args = parse_signature('void func()')
+ assert_equal 'func', func
+ assert_equal TYPE_VOID, ret
+ assert_equal [], args
+ end
+
+ def test_signature_semi
+ func, ret, args = parse_signature('void func();')
+ assert_equal 'func', func
+ assert_equal TYPE_VOID, ret
+ assert_equal [], args
+ end
+
+ def test_signature_void_arg
+ func, ret, args = parse_signature('void func(void)')
+ assert_equal 'func', func
+ assert_equal TYPE_VOID, ret
+ assert_equal [], args
+ end
+
+ def test_signature_type_args
+ types = [
+ 'char', 'unsigned char',
+ 'short', 'unsigned short',
+ 'int', 'unsigned int',
+ 'long', 'unsigned long',
+ defined?(TYPE_LONG_LONG) && \
+ [
+ 'long long', 'unsigned long long',
+ ],
+ 'float', 'double',
+ 'const char*', 'void*',
+ ].flatten.compact
+ func, ret, args = parse_signature("void func(#{types.join(',')})")
+ assert_equal 'func', func
+ assert_equal TYPE_VOID, ret
+ assert_equal [
+ TYPE_CHAR, -TYPE_CHAR,
+ TYPE_SHORT, -TYPE_SHORT,
+ TYPE_INT, -TYPE_INT,
+ TYPE_LONG, -TYPE_LONG,
+ defined?(TYPE_LONG_LONG) && \
+ [
+ TYPE_LONG_LONG, -TYPE_LONG_LONG,
+ ],
+ TYPE_FLOAT, TYPE_DOUBLE,
+ TYPE_VOIDP, TYPE_VOIDP,
+ ].flatten.compact, args
+ end
+
+ def test_signature_single_variable
+ func, ret, args = parse_signature('void func(int x)')
+ assert_equal 'func', func
+ assert_equal TYPE_VOID, ret
+ assert_equal [TYPE_INT], args
+ end
+
+ def test_signature_multiple_variables
+ func, ret, args = parse_signature('void func(int x, const char* s)')
+ assert_equal 'func', func
+ assert_equal TYPE_VOID, ret
+ assert_equal [TYPE_INT, TYPE_VOIDP], args
+ end
+
+ def test_signature_array_variable
+ func, ret, args = parse_signature('void func(int x[], int y[40])')
+ assert_equal 'func', func
+ assert_equal TYPE_VOID, ret
+ assert_equal [TYPE_VOIDP, TYPE_VOIDP], args
+ end
+
+ def test_signature_function_pointer
+ func, ret, args = parse_signature('int func(int (*sum)(int x, int y), int x, int y)')
+ assert_equal 'func', func
+ assert_equal TYPE_INT, ret
+ assert_equal [TYPE_VOIDP, TYPE_INT, TYPE_INT], args
+ end
+
+ def test_signature_variadic_arguments
+ unless Fiddle.const_defined?("TYPE_VARIADIC")
+ omit "libffi doesn't support variadic arguments"
+ end
+ assert_equal([
+ "printf",
+ TYPE_INT,
+ [TYPE_VOIDP, TYPE_VARIADIC],
+ ],
+ parse_signature('int printf(const char *format, ...)'))
+ end
+
+ def test_signature_return_pointer
+ func, ret, args = parse_signature('void* malloc(size_t)')
+ assert_equal 'malloc', func
+ assert_equal TYPE_VOIDP, ret
+ assert_equal [TYPE_SIZE_T], args
+ end
+
+ def test_signature_return_array
+ func, ret, args = parse_signature('int (*func())[32]')
+ assert_equal 'func', func
+ assert_equal TYPE_VOIDP, ret
+ assert_equal [], args
+ end
+
+ def test_signature_return_array_with_args
+ func, ret, args = parse_signature('int (*func(const char* s))[]')
+ assert_equal 'func', func
+ assert_equal TYPE_VOIDP, ret
+ assert_equal [TYPE_VOIDP], args
+ end
+
+ def test_signature_return_function_pointer
+ func, ret, args = parse_signature('int (*func())(int x, int y)')
+ assert_equal 'func', func
+ assert_equal TYPE_VOIDP, ret
+ assert_equal [], args
+ end
+
+ def test_signature_return_function_pointer_with_args
+ func, ret, args = parse_signature('int (*func(int z))(int x, int y)')
+ assert_equal 'func', func
+ assert_equal TYPE_VOIDP, ret
+ assert_equal [TYPE_INT], args
+ end
+ end
+end if defined?(Fiddle)
diff --git a/test/fiddle/test_fiddle.rb b/test/fiddle/test_fiddle.rb
new file mode 100644
index 0000000000..8751d96920
--- /dev/null
+++ b/test/fiddle/test_fiddle.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+begin
+ require_relative 'helper'
+rescue LoadError
+end
+
+class TestFiddle < Fiddle::TestCase
+ def test_windows_constant
+ require 'rbconfig'
+ if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
+ assert Fiddle::WINDOWS, "Fiddle::WINDOWS should be 'true' on Windows platforms"
+ else
+ refute Fiddle::WINDOWS, "Fiddle::WINDOWS should be 'false' on non-Windows platforms"
+ end
+ end
+
+end if defined?(Fiddle)
diff --git a/test/fiddle/test_func.rb b/test/fiddle/test_func.rb
new file mode 100644
index 0000000000..44893017e8
--- /dev/null
+++ b/test/fiddle/test_func.rb
@@ -0,0 +1,139 @@
+# frozen_string_literal: true
+begin
+ require_relative 'helper'
+rescue LoadError
+end
+
+module Fiddle
+ class TestFunc < TestCase
+ def test_random
+ f = Function.new(@libc['srand'], [-TYPE_LONG], TYPE_VOID)
+ assert_nil f.call(10)
+ end
+
+ def test_sinf
+ begin
+ f = Function.new(@libm['sinf'], [TYPE_FLOAT], TYPE_FLOAT)
+ rescue Fiddle::DLError
+ omit "libm may not have sinf()"
+ end
+ assert_in_delta 1.0, f.call(90 * Math::PI / 180), 0.0001
+ end
+
+ def test_sin
+ f = Function.new(@libm['sin'], [TYPE_DOUBLE], TYPE_DOUBLE)
+ assert_in_delta 1.0, f.call(90 * Math::PI / 180), 0.0001
+ end
+
+ def test_string
+ 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
+ f = Function.new(@libc['isdigit'], [TYPE_INT], TYPE_INT)
+ r1 = f.call(?1.ord)
+ r2 = f.call(?2.ord)
+ rr = f.call(?r.ord)
+ assert_operator r1, :>, 0
+ assert_operator r2, :>, 0
+ assert_equal 0, rr
+ end
+
+ def test_atof
+ f = Function.new(@libc['atof'], [TYPE_VOIDP], TYPE_DOUBLE)
+ r = f.call("12.34")
+ assert_includes(12.00..13.00, r)
+ end
+
+ def test_strtod
+ f = Function.new(@libc['strtod'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_DOUBLE)
+ buff1 = Pointer["12.34"]
+ buff2 = buff1 + 4
+ r = f.call(buff1, - buff2)
+ assert_in_delta(12.34, r, 0.001)
+ end
+
+ def test_qsort1
+ cb = Class.new(Closure) {
+ def call(x, y)
+ Pointer.new(x)[0] <=> Pointer.new(y)[0]
+ end
+ }.new(TYPE_INT, [TYPE_VOIDP, TYPE_VOIDP])
+
+ 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)
+
+ bug4929 = '[ruby-core:37395]'
+ buff = "9341"
+ under_gc_stress do
+ qsort.call(buff, buff.size, 1, cb)
+ end
+ assert_equal("1349", buff, bug4929)
+ end
+
+ def test_snprintf
+ unless Fiddle.const_defined?("TYPE_VARIADIC")
+ omit "libffi doesn't support variadic arguments"
+ end
+ if Fiddle::WINDOWS
+ snprintf_name = "_snprintf"
+ else
+ snprintf_name = "snprintf"
+ end
+ begin
+ snprintf_pointer = @libc[snprintf_name]
+ rescue Fiddle::DLError
+ omit "Can't find #{snprintf_name}: #{$!.message}"
+ end
+ snprintf = Function.new(snprintf_pointer,
+ [
+ :voidp,
+ :size_t,
+ :const_string,
+ :variadic,
+ ],
+ :int)
+ output_buffer = " " * 1024
+ output = Pointer[output_buffer]
+
+ written = snprintf.call(output,
+ output.size,
+ "int: %d, string: %.*s, const string: %s\n",
+ :int, -29,
+ :int, 4,
+ :voidp, "Hello",
+ :const_string, "World")
+ assert_equal("int: -29, string: Hell, const string: World\n",
+ output_buffer[0, written])
+
+ string_like_class = Class.new do
+ def initialize(string)
+ @string = string
+ end
+
+ def to_str
+ @string
+ end
+ end
+ written = snprintf.call(output,
+ output.size,
+ "string: %.*s, const string: %s, uint: %u\n",
+ :int, 2,
+ :voidp, "Hello",
+ :const_string, string_like_class.new("World"),
+ :int, 29)
+ assert_equal("string: He, const string: World, uint: 29\n",
+ output_buffer[0, written])
+ end
+ end
+end if defined?(Fiddle)
diff --git a/test/fiddle/test_function.rb b/test/fiddle/test_function.rb
new file mode 100644
index 0000000000..8ac4f60aa3
--- /dev/null
+++ b/test/fiddle/test_function.rb
@@ -0,0 +1,227 @@
+# frozen_string_literal: true
+begin
+ require_relative 'helper'
+rescue LoadError
+end
+
+module Fiddle
+ class TestFunction < Fiddle::TestCase
+ def setup
+ super
+ Fiddle.last_error = nil
+ if WINDOWS
+ Fiddle.win32_last_error = nil
+ Fiddle.win32_last_socket_error = nil
+ end
+ end
+
+ def test_default_abi
+ func = Function.new(@libm['sin'], [TYPE_DOUBLE], TYPE_DOUBLE)
+ assert_equal Function::DEFAULT, func.abi
+ end
+
+ def test_name
+ func = Function.new(@libm['sin'], [TYPE_DOUBLE], TYPE_DOUBLE, name: 'sin')
+ assert_equal 'sin', func.name
+ end
+
+ def test_need_gvl?
+ libruby = Fiddle.dlopen(nil)
+ rb_str_dup = Function.new(libruby['rb_str_dup'],
+ [:voidp],
+ :voidp,
+ need_gvl: true)
+ assert(rb_str_dup.need_gvl?)
+ assert_equal('Hello',
+ Fiddle.dlunwrap(rb_str_dup.call(Fiddle.dlwrap('Hello'))))
+ end
+
+ def test_argument_errors
+ assert_raise(TypeError) do
+ Function.new(@libm['sin'], TYPE_DOUBLE, TYPE_DOUBLE)
+ end
+
+ assert_raise(TypeError) do
+ Function.new(@libm['sin'], ['foo'], TYPE_DOUBLE)
+ end
+
+ assert_raise(TypeError) do
+ Function.new(@libm['sin'], [TYPE_DOUBLE], 'foo')
+ end
+ end
+
+ def test_argument_type_conversion
+ type = Struct.new(:int, :call_count) do
+ def initialize(int)
+ super(int, 0)
+ end
+ def to_int
+ raise "exhausted" if (self.call_count += 1) > 1
+ self.int
+ end
+ end
+ type_arg = type.new(TYPE_DOUBLE)
+ type_result = type.new(TYPE_DOUBLE)
+ assert_nothing_raised(RuntimeError) do
+ Function.new(@libm['sin'], [type_arg], type_result)
+ end
+ assert_equal(1, type_arg.call_count)
+ assert_equal(1, type_result.call_count)
+ end
+
+ def test_call
+ func = Function.new(@libm['sin'], [TYPE_DOUBLE], TYPE_DOUBLE)
+ assert_in_delta 1.0, func.call(90 * Math::PI / 180), 0.0001
+ end
+
+ def test_argument_count
+ closure = Class.new(Closure) {
+ 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
+ end
+ end
+
+ def test_last_error
+ func = Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP)
+
+ assert_nil Fiddle.last_error
+ func.call(+"000", "123")
+ 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"
+ str = f.call(buff, "123")
+ assert_equal("123", buff)
+ assert_equal("123", str.to_s)
+ end
+
+ def call_proc(string_to_copy)
+ buff = +"000"
+ str = yield(buff, string_to_copy)
+ [buff, str]
+ end
+
+ def test_function_as_proc
+ f = Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP)
+ buff, str = call_proc("123", &f)
+ assert_equal("123", buff)
+ assert_equal("123", str.to_s)
+ end
+
+ def test_function_as_method
+ f = Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP)
+ klass = Class.new do
+ define_singleton_method(:strcpy, &f)
+ end
+ buff = +"000"
+ str = klass.strcpy(buff, "123")
+ assert_equal("123", buff)
+ assert_equal("123", str.to_s)
+ end
+
+ def test_nogvl_poll
+ # XXX hack to quiet down CI errors on EINTR from r64353
+ # [ruby-core:88360] [Misc #14937]
+ # Making pipes (and sockets) non-blocking by default would allow
+ # us to get rid of POSIX timers / timer pthread
+ # https://bugs.ruby-lang.org/issues/14968
+ IO.pipe { |r,w| IO.select([r], [w]) }
+ begin
+ poll = @libc['poll']
+ rescue Fiddle::DLError
+ omit 'poll(2) not available'
+ end
+ f = Function.new(poll, [TYPE_VOIDP, TYPE_INT, TYPE_INT], TYPE_INT)
+
+ msec = 200
+ t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
+ th = Thread.new { f.call(nil, 0, msec) }
+ n1 = f.call(nil, 0, msec)
+ n2 = th.value
+ t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
+ assert_in_delta(msec, t1 - t0, 180, 'slept amount of time')
+ assert_equal(0, n1, perror("poll(2) in main-thread"))
+ assert_equal(0, n2, perror("poll(2) in sub-thread"))
+ end
+
+ def test_no_memory_leak
+ 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
+
+ def perror(m)
+ proc do
+ if e = Fiddle.last_error
+ m = "#{m}: #{SystemCallError.new(e).message}"
+ end
+ m
+ end
+ end
+ end
+end if defined?(Fiddle)
diff --git a/test/fiddle/test_handle.rb b/test/fiddle/test_handle.rb
new file mode 100644
index 0000000000..7e3ff9d844
--- /dev/null
+++ b/test/fiddle/test_handle.rb
@@ -0,0 +1,208 @@
+# frozen_string_literal: true
+begin
+ require_relative 'helper'
+rescue LoadError
+end
+
+module Fiddle
+ class TestHandle < TestCase
+ include Fiddle
+
+ def test_to_i
+ handle = Fiddle::Handle.new(LIBC_SO)
+ 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'] }
+ end
+
+ def test_static_sym
+ begin
+ # Linux / Darwin / FreeBSD
+ refute_nil Fiddle::Handle.sym('dlopen')
+ assert_equal Fiddle::Handle.sym('dlopen'), Fiddle::Handle['dlopen']
+ return
+ rescue
+ end
+
+ begin
+ # NetBSD
+ require '-test-/dln/empty'
+ refute_nil Fiddle::Handle.sym('Init_empty')
+ assert_equal Fiddle::Handle.sym('Init_empty'), Fiddle::Handle['Init_empty']
+ return
+ rescue
+ end
+ end unless /mswin|mingw/ =~ RUBY_PLATFORM
+
+ def test_sym_closed_handle
+ handle = Fiddle::Handle.new(LIBC_SO)
+ handle.close
+ assert_raise(DLError) { handle.sym("calloc") }
+ assert_raise(DLError) { handle["calloc"] }
+ end
+
+ def test_sym_unknown
+ handle = Fiddle::Handle.new(LIBC_SO)
+ assert_raise(DLError) { handle.sym('fooo') }
+ assert_raise(DLError) { handle['fooo'] }
+ end
+
+ def test_sym_with_bad_args
+ handle = Handle.new(LIBC_SO)
+ assert_raise(TypeError) { handle.sym(nil) }
+ assert_raise(TypeError) { handle[nil] }
+ end
+
+ def test_sym
+ handle = Handle.new(LIBC_SO)
+ refute_nil handle.sym('calloc')
+ refute_nil handle['calloc']
+ end
+
+ def test_handle_close
+ handle = Handle.new(LIBC_SO)
+ assert_equal 0, handle.close
+ end
+
+ def test_handle_close_twice
+ handle = Handle.new(LIBC_SO)
+ handle.close
+ assert_raise(DLError) do
+ handle.close
+ end
+ end
+
+ def test_dlopen_returns_handle
+ assert_instance_of Handle, dlopen(LIBC_SO)
+ end
+
+ def test_initialize_noargs
+ handle = Handle.new
+ refute_nil handle['rb_str_new']
+ end
+
+ def test_initialize_flags
+ handle = Handle.new(LIBC_SO, RTLD_LAZY | RTLD_GLOBAL)
+ refute_nil handle['calloc']
+ end
+
+ def test_enable_close
+ handle = Handle.new(LIBC_SO)
+ assert !handle.close_enabled?, 'close is enabled'
+
+ handle.enable_close
+ assert handle.close_enabled?, 'close is not enabled'
+ end
+
+ def test_disable_close
+ handle = Handle.new(LIBC_SO)
+
+ handle.enable_close
+ assert handle.close_enabled?, 'close is enabled'
+ handle.disable_close
+ 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
+ #
+ # There are two special pseudo-handles, RTLD_DEFAULT and RTLD_NEXT. The former will find
+ # the first occurrence of the desired symbol using the default library search order. The
+ # latter will find the next occurrence of a function in the search order after the current
+ # library. This allows one to provide a wrapper around a function in another shared
+ # library.
+ # --- Ubuntu Linux 8.04 dlsym(3)
+ handle = Handle::NEXT
+ refute_nil handle['malloc']
+ return
+ rescue
+ end
+
+ begin
+ # BSD
+ #
+ # If dlsym() is called with the special handle RTLD_NEXT, then the search
+ # for the symbol is limited to the shared objects which were loaded after
+ # the one issuing the call to dlsym(). Thus, if the function is called
+ # from the main program, all the shared libraries are searched. If it is
+ # called from a shared library, all subsequent shared libraries are
+ # searched. RTLD_NEXT is useful for implementing wrappers around library
+ # functions. For example, a wrapper function getpid() could access the
+ # "real" getpid() with dlsym(RTLD_NEXT, "getpid"). (Actually, the dlfunc()
+ # interface, below, should be used, since getpid() is a function and not a
+ # data object.)
+ # --- FreeBSD 8.0 dlsym(3)
+ require '-test-/dln/empty'
+ handle = Handle::NEXT
+ refute_nil handle['Init_empty']
+ return
+ rescue
+ end
+ end unless /mswin|mingw/ =~ RUBY_PLATFORM
+
+ def test_DEFAULT
+ handle = Handle::DEFAULT
+ refute_nil handle['malloc']
+ end unless /mswin|mingw/ =~ RUBY_PLATFORM
+
+ def test_dlerror
+ # FreeBSD (at least 7.2 to 7.2) calls nsdispatch(3) when it calls
+ # getaddrinfo(3). And nsdispatch(3) doesn't call dlerror(3) even if
+ # 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.
+ require 'socket'
+ Socket.gethostbyname("localhost")
+ Fiddle.dlopen("/lib/libc.so.7").sym('strcpy')
+ end if /freebsd/=~ RUBY_PLATFORM
+
+ def test_no_memory_leak
+ 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
+ def test_fallback_to_ansi
+ k = Fiddle::Handle.new("kernel32.dll")
+ ansi = k["GetFileAttributesA"]
+ assert_equal(ansi, k["GetFileAttributes"], "should fallback to ANSI version")
+ end
+ end
+ end
+end if defined?(Fiddle)
diff --git a/test/fiddle/test_import.rb b/test/fiddle/test_import.rb
new file mode 100644
index 0000000000..afa8df9e00
--- /dev/null
+++ b/test/fiddle/test_import.rb
@@ -0,0 +1,479 @@
+# coding: US-ASCII
+# frozen_string_literal: true
+begin
+ require_relative 'helper'
+ require 'fiddle/import'
+rescue LoadError
+end
+
+module Fiddle
+ module LIBC
+ extend Importer
+ dlload LIBC_SO, LIBM_SO
+
+ typealias 'string', 'char*'
+ typealias 'FILE*', 'void*'
+
+ extern "void *strcpy(char*, char*)"
+ extern "int isdigit(int)"
+ extern "double atof(string)"
+ extern "unsigned long strtoul(char*, char **, int)"
+ extern "int qsort(void*, unsigned long, unsigned long, void*)"
+ 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",
+ ]
+ Timezone = struct [
+ "int tz_minuteswest",
+ "int tz_dsttime",
+ ]
+ MyStruct = struct [
+ "short num[5]",
+ "char c",
+ "unsigned char buff[7]",
+ ]
+ StructNestedStruct = struct [
+ {
+ "vertices[2]" => {
+ position: ["float x", "float y", "float z"],
+ texcoord: ["float u", "float v"]
+ },
+ object: ["int id", "void *user_data"],
+ },
+ "int id"
+ ]
+ UnionNestedStruct = union [
+ {
+ keyboard: [
+ 'unsigned int state',
+ 'char key'
+ ],
+ mouse: [
+ 'unsigned int button',
+ 'unsigned short x',
+ 'unsigned short y'
+ ]
+ }
+ ]
+
+ 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
+ def test_ensure_call_dlload
+ err = assert_raise(RuntimeError) do
+ Class.new do
+ extend Importer
+ extern "void *strcpy(char*, char*)"
+ end
+ end
+ assert_match(/call dlload before/, err.message)
+ end
+
+ def test_struct_memory_access()
+ # check memory operations performed directly on struct
+ Fiddle::Importer.struct(['int id']).malloc(Fiddle::RUBY_FREE) do |my_struct|
+ my_struct[0, Fiddle::SIZEOF_INT] = "\x01".b * Fiddle::SIZEOF_INT
+ assert_equal 0x01010101, my_struct.id
+
+ my_struct.id = 0
+ assert_equal "\x00".b * Fiddle::SIZEOF_INT, my_struct[0, Fiddle::SIZEOF_INT]
+ end
+ end
+
+ def test_struct_ptr_array_subscript_multiarg()
+ # check memory operations performed on struct#to_ptr
+ Fiddle::Importer.struct([ 'int x' ]).malloc(Fiddle::RUBY_FREE) do |struct|
+ ptr = struct.to_ptr
+
+ struct.x = 0x02020202
+ assert_equal("\x02".b * Fiddle::SIZEOF_INT, ptr[0, Fiddle::SIZEOF_INT])
+
+ ptr[0, Fiddle::SIZEOF_INT] = "\x01".b * Fiddle::SIZEOF_INT
+ assert_equal 0x01010101, struct.x
+ end
+ end
+
+ def test_malloc()
+ LIBC::Timeval.malloc(Fiddle::RUBY_FREE) do |s1|
+ LIBC::Timeval.malloc(Fiddle::RUBY_FREE) do |s2|
+ refute_equal(s1.to_ptr.to_i, s2.to_ptr.to_i)
+ end
+ end
+ end
+
+ def test_sizeof()
+ assert_equal(SIZEOF_VOIDP, LIBC.sizeof("FILE*"))
+ assert_equal(LIBC::MyStruct.size(), LIBC.sizeof(LIBC::MyStruct))
+ LIBC::MyStruct.malloc(Fiddle::RUBY_FREE) do |my_struct|
+ assert_equal(LIBC::MyStruct.size(), LIBC.sizeof(my_struct))
+ end
+ assert_equal(SIZEOF_LONG_LONG, LIBC.sizeof("long long")) if defined?(SIZEOF_LONG_LONG)
+ assert_equal(LIBC::StructNestedStruct.size(), LIBC.sizeof(LIBC::StructNestedStruct))
+ end
+
+ Fiddle.constants.grep(/\ATYPE_(?!VOID|VARIADIC\z)(.*)/) do
+ type = $&
+ const_type_name = $1
+ size = Fiddle.const_get("SIZEOF_#{const_type_name}")
+ if const_type_name == "CONST_STRING"
+ name = "const_string"
+ type_name = "const char*"
+ else
+ name = $1.sub(/P\z/,"*").gsub(/_(?!T\z)/, " ").downcase
+ type_name = name
+ end
+ define_method("test_sizeof_#{name}") do
+ assert_equal(size, Fiddle::Importer.sizeof(type_name), type)
+ end
+ end
+
+ def test_unsigned_result()
+ d = (2 ** 31) + 1
+
+ r = LIBC.strtoul(d.to_s, 0, 0)
+ assert_equal(d, r)
+ end
+
+ def test_io()
+ if( RUBY_PLATFORM != BUILD_RUBY_PLATFORM ) || !defined?(LIBC.fprintf)
+ return
+ end
+ io_in,io_out = IO.pipe()
+ LIBC.fprintf(io_out, "hello")
+ io_out.flush()
+ io_out.close()
+ str = io_in.read()
+ io_in.close()
+ assert_equal("hello", str)
+ end
+
+ def test_value()
+ i = LIBC.value('int', 2)
+ assert_equal(2, i.value)
+
+ d = LIBC.value('double', 2.0)
+ assert_equal(2.0, d.value)
+
+ ary = LIBC.value('int[3]', [0,1,2])
+ assert_equal([0,1,2], ary.value)
+ end
+
+ def test_struct_array_assignment()
+ Fiddle::Importer.struct(["unsigned int stages[3]"]).malloc(Fiddle::RUBY_FREE) do |instance|
+ instance.stages[0] = 1024
+ instance.stages[1] = 10
+ instance.stages[2] = 100
+ assert_equal 1024, instance.stages[0]
+ assert_equal 10, instance.stages[1]
+ assert_equal 100, instance.stages[2]
+ assert_equal [1024, 10, 100].pack(Fiddle::PackInfo::PACK_MAP[-Fiddle::TYPE_INT] * 3),
+ instance.to_ptr[0, 3 * Fiddle::SIZEOF_INT]
+ assert_raise(IndexError) { instance.stages[-1] = 5 }
+ assert_raise(IndexError) { instance.stages[3] = 5 }
+ end
+ end
+
+ def test_nested_struct_reusing_other_structs()
+ position_struct = Fiddle::Importer.struct(['float x', 'float y', 'float z'])
+ texcoord_struct = Fiddle::Importer.struct(['float u', 'float v'])
+ vertex_struct = Fiddle::Importer.struct(position: position_struct, texcoord: texcoord_struct)
+ mesh_struct = Fiddle::Importer.struct([
+ {
+ "vertices[2]" => vertex_struct,
+ object: [
+ "int id",
+ "void *user_data",
+ ],
+ },
+ "int id",
+ ])
+ assert_equal LIBC::StructNestedStruct.size, mesh_struct.size
+
+
+ keyboard_event_struct = Fiddle::Importer.struct(['unsigned int state', 'char key'])
+ mouse_event_struct = Fiddle::Importer.struct(['unsigned int button', 'unsigned short x', 'unsigned short y'])
+ event_union = Fiddle::Importer.union([{ keboard: keyboard_event_struct, mouse: mouse_event_struct}])
+ assert_equal LIBC::UnionNestedStruct.size, event_union.size
+ end
+
+ def test_nested_struct_alignment_is_not_its_size()
+ inner = Fiddle::Importer.struct(['int x', 'int y', 'int z', 'int w'])
+ outer = Fiddle::Importer.struct(['char a', { 'nested' => inner }, 'char b'])
+ outer.malloc(Fiddle::RUBY_FREE) do |instance|
+ offset = instance.to_ptr.instance_variable_get(:"@offset")
+ assert_equal Fiddle::SIZEOF_INT * 5, offset.last
+ assert_equal Fiddle::SIZEOF_INT * 6, outer.size
+ assert_equal instance.to_ptr.size, outer.size
+ end
+ end
+
+ def test_struct_nested_struct_members()
+ LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s|
+ Fiddle::Pointer.malloc(24, Fiddle::RUBY_FREE) do |user_data|
+ s.vertices[0].position.x = 1
+ s.vertices[0].position.y = 2
+ s.vertices[0].position.z = 3
+ s.vertices[0].texcoord.u = 4
+ s.vertices[0].texcoord.v = 5
+ s.vertices[1].position.x = 6
+ s.vertices[1].position.y = 7
+ s.vertices[1].position.z = 8
+ s.vertices[1].texcoord.u = 9
+ s.vertices[1].texcoord.v = 10
+ s.object.id = 100
+ s.object.user_data = user_data
+ s.id = 101
+ assert_equal({
+ "vertices" => [
+ {
+ "position" => {
+ "x" => 1,
+ "y" => 2,
+ "z" => 3,
+ },
+ "texcoord" => {
+ "u" => 4,
+ "v" => 5,
+ },
+ },
+ {
+ "position" => {
+ "x" => 6,
+ "y" => 7,
+ "z" => 8,
+ },
+ "texcoord" => {
+ "u" => 9,
+ "v" => 10,
+ },
+ },
+ ],
+ "object" => {
+ "id" => 100,
+ "user_data" => user_data,
+ },
+ "id" => 101,
+ },
+ s.to_h)
+ end
+ end
+ end
+
+ def test_union_nested_struct_members()
+ LIBC::UnionNestedStruct.malloc(Fiddle::RUBY_FREE) do |s|
+ s.keyboard.state = 100
+ s.keyboard.key = 101
+ assert_equal(100, s.mouse.button)
+ refute_equal( 0, s.mouse.x)
+ end
+ end
+
+ def test_struct_nested_struct_replace_array_element()
+ LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s|
+ s.vertices[0].position.x = 5
+
+ vertex_struct = Fiddle::Importer.struct [{
+ position: ["float x", "float y", "float z"],
+ texcoord: ["float u", "float v"]
+ }]
+ vertex_struct.malloc(Fiddle::RUBY_FREE) do |vertex|
+ vertex.position.x = 100
+ s.vertices[0] = vertex
+
+ # make sure element was copied by value, but things like memory address
+ # should not be changed
+ assert_equal(100, s.vertices[0].position.x)
+ refute_equal(vertex.object_id, s.vertices[0].object_id)
+ refute_equal(vertex.to_ptr, s.vertices[0].to_ptr)
+ end
+ end
+ end
+
+ def test_struct_nested_struct_replace_array_element_nil()
+ LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s|
+ s.vertices[0].position.x = 5
+ s.vertices[0] = nil
+ assert_equal({
+ "position" => {
+ "x" => 0.0,
+ "y" => 0.0,
+ "z" => 0.0,
+ },
+ "texcoord" => {
+ "u" => 0.0,
+ "v" => 0.0,
+ },
+ },
+ s.vertices[0].to_h)
+ end
+ end
+
+ def test_struct_nested_struct_replace_array_element_hash()
+ LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s|
+ s.vertices[0] = {
+ position: {
+ x: 10,
+ y: 100,
+ }
+ }
+ assert_equal({
+ "position" => {
+ "x" => 10.0,
+ "y" => 100.0,
+ "z" => 0.0,
+ },
+ "texcoord" => {
+ "u" => 0.0,
+ "v" => 0.0,
+ },
+ },
+ s.vertices[0].to_h)
+ end
+ end
+
+ def test_struct_nested_struct_replace_entire_array()
+ LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s|
+ vertex_struct = Fiddle::Importer.struct [{
+ position: ["float x", "float y", "float z"],
+ texcoord: ["float u", "float v"]
+ }]
+
+ vertex_struct.malloc(Fiddle::RUBY_FREE) do |same0|
+ vertex_struct.malloc(Fiddle::RUBY_FREE) do |same1|
+ same = [same0, same1]
+ same[0].position.x = 1; same[1].position.x = 6
+ same[0].position.y = 2; same[1].position.y = 7
+ same[0].position.z = 3; same[1].position.z = 8
+ same[0].texcoord.u = 4; same[1].texcoord.u = 9
+ same[0].texcoord.v = 5; same[1].texcoord.v = 10
+ s.vertices = same
+ assert_equal([
+ {
+ "position" => {
+ "x" => 1.0,
+ "y" => 2.0,
+ "z" => 3.0,
+ },
+ "texcoord" => {
+ "u" => 4.0,
+ "v" => 5.0,
+ },
+ },
+ {
+ "position" => {
+ "x" => 6.0,
+ "y" => 7.0,
+ "z" => 8.0,
+ },
+ "texcoord" => {
+ "u" => 9.0,
+ "v" => 10.0,
+ },
+ }
+ ],
+ s.vertices.collect(&:to_h))
+ end
+ end
+ end
+ end
+
+ def test_struct_nested_struct_replace_entire_array_with_different_struct()
+ LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s|
+ different_struct_same_size = Fiddle::Importer.struct [{
+ a: ['float i', 'float j', 'float k'],
+ b: ['float l', 'float m']
+ }]
+
+ different_struct_same_size.malloc(Fiddle::RUBY_FREE) do |different0|
+ different_struct_same_size.malloc(Fiddle::RUBY_FREE) do |different1|
+ different = [different0, different1]
+ different[0].a.i = 11; different[1].a.i = 16
+ different[0].a.j = 12; different[1].a.j = 17
+ different[0].a.k = 13; different[1].a.k = 18
+ different[0].b.l = 14; different[1].b.l = 19
+ different[0].b.m = 15; different[1].b.m = 20
+ s.vertices[0][0, s.vertices[0].class.size] = different[0].to_ptr
+ s.vertices[1][0, s.vertices[1].class.size] = different[1].to_ptr
+ assert_equal([
+ {
+ "position" => {
+ "x" => 11.0,
+ "y" => 12.0,
+ "z" => 13.0,
+ },
+ "texcoord" => {
+ "u" => 14.0,
+ "v" => 15.0,
+ },
+ },
+ {
+ "position" => {
+ "x" => 16.0,
+ "y" => 17.0,
+ "z" => 18.0,
+ },
+ "texcoord" => {
+ "u" => 19.0,
+ "v" => 20.0,
+ },
+ }
+ ],
+ s.vertices.collect(&:to_h))
+ end
+ end
+ end
+ end
+
+ def test_struct()
+ LIBC::MyStruct.malloc(Fiddle::RUBY_FREE) do |s|
+ s.num = [0,1,2,3,4]
+ s.c = ?a.ord
+ s.buff = "012345\377"
+ assert_equal([0,1,2,3,4], s.num)
+ assert_equal(?a.ord, s.c)
+ assert_equal([?0.ord,?1.ord,?2.ord,?3.ord,?4.ord,?5.ord,?\377.ord], s.buff)
+ end
+ end
+
+ def test_gettimeofday()
+ if( defined?(LIBC.gettimeofday) )
+ LIBC::Timeval.malloc(Fiddle::RUBY_FREE) do |timeval|
+ LIBC::Timezone.malloc(Fiddle::RUBY_FREE) do |timezone|
+ LIBC.gettimeofday(timeval, timezone)
+ end
+ cur = Time.now()
+ assert(cur.to_i - 2 <= timeval.tv_sec && timeval.tv_sec <= cur.to_i)
+ end
+ end
+ end
+
+ def test_strcpy()
+ buff = +"000"
+ str = LIBC.strcpy(buff, "123")
+ assert_equal("123", buff)
+ assert_equal("123", str.to_s)
+ end
+
+ def test_isdigit
+ r1 = LIBC.isdigit(?1.ord)
+ r2 = LIBC.isdigit(?2.ord)
+ rr = LIBC.isdigit(?r.ord)
+ assert_operator(r1, :>, 0)
+ assert_operator(r2, :>, 0)
+ assert_equal(0, rr)
+ end
+
+ def test_atof
+ r = LIBC.atof("12.34")
+ assert_includes(12.00..13.00, r)
+ end
+ end
+end if defined?(Fiddle)
diff --git a/test/fiddle/test_memory_view.rb b/test/fiddle/test_memory_view.rb
new file mode 100644
index 0000000000..240cda37df
--- /dev/null
+++ b/test/fiddle/test_memory_view.rb
@@ -0,0 +1,143 @@
+# frozen_string_literal: true
+begin
+ require_relative 'helper'
+rescue LoadError
+ return
+end
+
+begin
+ require '-test-/memory_view'
+rescue LoadError
+ return
+end
+
+module Fiddle
+ class TestMemoryView < TestCase
+ def setup
+ omit "MemoryView is unavailable" unless defined? Fiddle::MemoryView
+ end
+
+ def test_null_ptr
+ assert_raise(ArgumentError) do
+ MemoryView.new(Fiddle::NULL)
+ end
+ end
+
+ def test_memory_view_from_unsupported_obj
+ obj = Object.new
+ assert_raise(ArgumentError) do
+ MemoryView.new(obj)
+ end
+ end
+
+ def test_memory_view_from_pointer
+ str = Marshal.load(Marshal.dump("hello world"))
+ ptr = Pointer[str]
+ mview = MemoryView.new(ptr)
+ assert_same(ptr, mview.obj)
+ assert_equal(str.bytesize, mview.byte_size)
+ assert_equal(true, mview.readonly?)
+ assert_equal(nil, mview.format)
+ assert_equal(1, mview.item_size)
+ assert_equal(1, mview.ndim)
+ assert_equal(nil, mview.shape)
+ assert_equal(nil, mview.strides)
+ assert_equal(nil, mview.sub_offsets)
+
+ codes = str.codepoints
+ assert_equal(codes, (0...str.bytesize).map {|i| mview[i] })
+ end
+
+ def test_memory_view_multi_dimensional
+ omit "MemoryViewTestUtils is unavailable" unless defined? MemoryViewTestUtils
+
+ buf = [ 1, 2, 3, 4,
+ 5, 6, 7, 8,
+ 9, 10, 11, 12 ].pack("l!*")
+ shape = [3, 4]
+ md = MemoryViewTestUtils::MultiDimensionalView.new(buf, "l!", shape, nil)
+ mview = Fiddle::MemoryView.new(md)
+ assert_equal(buf.bytesize, mview.byte_size)
+ assert_equal("l!", mview.format)
+ assert_equal(Fiddle::SIZEOF_LONG, mview.item_size)
+ assert_equal(2, mview.ndim)
+ assert_equal(shape, mview.shape)
+ assert_equal([Fiddle::SIZEOF_LONG*4, Fiddle::SIZEOF_LONG], mview.strides)
+ assert_equal(nil, mview.sub_offsets)
+ assert_equal(1, mview[0, 0])
+ assert_equal(4, mview[0, 3])
+ assert_equal(6, mview[1, 1])
+ assert_equal(10, mview[2, 1])
+ end
+
+ def test_memory_view_multi_dimensional_with_strides
+ 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!*")
+ shape = [2, 8]
+ strides = [4*Fiddle::SIZEOF_LONG*2, Fiddle::SIZEOF_LONG*2]
+ md = MemoryViewTestUtils::MultiDimensionalView.new(buf, "l!", shape, strides)
+ mview = Fiddle::MemoryView.new(md)
+ assert_equal("l!", mview.format)
+ assert_equal(Fiddle::SIZEOF_LONG, mview.item_size)
+ assert_equal(buf.bytesize, mview.byte_size)
+ assert_equal(2, mview.ndim)
+ assert_equal(shape, mview.shape)
+ assert_equal(strides, mview.strides)
+ assert_equal(nil, mview.sub_offsets)
+ assert_equal(1, mview[0, 0])
+ assert_equal(5, mview[0, 2])
+ assert_equal(9, mview[1, 0])
+ assert_equal(15, mview[1, 3])
+ end
+
+ def test_memory_view_multi_dimensional_with_multiple_members
+ 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*")
+ shape = [2, 4]
+ strides = [4*Fiddle::SIZEOF_SHORT*2, Fiddle::SIZEOF_SHORT*2]
+ md = MemoryViewTestUtils::MultiDimensionalView.new(buf, "ss", shape, strides)
+ mview = Fiddle::MemoryView.new(md)
+ assert_equal("ss", mview.format)
+ assert_equal(Fiddle::SIZEOF_SHORT*2, mview.item_size)
+ assert_equal(buf.bytesize, mview.byte_size)
+ assert_equal(2, mview.ndim)
+ assert_equal(shape, mview.shape)
+ assert_equal(strides, mview.strides)
+ assert_equal(nil, mview.sub_offsets)
+ assert_equal([1, 2], mview[0, 0])
+ assert_equal([5, 6], mview[0, 2])
+ 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_pinned.rb b/test/fiddle/test_pinned.rb
new file mode 100644
index 0000000000..f0d375b1cc
--- /dev/null
+++ b/test/fiddle/test_pinned.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+begin
+ require_relative 'helper'
+rescue LoadError
+ return
+end
+
+module Fiddle
+ class TestPinned < Fiddle::TestCase
+ def test_pin_object
+ x = Object.new
+ pinner = Pinned.new x
+ assert_same x, pinner.ref
+ end
+
+ def test_clear
+ pinner = Pinned.new Object.new
+ refute pinner.cleared?
+ pinner.clear
+ assert pinner.cleared?
+ ex = assert_raise(Fiddle::ClearedReferenceError) do
+ pinner.ref
+ end
+ assert_match "called on", ex.message
+ end
+ end
+end
+
diff --git a/test/fiddle/test_pointer.rb b/test/fiddle/test_pointer.rb
new file mode 100644
index 0000000000..7d708ee417
--- /dev/null
+++ b/test/fiddle/test_pointer.rb
@@ -0,0 +1,287 @@
+# frozen_string_literal: true
+begin
+ require_relative 'helper'
+rescue LoadError
+end
+
+module Fiddle
+ class TestPointer < TestCase
+ def dlwrap arg
+ Fiddle.dlwrap arg
+ end
+
+ def test_cptr_to_int
+ null = Fiddle::NULL
+ assert_equal(null.to_i, null.to_int)
+ end
+
+ def test_malloc_free_func_int
+ free = Fiddle::Function.new(Fiddle::RUBY_FREE, [TYPE_VOIDP], TYPE_VOID)
+ assert_equal free.to_i, Fiddle::RUBY_FREE.to_i
+
+ ptr = Pointer.malloc(10, free.to_i)
+ assert_equal 10, ptr.size
+ assert_equal free.to_i, ptr.free.to_i
+ end
+
+ def test_malloc_free_func
+ free = Fiddle::Function.new(Fiddle::RUBY_FREE, [TYPE_VOIDP], TYPE_VOID)
+
+ ptr = Pointer.malloc(10, free)
+ assert_equal 10, ptr.size
+ assert_equal free.to_i, ptr.free.to_i
+ end
+
+ def test_malloc_block
+ escaped_ptr = nil
+ returned = Pointer.malloc(10, Fiddle::RUBY_FREE) do |ptr|
+ assert_equal 10, ptr.size
+ assert_equal Fiddle::RUBY_FREE, ptr.free.to_i
+ escaped_ptr = ptr
+ :returned
+ end
+ assert_equal :returned, returned
+ assert escaped_ptr.freed?
+ end
+
+ def test_malloc_block_no_free
+ assert_raise ArgumentError do
+ Pointer.malloc(10) { |ptr| }
+ end
+ end
+
+ def test_malloc_subclass
+ subclass = Class.new(Pointer)
+ subclass.malloc(10, Fiddle::RUBY_FREE) do |ptr|
+ assert ptr.is_a?(subclass)
+ end
+ end
+
+ def test_to_str
+ str = Marshal.load(Marshal.dump("hello world"))
+ ptr = Pointer[str]
+
+ assert_equal 3, ptr.to_str(3).length
+ assert_equal str, ptr.to_str
+
+ ptr[5] = 0
+ assert_equal "hello\0world", ptr.to_str
+ end
+
+ def test_to_s
+ str = Marshal.load(Marshal.dump("hello world"))
+ ptr = Pointer[str]
+
+ assert_equal 3, ptr.to_s(3).length
+ assert_equal str, ptr.to_s
+
+ ptr[5] = 0
+ assert_equal 'hello', ptr.to_s
+ end
+
+ def test_minus
+ str = "hello world"
+ ptr = Pointer[str]
+ assert_equal ptr.to_s, (ptr + 3 - 3).to_s
+ end
+
+ # TODO: what if the pointer size is 0? raise an exception? do we care?
+ def test_plus
+ str = "hello world"
+ ptr = Pointer[str]
+ new_str = ptr + 3
+ assert_equal 'lo world', new_str.to_s
+ end
+
+ def test_inspect
+ ptr = Pointer.new(0)
+ inspect = ptr.inspect
+ assert_match(/size=#{ptr.size}/, inspect)
+ assert_match(/free=#{sprintf("%#x", ptr.free.to_i)}/, inspect)
+ assert_match(/ptr=#{sprintf("%#x", ptr.to_i)}/, inspect)
+ end
+
+ def test_to_ptr_string
+ str = "hello world"
+ ptr = Pointer[str]
+ assert_equal str.length, ptr.size
+ assert_equal 'hello', ptr[0,5]
+ end
+
+ def test_to_ptr_io
+ Pointer.malloc(10, Fiddle::RUBY_FREE) do |buf|
+ File.open(__FILE__, 'r') do |f|
+ ptr = Pointer.to_ptr f
+ fread = Function.new(@libc['fread'],
+ [TYPE_VOIDP, TYPE_INT, TYPE_INT, TYPE_VOIDP],
+ TYPE_INT)
+ fread.call(buf.to_i, Fiddle::SIZEOF_CHAR, buf.size - 1, ptr.to_i)
+ end
+
+ File.open(__FILE__, 'r') do |f|
+ assert_equal f.read(9), buf.to_s
+ end
+ end
+ end
+
+ def test_to_ptr_with_ptr
+ ptr = Pointer.new 0
+ ptr2 = Pointer.to_ptr Struct.new(:to_ptr).new(ptr)
+ assert_equal ptr, ptr2
+
+ assert_raise(Fiddle::DLError) do
+ Pointer.to_ptr Struct.new(:to_ptr).new(nil)
+ end
+ end
+
+ def test_to_ptr_with_num
+ ptr = Pointer.new 0
+ assert_equal ptr, Pointer[0]
+ end
+
+ def test_equals
+ ptr = Pointer.new 0
+ ptr2 = Pointer.new 0
+ assert_equal ptr2, ptr
+ end
+
+ def test_not_equals
+ ptr = Pointer.new 0
+ refute_equal 10, ptr, '10 should not equal the pointer'
+ end
+
+ def test_cmp
+ ptr = Pointer.new 0
+ assert_nil(ptr <=> 10, '10 should not be comparable')
+ end
+
+ def test_ref_ptr
+ ary = [0,1,2,4,5]
+ addr = Pointer.new(dlwrap(ary))
+ assert_equal addr.to_i, addr.ref.ptr.to_i
+
+ assert_equal addr.to_i, (+ (- addr)).to_i
+ end
+
+ def test_to_value
+ ary = [0,1,2,4,5]
+ addr = Pointer.new(dlwrap(ary))
+ assert_equal ary, addr.to_value
+ end
+
+ def test_free
+ ptr = Pointer.malloc(4)
+ begin
+ assert_nil ptr.free
+ ensure
+ Fiddle.free ptr
+ end
+ end
+
+ def test_free=
+ free = Function.new(Fiddle::RUBY_FREE, [TYPE_VOIDP], TYPE_VOID)
+ ptr = Pointer.malloc(4)
+ ptr.free = free
+
+ assert_equal free.ptr, ptr.free.ptr
+ end
+
+ def test_free_with_func
+ ptr = Pointer.malloc(4, Fiddle::RUBY_FREE)
+ refute ptr.freed?
+ ptr.call_free
+ assert ptr.freed?
+ ptr.call_free # you can safely run it again
+ assert ptr.freed?
+ GC.start # you can safely run the GC routine
+ assert ptr.freed?
+ end
+
+ def test_free_with_no_func
+ ptr = Pointer.malloc(4)
+ refute ptr.freed?
+ ptr.call_free
+ refute ptr.freed?
+ ptr.call_free # you can safely run it again
+ refute ptr.freed?
+ end
+
+ def test_freed?
+ ptr = Pointer.malloc(4, Fiddle::RUBY_FREE)
+ refute ptr.freed?
+ ptr.call_free
+ assert ptr.freed?
+ end
+
+ def test_null?
+ ptr = Pointer.new(0)
+ assert ptr.null?
+ end
+
+ def test_size
+ Pointer.malloc(4, Fiddle::RUBY_FREE) do |ptr|
+ assert_equal 4, ptr.size
+ end
+ end
+
+ def test_size=
+ Pointer.malloc(4, Fiddle::RUBY_FREE) do |ptr|
+ ptr.size = 10
+ assert_equal 10, ptr.size
+ end
+ end
+
+ def test_aref_aset
+ check = Proc.new{|str,ptr|
+ assert_equal(str.size(), ptr.size())
+ assert_equal(str, ptr.to_s())
+ assert_equal(str[0,2], ptr.to_s(2))
+ assert_equal(str[0,2], ptr[0,2])
+ assert_equal(str[1,2], ptr[1,2])
+ assert_equal(str[1,0], ptr[1,0])
+ assert_equal(str[0].ord, ptr[0])
+ assert_equal(str[1].ord, ptr[1])
+ }
+ str = Marshal.load(Marshal.dump('abc'))
+ ptr = Pointer[str]
+ check.call(str, ptr)
+
+ str[0] = "c"
+ assert_equal 'c'.ord, ptr[0] = "c".ord
+ check.call(str, ptr)
+
+ str[0,2] = "aa"
+ assert_equal 'aa', ptr[0,2] = "aa"
+ check.call(str, ptr)
+
+ ptr2 = Pointer['cdeeee']
+ str[0,2] = "cd"
+ assert_equal ptr2, ptr[0,2] = ptr2
+ check.call(str, ptr)
+
+ ptr3 = Pointer['vvvv']
+ str[0,2] = "vv"
+ assert_equal ptr3.to_i, ptr[0,2] = ptr3.to_i
+ check.call(str, ptr)
+ end
+
+ def test_null_pointer
+ nullpo = Pointer.new(0)
+ assert_raise(DLError) {nullpo[0]}
+ assert_raise(DLError) {nullpo[0] = 1}
+ end
+
+ def test_no_memory_leak
+ 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)