From fcbf63e62c627deae76c1b8cb8c0876c536ed811 Mon Sep 17 00:00:00 2001 From: Jari Vetoniemi Date: Mon, 16 Mar 2020 18:49:26 +0900 Subject: Fresh start --- jni/ruby/test/fiddle/helper.rb | 124 +++++++++++++++ jni/ruby/test/fiddle/test_c_struct_entry.rb | 76 +++++++++ jni/ruby/test/fiddle/test_c_union_entity.rb | 34 ++++ jni/ruby/test/fiddle/test_closure.rb | 84 ++++++++++ jni/ruby/test/fiddle/test_cparser.rb | 35 ++++ jni/ruby/test/fiddle/test_fiddle.rb | 16 ++ jni/ruby/test/fiddle/test_func.rb | 92 +++++++++++ jni/ruby/test/fiddle/test_function.rb | 82 ++++++++++ jni/ruby/test/fiddle/test_handle.rb | 195 +++++++++++++++++++++++ jni/ruby/test/fiddle/test_import.rb | 150 ++++++++++++++++++ jni/ruby/test/fiddle/test_pointer.rb | 237 ++++++++++++++++++++++++++++ 11 files changed, 1125 insertions(+) create mode 100644 jni/ruby/test/fiddle/helper.rb create mode 100644 jni/ruby/test/fiddle/test_c_struct_entry.rb create mode 100644 jni/ruby/test/fiddle/test_c_union_entity.rb create mode 100644 jni/ruby/test/fiddle/test_closure.rb create mode 100644 jni/ruby/test/fiddle/test_cparser.rb create mode 100644 jni/ruby/test/fiddle/test_fiddle.rb create mode 100644 jni/ruby/test/fiddle/test_func.rb create mode 100644 jni/ruby/test/fiddle/test_function.rb create mode 100644 jni/ruby/test/fiddle/test_handle.rb create mode 100644 jni/ruby/test/fiddle/test_import.rb create mode 100644 jni/ruby/test/fiddle/test_pointer.rb (limited to 'jni/ruby/test/fiddle') diff --git a/jni/ruby/test/fiddle/helper.rb b/jni/ruby/test/fiddle/helper.rb new file mode 100644 index 0000000..bc98f85 --- /dev/null +++ b/jni/ruby/test/fiddle/helper.rb @@ -0,0 +1,124 @@ +require 'minitest/autorun' +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 /x86_64-linux/ + libc_so = "/lib64/libc.so.6" + libm_so = "/lib64/libm.so.6" +when /linux/ + libdir = '/lib' + case [0].pack('L!').size + when 4 + # 32-bit ruby + libdir = '/lib32' if File.directory? '/lib32' + when 8 + # 64-bit ruby + libdir = '/lib64' if File.directory? '/lib64' + end + libc_so = File.join(libdir, "libc.so.6") + libm_so = File.join(libdir, "libm.so.6") +when /mingw/, /mswin/ + require "rbconfig" + libc_so = libm_so = RbConfig::CONFIG["RUBY_SO_NAME"].split(/-/).find{|e| /^msvc/ =~ e} + ".dll" +when /darwin/ + libc_so = "/usr/lib/libc.dylib" + libm_so = "/usr/lib/libm.dylib" +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 [0].pack('L!').size + 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 +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} ") + end +end + +libc_so = nil if !libc_so || (libc_so[0] == ?/ && !File.file?(libc_so)) +libm_so = nil if !libm_so || (libm_so[0] == ?/ && !File.file?(libm_so)) + +if !libc_so || !libm_so + ruby = EnvUtil.rubybin + 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 < MiniTest::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 + end +end diff --git a/jni/ruby/test/fiddle/test_c_struct_entry.rb b/jni/ruby/test/fiddle/test_c_struct_entry.rb new file mode 100644 index 0000000..de5449b --- /dev/null +++ b/jni/ruby/test/fiddle/test_c_struct_entry.rb @@ -0,0 +1,76 @@ +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 + union = CStructEntity.malloc [TYPE_INT, TYPE_LONG] + union.assign_names %w[int long] + + # this test is roundabout because the stored ctypes are not accessible + union['long'] = 1 + union['int'] = 2 + + assert_equal 1, union['long'] + assert_equal 2, union['int'] + end + + def test_aref_pointer_array + team = CStructEntity.malloc([[TYPE_VOIDP, 2]]) + team.assign_names(["names"]) + alice = Fiddle::Pointer.malloc(6) + alice[0, 6] = "Alice\0" + bob = Fiddle::Pointer.malloc(4) + bob[0, 4] = "Bob\0" + team["names"] = [alice, bob] + assert_equal(["Alice", "Bob"], team["names"].map(&:to_s)) + end + + def test_aref_pointer + user = CStructEntity.malloc([TYPE_VOIDP]) + user.assign_names(["name"]) + alice = Fiddle::Pointer.malloc(6) + alice[0, 6] = "Alice\0" + user["name"] = alice + assert_equal("Alice", user["name"].to_s) + end + end +end if defined?(Fiddle) diff --git a/jni/ruby/test/fiddle/test_c_union_entity.rb b/jni/ruby/test/fiddle/test_c_union_entity.rb new file mode 100644 index 0000000..165c4ec --- /dev/null +++ b/jni/ruby/test/fiddle/test_c_union_entity.rb @@ -0,0 +1,34 @@ +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 + union = CUnionEntity.malloc [TYPE_INT, TYPE_LONG] + 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 if defined?(Fiddle) diff --git a/jni/ruby/test/fiddle/test_closure.rb b/jni/ruby/test/fiddle/test_closure.rb new file mode 100644 index 0000000..56839e7 --- /dev/null +++ b/jni/ruby/test/fiddle/test_closure.rb @@ -0,0 +1,84 @@ +begin + require_relative 'helper' +rescue LoadError +end + +module Fiddle + class TestClosure < Fiddle::TestCase + def test_argument_errors + assert_raises(TypeError) do + Closure.new(TYPE_INT, TYPE_INT) + end + + assert_raises(TypeError) do + Closure.new('foo', [TYPE_INT]) + end + + assert_raises(TypeError) do + Closure.new(TYPE_INT, ['meow!']) + end + 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_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/jni/ruby/test/fiddle/test_cparser.rb b/jni/ruby/test/fiddle/test_cparser.rb new file mode 100644 index 0000000..666d8c8 --- /dev/null +++ b/jni/ruby/test/fiddle/test_cparser.rb @@ -0,0 +1,35 @@ +begin + require_relative 'helper' + require 'fiddle/cparser' +rescue LoadError +end + +module Fiddle + class TestCParser < TestCase + include CParser + + def test_uint_ctype + assert_equal(-TYPE_INT, parse_ctype('uint')) + end + + def test_size_t_ctype + assert_equal(TYPE_SIZE_T, parse_ctype("size_t")) + end + + def test_ssize_t_ctype + assert_equal(TYPE_SSIZE_T, parse_ctype("ssize_t")) + end + + def test_ptrdiff_t_ctype + assert_equal(TYPE_PTRDIFF_T, parse_ctype("ptrdiff_t")) + end + + def test_intptr_t_ctype + assert_equal(TYPE_INTPTR_T, parse_ctype("intptr_t")) + end + + def test_uintptr_t_ctype + assert_equal(TYPE_UINTPTR_T, parse_ctype("uintptr_t")) + end + end +end if defined?(Fiddle) diff --git a/jni/ruby/test/fiddle/test_fiddle.rb b/jni/ruby/test/fiddle/test_fiddle.rb new file mode 100644 index 0000000..4c6ab97 --- /dev/null +++ b/jni/ruby/test/fiddle/test_fiddle.rb @@ -0,0 +1,16 @@ +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/jni/ruby/test/fiddle/test_func.rb b/jni/ruby/test/fiddle/test_func.rb new file mode 100644 index 0000000..529aaa8 --- /dev/null +++ b/jni/ruby/test/fiddle/test_func.rb @@ -0,0 +1,92 @@ +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_syscall_with_tainted_string + f = Function.new(@libc['system'], [TYPE_VOIDP], TYPE_INT) + assert_raises(SecurityError) do + Thread.new { + $SAFE = 1 + f.call("uname -rs".taint) + }.join + end + end + + def test_sinf + begin + f = Function.new(@libm['sinf'], [TYPE_FLOAT], TYPE_FLOAT) + rescue Fiddle::DLError + skip "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 + stress, GC.stress = GC.stress, true + f = Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP) + buff = "000" + str = f.call(buff, "123") + assert_equal("123", buff) + assert_equal("123", str.to_s) + ensure + GC.stress = stress + 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" + EnvUtil.under_gc_stress {qsort.call(buff, buff.size, 1, cb)} + assert_equal("1349", buff, bug4929) + end + end +end if defined?(Fiddle) diff --git a/jni/ruby/test/fiddle/test_function.rb b/jni/ruby/test/fiddle/test_function.rb new file mode 100644 index 0000000..de7c958 --- /dev/null +++ b/jni/ruby/test/fiddle/test_function.rb @@ -0,0 +1,82 @@ +begin + require_relative 'helper' +rescue LoadError +end + +module Fiddle + class TestFunction < Fiddle::TestCase + include Test::Unit::Assertions + + def setup + super + Fiddle.last_error = nil + 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_argument_errors + assert_raises(TypeError) do + Function.new(@libm['sin'], TYPE_DOUBLE, TYPE_DOUBLE) + end + + assert_raises(TypeError) do + Function.new(@libm['sin'], ['foo'], TYPE_DOUBLE) + end + + assert_raises(TypeError) do + Function.new(@libm['sin'], [TYPE_DOUBLE], 'foo') + end + 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_raises(ArgumentError) do + func.call(1,2,3) + end + assert_raises(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 + + 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 test_no_memory_leak + prep = 'r = Fiddle::Function.new(Fiddle.dlopen(nil)["rb_obj_tainted"], [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 +end if defined?(Fiddle) diff --git a/jni/ruby/test/fiddle/test_handle.rb b/jni/ruby/test/fiddle/test_handle.rb new file mode 100644 index 0000000..ffbde41 --- /dev/null +++ b/jni/ruby/test/fiddle/test_handle.rb @@ -0,0 +1,195 @@ +begin + require_relative 'helper' +rescue LoadError +end + +module Fiddle + class TestHandle < TestCase + include Fiddle + + include Test::Unit::Assertions + + def test_to_i + handle = Fiddle::Handle.new(LIBC_SO) + assert_kind_of Integer, handle.to_i + end + + def test_static_sym_secure + assert_raises(SecurityError) do + Thread.new do + $SAFE = 2 + Fiddle::Handle.sym('calloc') + end.join + end + end + + def test_static_sym_unknown + assert_raises(DLError) { Fiddle::Handle.sym('fooo') } + assert_raises(DLError) { Fiddle::Handle['fooo'] } + end + + def test_static_sym + skip "Fiddle::Handle.sym is not supported" if /mswin|mingw/ =~ RUBY_PLATFORM + begin + # Linux / Darwin / FreeBSD + refute_nil Fiddle::Handle.sym('dlopen') + assert_equal Fiddle::Handle.sym('dlopen'), Fiddle::Handle['dlopen'] + rescue + # NetBSD + require 'objspace' + refute_nil Fiddle::Handle.sym('Init_objspace') + assert_equal Fiddle::Handle.sym('Init_objspace'), Fiddle::Handle['Init_objspace'] + end + end + + def test_sym_closed_handle + handle = Fiddle::Handle.new(LIBC_SO) + handle.close + assert_raises(DLError) { handle.sym("calloc") } + assert_raises(DLError) { handle["calloc"] } + end + + def test_sym_unknown + handle = Fiddle::Handle.new(LIBC_SO) + assert_raises(DLError) { handle.sym('fooo') } + assert_raises(DLError) { handle['fooo'] } + end + + def test_sym_with_bad_args + handle = Handle.new(LIBC_SO) + assert_raises(TypeError) { handle.sym(nil) } + assert_raises(TypeError) { handle[nil] } + end + + def test_sym_secure + assert_raises(SecurityError) do + Thread.new do + $SAFE = 2 + handle = Handle.new(LIBC_SO) + handle.sym('calloc') + end.join + end + 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_raises(DLError) do + handle.close + end + end + + def test_dlopen_returns_handle + assert_instance_of Handle, dlopen(LIBC_SO) + end + + def test_dlopen_safe + assert_raises(SecurityError) do + Thread.new do + $SAFE = 2 + dlopen(LIBC_SO) + end.join + end + end + + def test_initialize_safe + assert_raises(SecurityError) do + Thread.new do + $SAFE = 2 + Handle.new(LIBC_SO) + end.join + end + 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_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'] + rescue + # 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 'objspace' + handle = Handle::NEXT + refute_nil handle['Init_objspace'] + end + end unless /mswin|mingw/ =~ RUBY_PLATFORM + + def test_DEFAULT + skip "Handle::DEFAULT is not supported" if /mswin|mingw/ =~ RUBY_PLATFORM + 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 + assert_no_memory_leak(%w[-W0 -rfiddle.so], '', '100_000.times {Fiddle::Handle.allocate}; GC.start', rss: true) + end + end +end if defined?(Fiddle) diff --git a/jni/ruby/test/fiddle/test_import.rb b/jni/ruby/test/fiddle/test_import.rb new file mode 100644 index 0000000..c83f50f --- /dev/null +++ b/jni/ruby/test/fiddle/test_import.rb @@ -0,0 +1,150 @@ +# coding: US-ASCII +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*)" + 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]", + ] + + 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_raises(RuntimeError) do + Class.new do + extend Importer + extern "void *strcpy(char*, char*)" + end + end + assert_match(/call dlload before/, err.message) + end + + def test_malloc() + s1 = LIBC::Timeval.malloc() + s2 = LIBC::Timeval.malloc() + refute_equal(s1.to_ptr.to_i, s2.to_ptr.to_i) + end + + def test_sizeof() + assert_equal(SIZEOF_VOIDP, LIBC.sizeof("FILE*")) + assert_equal(LIBC::MyStruct.size(), LIBC.sizeof(LIBC::MyStruct)) + assert_equal(LIBC::MyStruct.size(), LIBC.sizeof(LIBC::MyStruct.malloc())) + assert_equal(SIZEOF_LONG_LONG, LIBC.sizeof("long long")) + end + + Fiddle.constants.grep(/\ATYPE_(?!VOID\z)(.*)/) do + type = $& + size = Fiddle.const_get("SIZEOF_#{$1}") + name = $1.sub(/P\z/,"*").gsub(/_(?!T\z)/, " ").downcase + define_method("test_sizeof_#{name}") do + assert_equal(size, Fiddle::Importer.sizeof(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 ) + 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() + s = LIBC::MyStruct.malloc() + 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 + + def test_gettimeofday() + if( defined?(LIBC.gettimeofday) ) + timeval = LIBC::Timeval.malloc() + timezone = LIBC::Timezone.malloc() + LIBC.gettimeofday(timeval, timezone) + cur = Time.now() + assert(cur.to_i - 2 <= timeval.tv_sec && timeval.tv_sec <= cur.to_i) + 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/jni/ruby/test/fiddle/test_pointer.rb b/jni/ruby/test/fiddle/test_pointer.rb new file mode 100644 index 0000000..3ea9bc8 --- /dev/null +++ b/jni/ruby/test/fiddle/test_pointer.rb @@ -0,0 +1,237 @@ +begin + require_relative 'helper' +rescue LoadError +end + +module Fiddle + class TestPointer < TestCase + def dlwrap arg + Fiddle.dlwrap arg + end + + include Test::Unit::Assertions + + 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_to_str + str = "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 = "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 ptr.tainted?, 'pointer should be tainted' + assert_equal str.length, ptr.size + assert_equal 'hello', ptr[0,5] + end + + def test_to_ptr_io + buf = Pointer.malloc(10) + 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 + + 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_raises(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) + assert_nil ptr.free + end + + def test_free= + assert_normal_exit(<<-"End", '[ruby-dev:39269]') + require 'fiddle' + Fiddle::LIBC_SO = #{Fiddle::LIBC_SO.dump} + Fiddle::LIBM_SO = #{Fiddle::LIBM_SO.dump} + include Fiddle + @libc = dlopen(LIBC_SO) + @libm = dlopen(LIBM_SO) + free = Fiddle::Function.new(Fiddle::RUBY_FREE, [TYPE_VOIDP], TYPE_VOID) + ptr = Fiddle::Pointer.malloc(4) + ptr.free = free + free.ptr + ptr.free.ptr + End + + free = Function.new(Fiddle::RUBY_FREE, [TYPE_VOIDP], TYPE_VOID) + ptr = Pointer.malloc(4) + ptr.free = free + + assert_equal free.ptr, ptr.free.ptr + end + + def test_null? + ptr = Pointer.new(0) + assert ptr.null? + end + + def test_size + ptr = Pointer.malloc(4) + assert_equal 4, ptr.size + Fiddle.free ptr.to_i + end + + def test_size= + ptr = Pointer.malloc(4) + ptr.size = 10 + assert_equal 10, ptr.size + Fiddle.free ptr.to_i + 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 = '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 + assert_no_memory_leak(%w[-W0 -rfiddle.so], '', '100_000.times {Fiddle::Pointer.allocate}', rss: true) + end + end +end if defined?(Fiddle) -- cgit v1.2.3-70-g09d2