You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

521 lines
18 KiB

import py
from cffi import FFI, CDefError
import math, os, sys
import ctypes.util
from cffi.backend_ctypes import CTypesBackend
from testing.udir import udir
from testing.support import FdWriteCapture
from .backend_tests import needs_dlopen_none
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
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_getenv_no_return_value(self):
# check that 'void'-returning functions work too
ffi = FFI(backend=self.Backend())
ffi.cdef("""
void getenv(char *);
""")
needs_dlopen_none()
m = ffi.dlopen(None)
x = m.getenv(b"FOO")
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_dlopen_constant(self):
ffi = FFI(backend=self.Backend())
ffi.cdef("""
#define FOOBAR 42
static const float baz = 42.5; /* not visible */
double sin(double x);
""")
m = ffi.dlopen(lib_m)
assert m.FOOBAR == 42
py.test.raises(NotImplementedError, "m.baz")
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;
""")
needs_dlopen_none()
ffi.C = ffi.dlopen(None)
ffi.C.fputs # fetch before capturing, for easier debugging
with FdWriteCapture() 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;
""")
needs_dlopen_none()
ffi.C = ffi.dlopen(None)
ffi.C.fputs # fetch before capturing, for easier debugging
with FdWriteCapture() 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;
""")
needs_dlopen_none()
ffi.C = ffi.dlopen(None)
with FdWriteCapture() 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, ...);
""")
needs_dlopen_none()
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 *);
""")
needs_dlopen_none()
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("<cdata 'int puts(char *)' 0x")
def test_function_pointer(self):
ffi = FFI(backend=self.Backend())
def cb(charp):
assert repr(charp).startswith("<cdata 'char *' 0x")
return 42
fptr = ffi.callback("int(*)(const char *txt)", cb)
assert fptr != ffi.callback("int(*)(const char *)", cb)
assert repr(fptr) == "<cdata 'int(*)(char *)' calling %r>" % (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;
""")
needs_dlopen_none()
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("<cdata 'int(*)(char *, void *)' 0x")
with FdWriteCapture() as fd:
fptr(b"world\n", ffi.C.stderr)
res = fd.getvalue()
assert res == b'world\n'
def test_callback_returning_void(self):
ffi = FFI(backend=self.Backend())
for returnvalue in [None, 42]:
def cb():
return returnvalue
fptr = ffi.callback("void(*)(void)", cb)
old_stderr = sys.stderr
try:
sys.stderr = StringIO()
returned = fptr()
printed = sys.stderr.getvalue()
finally:
sys.stderr = old_stderr
assert returned is None
if returnvalue is None:
assert printed == ''
else:
assert "None" in printed
def test_passing_array(self):
ffi = FFI(backend=self.Backend())
ffi.cdef("""
int strlen(char[]);
""")
needs_dlopen_none()
ffi.C = ffi.dlopen(None)
p = ffi.new("char[]", b"hello")
res = ffi.C.strlen(p)
assert res == 5
def test_write_variable(self):
if not sys.platform.startswith('linux'):
py.test.skip("probably no symbol 'stdout' in the lib")
ffi = FFI(backend=self.Backend())
ffi.cdef("""
void *stdout;
""")
needs_dlopen_none()
C = ffi.dlopen(None)
pout = C.stdout
C.stdout = ffi.NULL
assert C.stdout == ffi.NULL
C.stdout = pout
assert C.stdout == pout
def test_strchr(self):
ffi = FFI(backend=self.Backend())
ffi.cdef("""
char *strchr(const char *s, int c);
""")
needs_dlopen_none()
ffi.C = ffi.dlopen(None)
p = ffi.new("char[]", b"hello world!")
q = ffi.C.strchr(p, ord('w'))
assert ffi.string(q) == b"world!"
def test_function_with_struct_argument(self):
if sys.platform == 'win32':
py.test.skip("no 'inet_ntoa'")
if (self.Backend is CTypesBackend and
'__pypy__' in sys.builtin_module_names):
py.test.skip("ctypes limitation on pypy")
ffi = FFI(backend=self.Backend())
ffi.cdef("""
struct in_addr { unsigned int s_addr; };
char *inet_ntoa(struct in_addr in);
""")
needs_dlopen_none()
ffi.C = ffi.dlopen(None)
ina = ffi.new("struct in_addr *", [0x04040404])
a = ffi.C.inet_ntoa(ina[0])
assert ffi.string(a) == b'4.4.4.4'
def test_function_typedef(self):
ffi = FFI(backend=self.Backend())
ffi.cdef("""
typedef double func_t(double);
func_t sin;
""")
m = ffi.dlopen(lib_m)
x = m.sin(1.23)
assert x == math.sin(1.23)
def test_fputs_custom_FILE(self):
if self.Backend is CTypesBackend:
py.test.skip("FILE not supported with the ctypes backend")
filename = str(udir.join('fputs_custom_FILE'))
ffi = FFI(backend=self.Backend())
ffi.cdef("int fputs(const char *, FILE *);")
needs_dlopen_none()
C = ffi.dlopen(None)
with open(filename, 'wb') as f:
f.write(b'[')
C.fputs(b"hello from custom file", f)
f.write(b'][')
C.fputs(b"some more output", f)
f.write(b']')
with open(filename, 'rb') as f:
res = f.read()
assert res == b'[hello from custom file][some more output]'
def test_constants_on_lib(self):
ffi = FFI(backend=self.Backend())
ffi.cdef("""enum foo_e { AA, BB, CC=5, DD };
typedef enum { EE=-5, FF } some_enum_t;""")
needs_dlopen_none()
lib = ffi.dlopen(None)
assert lib.AA == 0
assert lib.BB == 1
assert lib.CC == 5
assert lib.DD == 6
assert lib.EE == -5
assert lib.FF == -4
def test_void_star_accepts_string(self):
ffi = FFI(backend=self.Backend())
ffi.cdef("""int strlen(const void *);""")
needs_dlopen_none()
lib = ffi.dlopen(None)
res = lib.strlen(b"hello")
assert res == 5
def test_signed_char_star_accepts_string(self):
if self.Backend is CTypesBackend:
py.test.skip("not supported by the ctypes backend")
ffi = FFI(backend=self.Backend())
ffi.cdef("""int strlen(signed char *);""")
needs_dlopen_none()
lib = ffi.dlopen(None)
res = lib.strlen(b"hello")
assert res == 5
def test_unsigned_char_star_accepts_string(self):
if self.Backend is CTypesBackend:
py.test.skip("not supported by the ctypes backend")
ffi = FFI(backend=self.Backend())
ffi.cdef("""int strlen(unsigned char *);""")
needs_dlopen_none()
lib = ffi.dlopen(None)
res = lib.strlen(b"hello")
assert res == 5
def test_missing_function(self):
ffi = FFI(backend=self.Backend())
ffi.cdef("""
int nonexistent();
""")
m = ffi.dlopen(lib_m)
assert not hasattr(m, 'nonexistent')
def test_wraps_from_stdlib(self):
import functools
ffi = FFI(backend=self.Backend())
ffi.cdef("""
double sin(double x);
""")
def my_decorator(f):
@functools.wraps(f)
def wrapper(*args):
return f(*args) + 100
return wrapper
m = ffi.dlopen(lib_m)
sin100 = my_decorator(m.sin)
x = sin100(1.23)
assert x == math.sin(1.23) + 100
def test_free_callback_cycle(self):
if self.Backend is CTypesBackend:
py.test.skip("seems to fail with the ctypes backend on windows")
import weakref
def make_callback(data):
container = [data]
callback = ffi.callback('int()', lambda: len(container))
container.append(callback)
# Ref cycle: callback -> 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
def test_windows_stdcall(self):
if sys.platform != 'win32':
py.test.skip("Windows-only test")
if self.Backend is CTypesBackend:
py.test.skip("not with the ctypes backend")
ffi = FFI(backend=self.Backend())
ffi.cdef("""
BOOL QueryPerformanceFrequency(LONGLONG *lpFrequency);
""")
m = ffi.dlopen("Kernel32.dll")
p_freq = ffi.new("LONGLONG *")
res = m.QueryPerformanceFrequency(p_freq)
assert res != 0
assert p_freq[0] != 0
def test_explicit_cdecl_stdcall(self):
if sys.platform != 'win32':
py.test.skip("Windows-only test")
if self.Backend is CTypesBackend:
py.test.skip("not with the ctypes backend")
win64 = (sys.maxsize > 2**32)
#
ffi = FFI(backend=self.Backend())
ffi.cdef("""
BOOL QueryPerformanceFrequency(LONGLONG *lpFrequency);
""")
m = ffi.dlopen("Kernel32.dll")
tp = ffi.typeof(m.QueryPerformanceFrequency)
assert str(tp) == "<ctype 'int(*)(long long *)'>"
#
ffi = FFI(backend=self.Backend())
ffi.cdef("""
BOOL __cdecl QueryPerformanceFrequency(LONGLONG *lpFrequency);
""")
m = ffi.dlopen("Kernel32.dll")
tpc = ffi.typeof(m.QueryPerformanceFrequency)
assert tpc is tp
#
ffi = FFI(backend=self.Backend())
ffi.cdef("""
BOOL WINAPI QueryPerformanceFrequency(LONGLONG *lpFrequency);
""")
m = ffi.dlopen("Kernel32.dll")
tps = ffi.typeof(m.QueryPerformanceFrequency)
if win64:
assert tps is tpc
else:
assert tps is not tpc
assert str(tps) == "<ctype 'int(__stdcall *)(long long *)'>"
#
ffi = FFI(backend=self.Backend())
ffi.cdef("typedef int (__cdecl *fnc_t)(int);")
ffi.cdef("typedef int (__stdcall *fns_t)(int);")
tpc = ffi.typeof("fnc_t")
tps = ffi.typeof("fns_t")
assert str(tpc) == "<ctype 'int(*)(int)'>"
if win64:
assert tps is tpc
else:
assert str(tps) == "<ctype 'int(__stdcall *)(int)'>"
#
fnc = ffi.cast("fnc_t", 0)
fns = ffi.cast("fns_t", 0)
ffi.new("fnc_t[]", [fnc])
if not win64:
py.test.raises(TypeError, ffi.new, "fnc_t[]", [fns])
py.test.raises(TypeError, ffi.new, "fns_t[]", [fnc])
ffi.new("fns_t[]", [fns])
def test_stdcall_only_on_windows(self):
ffi = FFI(backend=self.Backend())
ffi.cdef("double __stdcall sin(double x);") # stdcall ignored
m = ffi.dlopen(lib_m)
if (sys.platform == 'win32' and sys.maxsize < 2**32 and
self.Backend is not CTypesBackend):
assert "double(__stdcall *)(double)" in str(ffi.typeof(m.sin))
else:
assert "double(*)(double)" in str(ffi.typeof(m.sin))
x = m.sin(1.23)
assert x == math.sin(1.23)
def test_dir_on_dlopen_lib(self):
ffi = FFI(backend=self.Backend())
ffi.cdef("""
typedef enum { MYE1, MYE2 } myenum_t;
double myfunc(double);
double myvar;
const double myconst;
#define MYFOO 42
""")
m = ffi.dlopen(lib_m)
assert dir(m) == ['MYE1', 'MYE2', 'MYFOO', 'myconst', 'myfunc', 'myvar']
def test_dlclose(self):
if self.Backend is CTypesBackend:
py.test.skip("not with the ctypes backend")
ffi = FFI(backend=self.Backend())
ffi.cdef("int foobar(void); int foobaz;")
lib = ffi.dlopen(lib_m)
ffi.dlclose(lib)
e = py.test.raises(ValueError, getattr, lib, 'foobar')
assert str(e.value).startswith("library '")
assert str(e.value).endswith("' has already been closed")
e = py.test.raises(ValueError, getattr, lib, 'foobaz')
assert str(e.value).startswith("library '")
assert str(e.value).endswith("' has already been closed")
e = py.test.raises(ValueError, setattr, lib, 'foobaz', 42)
assert str(e.value).startswith("library '")
assert str(e.value).endswith("' has already been closed")
ffi.dlclose(lib) # does not raise