1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
|
#
# This file is part of ruby-ffi.
# For licensing, see LICENSE.SPECS
#
require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper"))
require 'delegate'
module PointerTestLib
extend FFI::Library
ffi_lib TestLibrary::PATH
begin
attach_function :ptr_ret_int32_t, [ :pointer, :int ], :int
rescue FFI::NotFoundError
# NetBSD uses #define instead of typedef for these
attach_function :ptr_ret_int32_t, :ptr_ret___int32_t, [ :pointer, :int ], :int
end
attach_function :ptr_from_address, [ FFI::Platform::ADDRESS_SIZE == 32 ? :uint : :ulong_long ], :pointer
attach_function :ptr_set_pointer, [ :pointer, :int, :pointer ], :void
attach_function :ptr_ret_pointer, [ :pointer, :int ], :pointer
end
describe "Pointer" do
include FFI
class ToPtrTest
def initialize(ptr)
@ptr = ptr
end
def to_ptr
@ptr
end
end
it "Any object implementing #to_ptr can be passed as a :pointer parameter" do
memory = FFI::MemoryPointer.new :long_long
magic = 0x12345678
memory.put_int32(0, magic)
tp = ToPtrTest.new(memory)
expect(PointerTestLib.ptr_ret_int32_t(tp, 0)).to eq(magic)
end
class PointerDelegate < DelegateClass(FFI::Pointer)
def initialize(ptr)
@ptr = ptr
end
def to_ptr
@ptr
end
end
it "A DelegateClass(Pointer) can be passed as a :pointer parameter" do
memory = FFI::MemoryPointer.new :long_long
magic = 0x12345678
memory.put_int32(0, magic)
ptr = PointerDelegate.new(memory)
expect(PointerTestLib.ptr_ret_int32_t(ptr, 0)).to eq(magic)
end
it "Fixnum cannot be used as a Pointer argument" do
expect { PointerTestLib.ptr_ret_int32(0, 0) }.to raise_error
end
it "Bignum cannot be used as a Pointer argument" do
expect { PointerTestLib.ptr_ret_int32(0xfee1deadbeefcafebabe, 0) }.to raise_error
end
describe "pointer type methods" do
it "#read_pointer" do
memory = FFI::MemoryPointer.new :pointer
PointerTestLib.ptr_set_pointer(memory, 0, PointerTestLib.ptr_from_address(0xdeadbeef))
expect(memory.read_pointer.address).to eq(0xdeadbeef)
end
it "#write_pointer" do
memory = FFI::MemoryPointer.new :pointer
memory.write_pointer(PointerTestLib.ptr_from_address(0xdeadbeef))
expect(PointerTestLib.ptr_ret_pointer(memory, 0).address).to eq(0xdeadbeef)
end
it "#read_array_of_pointer" do
values = [0x12345678, 0xfeedf00d, 0xdeadbeef]
memory = FFI::MemoryPointer.new :pointer, values.size
values.each_with_index do |address, j|
PointerTestLib.ptr_set_pointer(memory, j * FFI.type_size(:pointer), PointerTestLib.ptr_from_address(address))
end
array = memory.read_array_of_pointer(values.size)
values.each_with_index do |address, j|
expect(array[j].address).to eq(address)
end
end
end
describe 'NULL' do
it 'should be obtained using Pointer::NULL constant' do
null_ptr = FFI::Pointer::NULL
expect(null_ptr).to be_null
end
it 'should be obtained passing address 0 to constructor' do
expect(FFI::Pointer.new(0)).to be_null
end
it 'should raise an error when attempting read/write operations on it' do
null_ptr = FFI::Pointer::NULL
expect { null_ptr.read_int }.to raise_error(FFI::NullPointerError)
expect { null_ptr.write_int(0xff1) }.to raise_error(FFI::NullPointerError)
end
it 'returns true when compared with nil' do
expect((FFI::Pointer::NULL == nil)).to be true
end
end
it "Pointer.size returns sizeof pointer on platform" do
expect(FFI::Pointer.size).to eq((FFI::Platform::ADDRESS_SIZE / 8))
end
describe "#slice" do
before(:each) do
@mptr = FFI::MemoryPointer.new(:char, 12)
@mptr.put_uint(0, 0x12345678)
@mptr.put_uint(4, 0xdeadbeef)
end
it "contents of sliced pointer matches original pointer at offset" do
expect(@mptr.slice(4, 4).get_uint(0)).to eq(0xdeadbeef)
end
it "modifying sliced pointer is reflected in original pointer" do
@mptr.slice(4, 4).put_uint(0, 0xfee1dead)
expect(@mptr.get_uint(4)).to eq(0xfee1dead)
end
it "access beyond bounds should raise IndexError" do
expect { @mptr.slice(4, 4).get_int(4) }.to raise_error(IndexError)
end
end
describe "#type_size" do
it "should be same as FFI.type_size(type)" do
expect(FFI::MemoryPointer.new(:int, 1).type_size).to eq(FFI.type_size(:int))
end
end
end
describe "AutoPointer" do
loop_count = 30
wiggle_room = 5 # GC rarely cleans up all objects. we can get most of them, and that's enough to determine if the basic functionality is working.
magic = 0x12345678
class AutoPointerTestHelper
@@count = 0
def self.release
@@count += 1 if @@count > 0
end
def self.reset
@@count = 0
end
def self.gc_everything(count)
loop = 5
while @@count < count && loop > 0
loop -= 1
TestLibrary.force_gc
sleep 0.05 unless @@count == count
end
@@count = 0
end
def self.finalizer
self.method(:release).to_proc
end
end
class AutoPointerSubclass < FFI::AutoPointer
def self.release(ptr); end
end
it "cleanup via default release method" do
expect(AutoPointerSubclass).to receive(:release).at_least(loop_count-wiggle_room).times
AutoPointerTestHelper.reset
loop_count.times do
# note that if we called
# AutoPointerTestHelper.method(:release).to_proc inline, we'd
# have a reference to the pointer and it would never get GC'd.
AutoPointerSubclass.new(PointerTestLib.ptr_from_address(magic))
end
AutoPointerTestHelper.gc_everything loop_count
end
it "cleanup when passed a proc" do
# NOTE: passing a proc is touchy, because it's so easy to create a memory leak.
#
# specifically, if we made an inline call to
#
# AutoPointerTestHelper.method(:release).to_proc
#
# we'd have a reference to the pointer and it would
# never get GC'd.
expect(AutoPointerTestHelper).to receive(:release).at_least(loop_count-wiggle_room).times
AutoPointerTestHelper.reset
loop_count.times do
FFI::AutoPointer.new(PointerTestLib.ptr_from_address(magic),
AutoPointerTestHelper.finalizer)
end
AutoPointerTestHelper.gc_everything loop_count
end
it "cleanup when passed a method" do
expect(AutoPointerTestHelper).to receive(:release).at_least(loop_count-wiggle_room).times
AutoPointerTestHelper.reset
loop_count.times do
FFI::AutoPointer.new(PointerTestLib.ptr_from_address(magic),
AutoPointerTestHelper.method(:release))
end
AutoPointerTestHelper.gc_everything loop_count
end
it "can be used as the return type of a function" do
expect do
Module.new do
extend FFI::Library
ffi_lib TestLibrary::PATH
class CustomAutoPointer < FFI::AutoPointer
def self.release(ptr); end
end
attach_function :ptr_from_address, [ FFI::Platform::ADDRESS_SIZE == 32 ? :uint : :ulong_long ], CustomAutoPointer
end
end.not_to raise_error
end
describe "#new" do
it "MemoryPointer argument raises TypeError" do
expect { FFI::AutoPointer.new(FFI::MemoryPointer.new(:int))}.to raise_error(::TypeError)
end
it "AutoPointer argument raises TypeError" do
expect { AutoPointerSubclass.new(AutoPointerSubclass.new(PointerTestLib.ptr_from_address(0))) }.to raise_error(::TypeError)
end
it "Buffer argument raises TypeError" do
expect { FFI::AutoPointer.new(FFI::Buffer.new(:int))}.to raise_error(::TypeError)
end
end
describe "#autorelease?" do
ptr_class = Class.new(FFI::AutoPointer) do
def self.release(ptr); end
end
it "should be true by default" do
expect(ptr_class.new(FFI::Pointer.new(0xdeadbeef)).autorelease?).to be true
end
it "should return false when autorelease=(false)" do
ptr = ptr_class.new(FFI::Pointer.new(0xdeadbeef))
ptr.autorelease = false
expect(ptr.autorelease?).to be false
end
end
describe "#type_size" do
ptr_class = Class.new(FFI::AutoPointer) do
def self.release(ptr); end
end
it "type_size of AutoPointer should match wrapped Pointer" do
aptr = ptr_class.new(FFI::Pointer.new(:int, 0xdeadbeef))
expect(aptr.type_size).to eq(FFI.type_size(:int))
end
it "[] offset should match wrapped Pointer" do
mptr = FFI::MemoryPointer.new(:int, 1024)
aptr = ptr_class.new(FFI::Pointer.new(:int, mptr))
aptr[0].write_uint(0xfee1dead)
aptr[1].write_uint(0xcafebabe)
expect(mptr[0].read_uint).to eq(0xfee1dead)
expect(mptr[1].read_uint).to eq(0xcafebabe)
end
end
end
|