diff options
Diffstat (limited to 'test/fiddle')
| -rw-r--r-- | test/fiddle/helper.rb | 178 | ||||
| -rw-r--r-- | test/fiddle/test_c_struct_builder.rb | 69 | ||||
| -rw-r--r-- | test/fiddle/test_c_struct_entry.rb | 165 | ||||
| -rw-r--r-- | test/fiddle/test_c_union_entity.rb | 36 | ||||
| -rw-r--r-- | test/fiddle/test_closure.rb | 110 | ||||
| -rw-r--r-- | test/fiddle/test_cparser.rb | 374 | ||||
| -rw-r--r-- | test/fiddle/test_fiddle.rb | 17 | ||||
| -rw-r--r-- | test/fiddle/test_func.rb | 139 | ||||
| -rw-r--r-- | test/fiddle/test_function.rb | 227 | ||||
| -rw-r--r-- | test/fiddle/test_handle.rb | 208 | ||||
| -rw-r--r-- | test/fiddle/test_import.rb | 479 | ||||
| -rw-r--r-- | test/fiddle/test_memory_view.rb | 143 | ||||
| -rw-r--r-- | test/fiddle/test_pinned.rb | 28 | ||||
| -rw-r--r-- | test/fiddle/test_pointer.rb | 287 |
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) |
