import py from cffi import FFI import math, os, sys import ctypes.util from cffi.backend_ctypes import CTypesBackend from testing.udir import udir try: from StringIO import StringIO except ImportError: from io import StringIO class FdWriteCapture(object): """xxx limited to capture at most 512 bytes of output, according to the Posix manual.""" def __init__(self, capture_fd): self.capture_fd = capture_fd def __enter__(self): self.read_fd, self.write_fd = os.pipe() self.copy_fd = os.dup(self.capture_fd) os.dup2(self.write_fd, self.capture_fd) return self def __exit__(self, *args): os.dup2(self.copy_fd, self.capture_fd) os.close(self.copy_fd) os.close(self.write_fd) self._value = os.read(self.read_fd, 512) os.close(self.read_fd) def getvalue(self): return self._value lib_m = 'm' if sys.platform == 'win32': #there is a small chance this fails on Mingw via environ $CC import distutils.ccompiler if distutils.ccompiler.get_default_compiler() == 'msvc': lib_m = 'msvcrt' class TestFunction(object): Backend = CTypesBackend def test_sin(self): ffi = FFI(backend=self.Backend()) ffi.cdef(""" double sin(double x); """) m = ffi.dlopen(lib_m) x = m.sin(1.23) assert x == math.sin(1.23) def test_sinf(self): if sys.platform == 'win32': py.test.skip("no sinf found in the Windows stdlib") ffi = FFI(backend=self.Backend()) ffi.cdef(""" float sinf(float x); """) m = ffi.dlopen(lib_m) x = m.sinf(1.23) assert type(x) is float assert x != math.sin(1.23) # rounding effects assert abs(x - math.sin(1.23)) < 1E-6 def test_sin_no_return_value(self): # check that 'void'-returning functions work too ffi = FFI(backend=self.Backend()) ffi.cdef(""" void sin(double x); """) m = ffi.dlopen(lib_m) x = m.sin(1.23) assert x is None def test_dlopen_filename(self): path = ctypes.util.find_library(lib_m) if not path: py.test.skip("%s not found" % lib_m) ffi = FFI(backend=self.Backend()) ffi.cdef(""" double cos(double x); """) m = ffi.dlopen(path) x = m.cos(1.23) assert x == math.cos(1.23) m = ffi.dlopen(os.path.basename(path)) x = m.cos(1.23) assert x == math.cos(1.23) def test_dlopen_flags(self): ffi = FFI(backend=self.Backend()) ffi.cdef(""" double cos(double x); """) m = ffi.dlopen(lib_m, ffi.RTLD_LAZY | ffi.RTLD_LOCAL) x = m.cos(1.23) assert x == math.cos(1.23) def test_tlsalloc(self): if sys.platform != 'win32': py.test.skip("win32 only") if self.Backend is CTypesBackend: py.test.skip("ctypes complains on wrong calling conv") ffi = FFI(backend=self.Backend()) ffi.cdef("long TlsAlloc(void); int TlsFree(long);") lib = ffi.dlopen('KERNEL32.DLL') x = lib.TlsAlloc() assert x != 0 y = lib.TlsFree(x) assert y != 0 def test_fputs(self): if not sys.platform.startswith('linux'): py.test.skip("probably no symbol 'stderr' in the lib") ffi = FFI(backend=self.Backend()) ffi.cdef(""" int fputs(const char *, void *); void *stderr; """) ffi.C = ffi.dlopen(None) ffi.C.fputs # fetch before capturing, for easier debugging with FdWriteCapture(2) as fd: ffi.C.fputs(b"hello\n", ffi.C.stderr) ffi.C.fputs(b" world\n", ffi.C.stderr) res = fd.getvalue() assert res == b'hello\n world\n' def test_fputs_without_const(self): if not sys.platform.startswith('linux'): py.test.skip("probably no symbol 'stderr' in the lib") ffi = FFI(backend=self.Backend()) ffi.cdef(""" int fputs(char *, void *); void *stderr; """) ffi.C = ffi.dlopen(None) ffi.C.fputs # fetch before capturing, for easier debugging with FdWriteCapture(2) as fd: ffi.C.fputs(b"hello\n", ffi.C.stderr) ffi.C.fputs(b" world\n", ffi.C.stderr) res = fd.getvalue() assert res == b'hello\n world\n' def test_vararg(self): if not sys.platform.startswith('linux'): py.test.skip("probably no symbol 'stderr' in the lib") ffi = FFI(backend=self.Backend()) ffi.cdef(""" int fprintf(void *, const char *format, ...); void *stderr; """) ffi.C = ffi.dlopen(None) with FdWriteCapture(2) as fd: ffi.C.fprintf(ffi.C.stderr, b"hello with no arguments\n") ffi.C.fprintf(ffi.C.stderr, b"hello, %s!\n", ffi.new("char[]", b"world")) ffi.C.fprintf(ffi.C.stderr, ffi.new("char[]", b"hello, %s!\n"), ffi.new("char[]", b"world2")) ffi.C.fprintf(ffi.C.stderr, b"hello int %d long %ld long long %lld\n", ffi.cast("int", 42), ffi.cast("long", 84), ffi.cast("long long", 168)) ffi.C.fprintf(ffi.C.stderr, b"hello %p\n", ffi.NULL) res = fd.getvalue() assert res == (b"hello with no arguments\n" b"hello, world!\n" b"hello, world2!\n" b"hello int 42 long 84 long long 168\n" b"hello (nil)\n") def test_must_specify_type_of_vararg(self): ffi = FFI(backend=self.Backend()) ffi.cdef(""" int printf(const char *format, ...); """) ffi.C = ffi.dlopen(None) e = py.test.raises(TypeError, ffi.C.printf, b"hello %d\n", 42) assert str(e.value) == ("argument 2 passed in the variadic part " "needs to be a cdata object (got int)") def test_function_has_a_c_type(self): ffi = FFI(backend=self.Backend()) ffi.cdef(""" int puts(const char *); """) ffi.C = ffi.dlopen(None) fptr = ffi.C.puts assert ffi.typeof(fptr) == ffi.typeof("int(*)(const char*)") if self.Backend is CTypesBackend: assert repr(fptr).startswith("" % (cb,) res = fptr(b"Hello") assert res == 42 # if not sys.platform.startswith('linux'): py.test.skip("probably no symbol 'stderr' in the lib") ffi.cdef(""" int fputs(const char *, void *); void *stderr; """) ffi.C = ffi.dlopen(None) fptr = ffi.cast("int(*)(const char *txt, void *)", ffi.C.fputs) assert fptr == ffi.C.fputs assert repr(fptr).startswith(" lambda (closure) -> container -> callback return callback class Data(object): pass ffi = FFI(backend=self.Backend()) data = Data() callback = make_callback(data) wr = weakref.ref(data) del callback, data for i in range(3): if wr() is not None: import gc; gc.collect() assert wr() is None # 'data' does not leak