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