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
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
|