diff options
Diffstat (limited to 'testing/cffi0/test_function.py')
-rw-r--r-- | testing/cffi0/test_function.py | 520 |
1 files changed, 520 insertions, 0 deletions
diff --git a/testing/cffi0/test_function.py b/testing/cffi0/test_function.py new file mode 100644 index 0000000..ca2353f --- /dev/null +++ b/testing/cffi0/test_function.py @@ -0,0 +1,520 @@ +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 |