summaryrefslogtreecommitdiff
path: root/testing/cffi0/test_function.py
diff options
context:
space:
mode:
Diffstat (limited to 'testing/cffi0/test_function.py')
-rw-r--r--testing/cffi0/test_function.py520
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