• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import py
2from cffi import FFI, CDefError
3import math, os, sys
4import ctypes.util
5from cffi.backend_ctypes import CTypesBackend
6from testing.udir import udir
7from testing.support import FdWriteCapture
8from .backend_tests import needs_dlopen_none
9
10try:
11    from StringIO import StringIO
12except ImportError:
13    from io import StringIO
14
15
16lib_m = 'm'
17if sys.platform == 'win32':
18    #there is a small chance this fails on Mingw via environ $CC
19    import distutils.ccompiler
20    if distutils.ccompiler.get_default_compiler() == 'msvc':
21        lib_m = 'msvcrt'
22
23class TestFunction(object):
24    Backend = CTypesBackend
25
26    def test_sin(self):
27        ffi = FFI(backend=self.Backend())
28        ffi.cdef("""
29            double sin(double x);
30        """)
31        m = ffi.dlopen(lib_m)
32        x = m.sin(1.23)
33        assert x == math.sin(1.23)
34
35    def test_sinf(self):
36        if sys.platform == 'win32':
37            py.test.skip("no sinf found in the Windows stdlib")
38        ffi = FFI(backend=self.Backend())
39        ffi.cdef("""
40            float sinf(float x);
41        """)
42        m = ffi.dlopen(lib_m)
43        x = m.sinf(1.23)
44        assert type(x) is float
45        assert x != math.sin(1.23)    # rounding effects
46        assert abs(x - math.sin(1.23)) < 1E-6
47
48    def test_getenv_no_return_value(self):
49        # check that 'void'-returning functions work too
50        ffi = FFI(backend=self.Backend())
51        ffi.cdef("""
52            void getenv(char *);
53        """)
54        needs_dlopen_none()
55        m = ffi.dlopen(None)
56        x = m.getenv(b"FOO")
57        assert x is None
58
59    def test_dlopen_filename(self):
60        path = ctypes.util.find_library(lib_m)
61        if not path:
62            py.test.skip("%s not found" % lib_m)
63        ffi = FFI(backend=self.Backend())
64        ffi.cdef("""
65            double cos(double x);
66        """)
67        m = ffi.dlopen(path)
68        x = m.cos(1.23)
69        assert x == math.cos(1.23)
70
71        m = ffi.dlopen(os.path.basename(path))
72        x = m.cos(1.23)
73        assert x == math.cos(1.23)
74
75    def test_dlopen_flags(self):
76        ffi = FFI(backend=self.Backend())
77        ffi.cdef("""
78            double cos(double x);
79        """)
80        m = ffi.dlopen(lib_m, ffi.RTLD_LAZY | ffi.RTLD_LOCAL)
81        x = m.cos(1.23)
82        assert x == math.cos(1.23)
83
84    def test_dlopen_constant(self):
85        ffi = FFI(backend=self.Backend())
86        ffi.cdef("""
87            #define FOOBAR 42
88            static const float baz = 42.5;   /* not visible */
89            double sin(double x);
90        """)
91        m = ffi.dlopen(lib_m)
92        assert m.FOOBAR == 42
93        py.test.raises(NotImplementedError, "m.baz")
94
95    def test_tlsalloc(self):
96        if sys.platform != 'win32':
97            py.test.skip("win32 only")
98        if self.Backend is CTypesBackend:
99            py.test.skip("ctypes complains on wrong calling conv")
100        ffi = FFI(backend=self.Backend())
101        ffi.cdef("long TlsAlloc(void); int TlsFree(long);")
102        lib = ffi.dlopen('KERNEL32.DLL')
103        x = lib.TlsAlloc()
104        assert x != 0
105        y = lib.TlsFree(x)
106        assert y != 0
107
108    def test_fputs(self):
109        if not sys.platform.startswith('linux'):
110            py.test.skip("probably no symbol 'stderr' in the lib")
111        ffi = FFI(backend=self.Backend())
112        ffi.cdef("""
113            int fputs(const char *, void *);
114            void *stderr;
115        """)
116        needs_dlopen_none()
117        ffi.C = ffi.dlopen(None)
118        ffi.C.fputs   # fetch before capturing, for easier debugging
119        with FdWriteCapture() as fd:
120            ffi.C.fputs(b"hello\n", ffi.C.stderr)
121            ffi.C.fputs(b"  world\n", ffi.C.stderr)
122        res = fd.getvalue()
123        assert res == b'hello\n  world\n'
124
125    def test_fputs_without_const(self):
126        if not sys.platform.startswith('linux'):
127            py.test.skip("probably no symbol 'stderr' in the lib")
128        ffi = FFI(backend=self.Backend())
129        ffi.cdef("""
130            int fputs(char *, void *);
131            void *stderr;
132        """)
133        needs_dlopen_none()
134        ffi.C = ffi.dlopen(None)
135        ffi.C.fputs   # fetch before capturing, for easier debugging
136        with FdWriteCapture() as fd:
137            ffi.C.fputs(b"hello\n", ffi.C.stderr)
138            ffi.C.fputs(b"  world\n", ffi.C.stderr)
139        res = fd.getvalue()
140        assert res == b'hello\n  world\n'
141
142    def test_vararg(self):
143        if not sys.platform.startswith('linux'):
144            py.test.skip("probably no symbol 'stderr' in the lib")
145        ffi = FFI(backend=self.Backend())
146        ffi.cdef("""
147           int fprintf(void *, const char *format, ...);
148           void *stderr;
149        """)
150        needs_dlopen_none()
151        ffi.C = ffi.dlopen(None)
152        with FdWriteCapture() as fd:
153            ffi.C.fprintf(ffi.C.stderr, b"hello with no arguments\n")
154            ffi.C.fprintf(ffi.C.stderr,
155                          b"hello, %s!\n", ffi.new("char[]", b"world"))
156            ffi.C.fprintf(ffi.C.stderr,
157                          ffi.new("char[]", b"hello, %s!\n"),
158                          ffi.new("char[]", b"world2"))
159            ffi.C.fprintf(ffi.C.stderr,
160                          b"hello int %d long %ld long long %lld\n",
161                          ffi.cast("int", 42),
162                          ffi.cast("long", 84),
163                          ffi.cast("long long", 168))
164            ffi.C.fprintf(ffi.C.stderr, b"hello %p\n", ffi.NULL)
165        res = fd.getvalue()
166        assert res == (b"hello with no arguments\n"
167                       b"hello, world!\n"
168                       b"hello, world2!\n"
169                       b"hello int 42 long 84 long long 168\n"
170                       b"hello (nil)\n")
171
172    def test_must_specify_type_of_vararg(self):
173        ffi = FFI(backend=self.Backend())
174        ffi.cdef("""
175           int printf(const char *format, ...);
176        """)
177        needs_dlopen_none()
178        ffi.C = ffi.dlopen(None)
179        e = py.test.raises(TypeError, ffi.C.printf, b"hello %d\n", 42)
180        assert str(e.value) == ("argument 2 passed in the variadic part "
181                                "needs to be a cdata object (got int)")
182
183    def test_function_has_a_c_type(self):
184        ffi = FFI(backend=self.Backend())
185        ffi.cdef("""
186            int puts(const char *);
187        """)
188        needs_dlopen_none()
189        ffi.C = ffi.dlopen(None)
190        fptr = ffi.C.puts
191        assert ffi.typeof(fptr) == ffi.typeof("int(*)(const char*)")
192        if self.Backend is CTypesBackend:
193            assert repr(fptr).startswith("<cdata 'int puts(char *)' 0x")
194
195    def test_function_pointer(self):
196        ffi = FFI(backend=self.Backend())
197        def cb(charp):
198            assert repr(charp).startswith("<cdata 'char *' 0x")
199            return 42
200        fptr = ffi.callback("int(*)(const char *txt)", cb)
201        assert fptr != ffi.callback("int(*)(const char *)", cb)
202        assert repr(fptr) == "<cdata 'int(*)(char *)' calling %r>" % (cb,)
203        res = fptr(b"Hello")
204        assert res == 42
205        #
206        if not sys.platform.startswith('linux'):
207            py.test.skip("probably no symbol 'stderr' in the lib")
208        ffi.cdef("""
209            int fputs(const char *, void *);
210            void *stderr;
211        """)
212        needs_dlopen_none()
213        ffi.C = ffi.dlopen(None)
214        fptr = ffi.cast("int(*)(const char *txt, void *)", ffi.C.fputs)
215        assert fptr == ffi.C.fputs
216        assert repr(fptr).startswith("<cdata 'int(*)(char *, void *)' 0x")
217        with FdWriteCapture() as fd:
218            fptr(b"world\n", ffi.C.stderr)
219        res = fd.getvalue()
220        assert res == b'world\n'
221
222    def test_callback_returning_void(self):
223        ffi = FFI(backend=self.Backend())
224        for returnvalue in [None, 42]:
225            def cb():
226                return returnvalue
227            fptr = ffi.callback("void(*)(void)", cb)
228            old_stderr = sys.stderr
229            try:
230                sys.stderr = StringIO()
231                returned = fptr()
232                printed = sys.stderr.getvalue()
233            finally:
234                sys.stderr = old_stderr
235            assert returned is None
236            if returnvalue is None:
237                assert printed == ''
238            else:
239                assert "None" in printed
240
241    def test_passing_array(self):
242        ffi = FFI(backend=self.Backend())
243        ffi.cdef("""
244            int strlen(char[]);
245        """)
246        needs_dlopen_none()
247        ffi.C = ffi.dlopen(None)
248        p = ffi.new("char[]", b"hello")
249        res = ffi.C.strlen(p)
250        assert res == 5
251
252    def test_write_variable(self):
253        if not sys.platform.startswith('linux'):
254            py.test.skip("probably no symbol 'stdout' in the lib")
255        ffi = FFI(backend=self.Backend())
256        ffi.cdef("""
257            void *stdout;
258        """)
259        needs_dlopen_none()
260        C = ffi.dlopen(None)
261        pout = C.stdout
262        C.stdout = ffi.NULL
263        assert C.stdout == ffi.NULL
264        C.stdout = pout
265        assert C.stdout == pout
266
267    def test_strchr(self):
268        ffi = FFI(backend=self.Backend())
269        ffi.cdef("""
270            char *strchr(const char *s, int c);
271        """)
272        needs_dlopen_none()
273        ffi.C = ffi.dlopen(None)
274        p = ffi.new("char[]", b"hello world!")
275        q = ffi.C.strchr(p, ord('w'))
276        assert ffi.string(q) == b"world!"
277
278    def test_function_with_struct_argument(self):
279        if sys.platform == 'win32':
280            py.test.skip("no 'inet_ntoa'")
281        if (self.Backend is CTypesBackend and
282            '__pypy__' in sys.builtin_module_names):
283            py.test.skip("ctypes limitation on pypy")
284        ffi = FFI(backend=self.Backend())
285        ffi.cdef("""
286            struct in_addr { unsigned int s_addr; };
287            char *inet_ntoa(struct in_addr in);
288        """)
289        needs_dlopen_none()
290        ffi.C = ffi.dlopen(None)
291        ina = ffi.new("struct in_addr *", [0x04040404])
292        a = ffi.C.inet_ntoa(ina[0])
293        assert ffi.string(a) == b'4.4.4.4'
294
295    def test_function_typedef(self):
296        ffi = FFI(backend=self.Backend())
297        ffi.cdef("""
298            typedef double func_t(double);
299            func_t sin;
300        """)
301        m = ffi.dlopen(lib_m)
302        x = m.sin(1.23)
303        assert x == math.sin(1.23)
304
305    def test_fputs_custom_FILE(self):
306        if self.Backend is CTypesBackend:
307            py.test.skip("FILE not supported with the ctypes backend")
308        filename = str(udir.join('fputs_custom_FILE'))
309        ffi = FFI(backend=self.Backend())
310        ffi.cdef("int fputs(const char *, FILE *);")
311        needs_dlopen_none()
312        C = ffi.dlopen(None)
313        with open(filename, 'wb') as f:
314            f.write(b'[')
315            C.fputs(b"hello from custom file", f)
316            f.write(b'][')
317            C.fputs(b"some more output", f)
318            f.write(b']')
319        with open(filename, 'rb') as f:
320            res = f.read()
321        assert res == b'[hello from custom file][some more output]'
322
323    def test_constants_on_lib(self):
324        ffi = FFI(backend=self.Backend())
325        ffi.cdef("""enum foo_e { AA, BB, CC=5, DD };
326                    typedef enum { EE=-5, FF } some_enum_t;""")
327        needs_dlopen_none()
328        lib = ffi.dlopen(None)
329        assert lib.AA == 0
330        assert lib.BB == 1
331        assert lib.CC == 5
332        assert lib.DD == 6
333        assert lib.EE == -5
334        assert lib.FF == -4
335
336    def test_void_star_accepts_string(self):
337        ffi = FFI(backend=self.Backend())
338        ffi.cdef("""int strlen(const void *);""")
339        needs_dlopen_none()
340        lib = ffi.dlopen(None)
341        res = lib.strlen(b"hello")
342        assert res == 5
343
344    def test_signed_char_star_accepts_string(self):
345        if self.Backend is CTypesBackend:
346            py.test.skip("not supported by the ctypes backend")
347        ffi = FFI(backend=self.Backend())
348        ffi.cdef("""int strlen(signed char *);""")
349        needs_dlopen_none()
350        lib = ffi.dlopen(None)
351        res = lib.strlen(b"hello")
352        assert res == 5
353
354    def test_unsigned_char_star_accepts_string(self):
355        if self.Backend is CTypesBackend:
356            py.test.skip("not supported by the ctypes backend")
357        ffi = FFI(backend=self.Backend())
358        ffi.cdef("""int strlen(unsigned char *);""")
359        needs_dlopen_none()
360        lib = ffi.dlopen(None)
361        res = lib.strlen(b"hello")
362        assert res == 5
363
364    def test_missing_function(self):
365        ffi = FFI(backend=self.Backend())
366        ffi.cdef("""
367            int nonexistent();
368        """)
369        m = ffi.dlopen(lib_m)
370        assert not hasattr(m, 'nonexistent')
371
372    def test_wraps_from_stdlib(self):
373        import functools
374        ffi = FFI(backend=self.Backend())
375        ffi.cdef("""
376            double sin(double x);
377        """)
378        def my_decorator(f):
379            @functools.wraps(f)
380            def wrapper(*args):
381                return f(*args) + 100
382            return wrapper
383        m = ffi.dlopen(lib_m)
384        sin100 = my_decorator(m.sin)
385        x = sin100(1.23)
386        assert x == math.sin(1.23) + 100
387
388    def test_free_callback_cycle(self):
389        if self.Backend is CTypesBackend:
390            py.test.skip("seems to fail with the ctypes backend on windows")
391        import weakref
392        def make_callback(data):
393            container = [data]
394            callback = ffi.callback('int()', lambda: len(container))
395            container.append(callback)
396            # Ref cycle: callback -> lambda (closure) -> container -> callback
397            return callback
398
399        class Data(object):
400            pass
401        ffi = FFI(backend=self.Backend())
402        data = Data()
403        callback = make_callback(data)
404        wr = weakref.ref(data)
405        del callback, data
406        for i in range(3):
407            if wr() is not None:
408                import gc; gc.collect()
409        assert wr() is None    # 'data' does not leak
410
411    def test_windows_stdcall(self):
412        if sys.platform != 'win32':
413            py.test.skip("Windows-only test")
414        if self.Backend is CTypesBackend:
415            py.test.skip("not with the ctypes backend")
416        ffi = FFI(backend=self.Backend())
417        ffi.cdef("""
418            BOOL QueryPerformanceFrequency(LONGLONG *lpFrequency);
419        """)
420        m = ffi.dlopen("Kernel32.dll")
421        p_freq = ffi.new("LONGLONG *")
422        res = m.QueryPerformanceFrequency(p_freq)
423        assert res != 0
424        assert p_freq[0] != 0
425
426    def test_explicit_cdecl_stdcall(self):
427        if sys.platform != 'win32':
428            py.test.skip("Windows-only test")
429        if self.Backend is CTypesBackend:
430            py.test.skip("not with the ctypes backend")
431        win64 = (sys.maxsize > 2**32)
432        #
433        ffi = FFI(backend=self.Backend())
434        ffi.cdef("""
435            BOOL QueryPerformanceFrequency(LONGLONG *lpFrequency);
436        """)
437        m = ffi.dlopen("Kernel32.dll")
438        tp = ffi.typeof(m.QueryPerformanceFrequency)
439        assert str(tp) == "<ctype 'int(*)(long long *)'>"
440        #
441        ffi = FFI(backend=self.Backend())
442        ffi.cdef("""
443            BOOL __cdecl QueryPerformanceFrequency(LONGLONG *lpFrequency);
444        """)
445        m = ffi.dlopen("Kernel32.dll")
446        tpc = ffi.typeof(m.QueryPerformanceFrequency)
447        assert tpc is tp
448        #
449        ffi = FFI(backend=self.Backend())
450        ffi.cdef("""
451            BOOL WINAPI QueryPerformanceFrequency(LONGLONG *lpFrequency);
452        """)
453        m = ffi.dlopen("Kernel32.dll")
454        tps = ffi.typeof(m.QueryPerformanceFrequency)
455        if win64:
456            assert tps is tpc
457        else:
458            assert tps is not tpc
459            assert str(tps) == "<ctype 'int(__stdcall *)(long long *)'>"
460        #
461        ffi = FFI(backend=self.Backend())
462        ffi.cdef("typedef int (__cdecl *fnc_t)(int);")
463        ffi.cdef("typedef int (__stdcall *fns_t)(int);")
464        tpc = ffi.typeof("fnc_t")
465        tps = ffi.typeof("fns_t")
466        assert str(tpc) == "<ctype 'int(*)(int)'>"
467        if win64:
468            assert tps is tpc
469        else:
470            assert str(tps) == "<ctype 'int(__stdcall *)(int)'>"
471        #
472        fnc = ffi.cast("fnc_t", 0)
473        fns = ffi.cast("fns_t", 0)
474        ffi.new("fnc_t[]", [fnc])
475        if not win64:
476            py.test.raises(TypeError, ffi.new, "fnc_t[]", [fns])
477            py.test.raises(TypeError, ffi.new, "fns_t[]", [fnc])
478        ffi.new("fns_t[]", [fns])
479
480    def test_stdcall_only_on_windows(self):
481        ffi = FFI(backend=self.Backend())
482        ffi.cdef("double __stdcall sin(double x);")     # stdcall ignored
483        m = ffi.dlopen(lib_m)
484        if (sys.platform == 'win32' and sys.maxsize < 2**32 and
485                self.Backend is not CTypesBackend):
486            assert "double(__stdcall *)(double)" in str(ffi.typeof(m.sin))
487        else:
488            assert "double(*)(double)" in str(ffi.typeof(m.sin))
489        x = m.sin(1.23)
490        assert x == math.sin(1.23)
491
492    def test_dir_on_dlopen_lib(self):
493        ffi = FFI(backend=self.Backend())
494        ffi.cdef("""
495            typedef enum { MYE1, MYE2 } myenum_t;
496            double myfunc(double);
497            double myvar;
498            const double myconst;
499            #define MYFOO 42
500        """)
501        m = ffi.dlopen(lib_m)
502        assert dir(m) == ['MYE1', 'MYE2', 'MYFOO', 'myconst', 'myfunc', 'myvar']
503
504    def test_dlclose(self):
505        if self.Backend is CTypesBackend:
506            py.test.skip("not with the ctypes backend")
507        ffi = FFI(backend=self.Backend())
508        ffi.cdef("int foobar(void); int foobaz;")
509        lib = ffi.dlopen(lib_m)
510        ffi.dlclose(lib)
511        e = py.test.raises(ValueError, getattr, lib, 'foobar')
512        assert str(e.value).startswith("library '")
513        assert str(e.value).endswith("' has already been closed")
514        e = py.test.raises(ValueError, getattr, lib, 'foobaz')
515        assert str(e.value).startswith("library '")
516        assert str(e.value).endswith("' has already been closed")
517        e = py.test.raises(ValueError, setattr, lib, 'foobaz', 42)
518        assert str(e.value).startswith("library '")
519        assert str(e.value).endswith("' has already been closed")
520        ffi.dlclose(lib)    # does not raise
521