ruby/test/fiddle/test_import.rb

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

491 строка
16 KiB
Ruby
Исходник Обычный вид История

# 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
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'
]
}
]
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
type_name = "unsigned #{$1}" if type_name =~ /\Au(long|short|char|int|long long)\z/
define_method("test_sizeof_#{name}") do
assert_equal(size, Fiddle::Importer.sizeof(type_name), type)
end
end
# Assert that the unsigned constants are equal to the "negative" signed ones
# for backwards compatibility
def test_unsigned_equals_negative_signed
Fiddle.constants.grep(/\ATYPE_(?!VOID|VARIADIC\z)(U.*)/) do |unsigned|
assert_equal(-Fiddle.const_get(unsigned.to_s.sub(/U/, '')),
Fiddle.const_get(unsigned))
end
end
[ruby/fiddle] Move "type" constants to `Fiddle::Types` (https://github.com/ruby/fiddle/pull/112) This helps to reduce repetition in code. Instead of doing "TYPE_*" everywhere, you can do `include Fiddle::Types`, and write the type name directly. This PR is to help reduce repetition when writing Fiddle code. Right now we have to type `TYPE_` everywhere, and you also have to include all of `Fiddle` to access `TYPE_*` constants. With this change, you can just include `Fiddle::Types` and it will shorten your code and also you only have to include those constants. Here is an example before: ```ruby require "fiddle" module MMAP # All Fiddle constants included include Fiddle def self.make_function name, args, ret ptr = Handle::DEFAULT[name] func = Function.new ptr, args, ret, name: name define_singleton_method name, &func.to_proc end make_function "munmap", [TYPE_VOIDP, # addr TYPE_SIZE_T], # len TYPE_INT make_function "mmap", [TYPE_VOIDP, TYPE_SIZE_T, TYPE_INT, TYPE_INT, TYPE_INT, TYPE_INT], TYPE_VOIDP make_function "mprotect", [TYPE_VOIDP, TYPE_SIZE_T, TYPE_INT], TYPE_INT end ``` After: ```ruby require "fiddle" module MMAP # Only type names included include Fiddle::Types def self.make_function name, args, ret ptr = Fiddle::Handle::DEFAULT[name] func = Fiddle::Function.new ptr, args, ret, name: name define_singleton_method name, &func.to_proc end make_function "munmap", [VOIDP, # addr SIZE_T], # len INT make_function "mmap", [VOIDP, SIZE_T, INT, INT, INT, INT], VOIDP make_function "mprotect", [VOIDP, SIZE_T, INT], INT end ``` We only need to import the type names, and you don't have to type `TYPE_` over and over. I think this makes Fiddle code easier to read. https://github.com/ruby/fiddle/commit/49fa7233e5 Co-authored-by: Sutou Kouhei <kou@clear-code.com>
2022-09-13 18:26:47 +03:00
def test_type_constants
Fiddle::Types.constants.each do |const|
assert_equal Fiddle::Types.const_get(const), Fiddle.const_get("TYPE_#{const}")
end
end
def test_unsigned_result()
d = (2 ** 31) + 1
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)