diff options
author | Lucia Li <luciali@google.com> | 2021-11-12 05:47:31 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2021-11-12 05:47:31 +0000 |
commit | 046d35db06dec26c759b92b7d8de38a979d84c0b (patch) | |
tree | 64f8c90740adbe25c30379d6567fd2acafcbd2dd /testing | |
parent | 11106c32f6af49935387d83aed1b1d60a77f21b4 (diff) | |
parent | 2eac055653de4ed6b019e0eb3d5ef997ded8663e (diff) | |
download | cffi-046d35db06dec26c759b92b7d8de38a979d84c0b.tar.gz |
Upgrade cffi from 1.12.2 to 1.15.0 am: 6aa63b0826 am: 1944553588 am: 65215427f5 am: 2eac055653t_frc_odp_330442040t_frc_odp_330442000t_frc_con_330443020t_frc_cbr_330443000t_frc_ase_330444010t_frc_art_330443060t_frc_adb_330444000android-13.0.0_r83android-13.0.0_r82android-13.0.0_r81android-13.0.0_r80android-13.0.0_r79android-13.0.0_r78android-13.0.0_r77android-13.0.0_r76android-13.0.0_r75android-13.0.0_r74android-13.0.0_r73android-13.0.0_r72android-13.0.0_r71android-13.0.0_r70android-13.0.0_r69android-13.0.0_r68android-13.0.0_r67android-13.0.0_r66android-13.0.0_r65android-13.0.0_r64android-13.0.0_r63android-13.0.0_r62android-13.0.0_r61android-13.0.0_r60android-13.0.0_r59android-13.0.0_r58android-13.0.0_r57android-13.0.0_r56android-13.0.0_r55android-13.0.0_r54android-13.0.0_r53android-13.0.0_r52android-13.0.0_r51android-13.0.0_r50android-13.0.0_r49android-13.0.0_r48android-13.0.0_r47android-13.0.0_r46android-13.0.0_r45android-13.0.0_r44android-13.0.0_r43android-13.0.0_r42android-13.0.0_r41android-13.0.0_r40android-13.0.0_r39android-13.0.0_r38android-13.0.0_r37android-13.0.0_r36android-13.0.0_r35android-13.0.0_r34android-13.0.0_r33android-13.0.0_r32android-13.0.0_r30android-13.0.0_r29android-13.0.0_r28android-13.0.0_r27android-13.0.0_r24android-13.0.0_r23android-13.0.0_r22android-13.0.0_r21android-13.0.0_r20android-13.0.0_r19android-13.0.0_r18android-13.0.0_r17android-13.0.0_r16aml_uwb_331910010aml_uwb_331820070aml_uwb_331613010aml_uwb_331611010aml_uwb_331410010aml_uwb_331310030aml_uwb_331115000aml_uwb_331015040aml_uwb_330810010aml_tz4_332714070aml_tz4_332714050aml_tz4_332714010aml_tz4_331910000aml_tz4_331314030aml_tz4_331314020aml_tz4_331314010aml_tz4_331012050aml_tz4_331012040aml_tz4_331012000aml_go_wif_330911000aml_go_uwb_330912000aml_go_tz4_330912000aml_go_tet_330914010aml_go_swc_330913000aml_go_sta_330911000aml_go_sdk_330810000aml_go_sch_330911000aml_go_res_330912000aml_go_per_330912000aml_go_odp_330913000aml_go_odp_330912000aml_go_neu_330912000aml_go_net_330913000aml_go_mpr_330912000aml_go_ase_330913000aml_go_ads_330915100aml_go_ads_330915000aml_go_ads_330913000aml_go_adb_330913000aml_ase_331311020aml_ase_331112000aml_ase_331011020aml_ads_331920180aml_ads_331814200aml_ads_331710270aml_ads_331611190aml_ads_331511020aml_ads_331418080aml_ads_331131000android13-qpr3-s9-releaseandroid13-qpr3-s8-releaseandroid13-qpr3-s7-releaseandroid13-qpr3-s6-releaseandroid13-qpr3-s5-releaseandroid13-qpr3-s4-releaseandroid13-qpr3-s3-releaseandroid13-qpr3-s2-releaseandroid13-qpr3-s14-releaseandroid13-qpr3-s13-releaseandroid13-qpr3-s12-releaseandroid13-qpr3-s11-releaseandroid13-qpr3-s10-releaseandroid13-qpr3-s1-releaseandroid13-qpr3-releaseandroid13-qpr3-c-s8-releaseandroid13-qpr3-c-s7-releaseandroid13-qpr3-c-s6-releaseandroid13-qpr3-c-s5-releaseandroid13-qpr3-c-s4-releaseandroid13-qpr3-c-s3-releaseandroid13-qpr3-c-s2-releaseandroid13-qpr3-c-s12-releaseandroid13-qpr3-c-s11-releaseandroid13-qpr3-c-s10-releaseandroid13-qpr3-c-s1-releaseandroid13-qpr2-s9-releaseandroid13-qpr2-s8-releaseandroid13-qpr2-s7-releaseandroid13-qpr2-s6-releaseandroid13-qpr2-s5-releaseandroid13-qpr2-s3-releaseandroid13-qpr2-s2-releaseandroid13-qpr2-s12-releaseandroid13-qpr2-s11-releaseandroid13-qpr2-s10-releaseandroid13-qpr2-s1-releaseandroid13-qpr2-releaseandroid13-qpr2-b-s1-releaseandroid13-qpr1-s8-releaseandroid13-qpr1-s7-releaseandroid13-qpr1-s6-releaseandroid13-qpr1-s5-releaseandroid13-qpr1-s4-releaseandroid13-qpr1-s3-releaseandroid13-qpr1-s2-releaseandroid13-qpr1-s1-releaseandroid13-qpr1-releaseandroid13-mainline-uwb-releaseandroid13-mainline-tzdata4-releaseandroid13-mainline-go-wifi-releaseandroid13-mainline-go-uwb-releaseandroid13-mainline-go-tzdata4-releaseandroid13-mainline-go-tethering-releaseandroid13-mainline-go-sdkext-releaseandroid13-mainline-go-scheduling-releaseandroid13-mainline-go-resolv-releaseandroid13-mainline-go-permission-releaseandroid13-mainline-go-os-statsd-releaseandroid13-mainline-go-odp-releaseandroid13-mainline-go-neuralnetworks-releaseandroid13-mainline-go-networking-releaseandroid13-mainline-go-mediaprovider-releaseandroid13-mainline-go-media-swcodec-releaseandroid13-mainline-go-appsearch-releaseandroid13-mainline-go-adservices-releaseandroid13-mainline-go-adbd-releaseandroid13-mainline-appsearch-releaseandroid13-mainline-adservices-releaseandroid13-frc-odp-releaseandroid13-frc-conscrypt-releaseandroid13-frc-cellbroadcast-releaseandroid13-frc-art-releaseandroid13-frc-adbd-releaseandroid13-devandroid13-d4-s2-releaseandroid13-d4-s1-releaseandroid13-d4-releaseandroid13-d3-s1-releaseandroid13-d2-releaseaml_tz4_332714010
Original change: https://googleplex-android-review.googlesource.com/c/platform/external/python/cffi/+/16211499
Change-Id: I02a81511bae603d9a355c8de655a1317e3535034
Diffstat (limited to 'testing')
25 files changed, 1334 insertions, 318 deletions
diff --git a/testing/cffi0/backend_tests.py b/testing/cffi0/backend_tests.py index 13a4c78..ab013a1 100644 --- a/testing/cffi0/backend_tests.py +++ b/testing/cffi0/backend_tests.py @@ -1,6 +1,7 @@ import py +import pytest import platform -import sys, ctypes +import sys, ctypes, ctypes.util from cffi import FFI, CDefError, FFIError, VerificationMissing from testing.support import * @@ -11,8 +12,8 @@ SIZE_OF_PTR = ctypes.sizeof(ctypes.c_void_p) SIZE_OF_WCHAR = ctypes.sizeof(ctypes.c_wchar) def needs_dlopen_none(): - if sys.platform == 'win32' and sys.version_info >= (3,): - py.test.skip("dlopen(None) cannot work on Windows for Python 3") + if sys.platform == 'win32' and not ctypes.util.find_library('c'): + py.test.skip("dlopen(None) cannot work on Windows with this runtime") class BackendTests: @@ -112,10 +113,14 @@ class BackendTests: p[9] = 43 assert p[0] == 42 assert p[9] == 43 - py.test.raises(IndexError, "p[10]") - py.test.raises(IndexError, "p[10] = 44") - py.test.raises(IndexError, "p[-1]") - py.test.raises(IndexError, "p[-1] = 44") + with pytest.raises(IndexError): + p[10] + with pytest.raises(IndexError): + p[10] = 44 + with pytest.raises(IndexError): + p[-1] + with pytest.raises(IndexError): + p[-1] = 44 def test_new_array_args(self): ffi = FFI(backend=self.Backend()) @@ -140,18 +145,21 @@ class BackendTests: ffi = FFI(backend=self.Backend()) p = ffi.new("int[]", 10) # a single integer is the length assert p[9] == 0 - py.test.raises(IndexError, "p[10]") + with pytest.raises(IndexError): + p[10] # py.test.raises(TypeError, ffi.new, "int[]") # p = ffi.new("int[]", [-6, -7]) # a list is all the items, like C assert p[0] == -6 assert p[1] == -7 - py.test.raises(IndexError, "p[2]") + with pytest.raises(IndexError): + p[2] assert repr(p) == "<cdata 'int[]' owning %d bytes>" % (2*SIZE_OF_INT) # p = ffi.new("int[]", 0) - py.test.raises(IndexError, "p[0]") + with pytest.raises(IndexError): + p[0] py.test.raises(ValueError, ffi.new, "int[]", -1) assert repr(p) == "<cdata 'int[]' owning 0 bytes>" @@ -259,7 +267,8 @@ class BackendTests: p[2][3] = 33 assert p[0][0] == 10 assert p[2][3] == 33 - py.test.raises(IndexError, "p[1][-1]") + with pytest.raises(IndexError): + p[1][-1] def test_constructor_array_of_array(self): ffi = FFI(backend=self.Backend()) @@ -386,7 +395,8 @@ class BackendTests: n = ffi.new("int*", 99) p = ffi.new("int*[]", [n]) assert p[0][0] == 99 - py.test.raises(TypeError, "p[0] = None") + with pytest.raises(TypeError): + p[0] = None p[0] = ffi.NULL assert p[0] == ffi.NULL @@ -422,13 +432,15 @@ class BackendTests: assert s.a == s.b == s.c == 0 s.b = -23 assert s.b == -23 - py.test.raises(OverflowError, "s.b = 32768") + with pytest.raises(OverflowError): + s.b = 32768 # s = ffi.new("struct foo*", [-2, -3]) assert s.a == -2 assert s.b == -3 assert s.c == 0 - py.test.raises((AttributeError, TypeError), "del s.a") + with pytest.raises((AttributeError, TypeError)): + del s.a assert repr(s) == "<cdata 'struct foo *' owning %d bytes>" % ( SIZE_OF_INT + 2 * SIZE_OF_SHORT) # @@ -450,8 +462,10 @@ class BackendTests: assert s[0].a == s[0].b == s[0].c == 0 s[0].b = -23 assert s[0].b == s.b == -23 - py.test.raises(OverflowError, "s[0].b = -32769") - py.test.raises(IndexError, "s[1]") + with pytest.raises(OverflowError): + s[0].b = -32769 + with pytest.raises(IndexError): + s[1] def test_struct_opaque(self): ffi = FFI(backend=self.Backend()) @@ -511,11 +525,13 @@ class BackendTests: u.b = -23 assert u.b == -23 assert u.a != 0 - py.test.raises(OverflowError, "u.b = 32768") + with pytest.raises(OverflowError): + u.b = 32768 # u = ffi.new("union foo*", [-2]) assert u.a == -2 - py.test.raises((AttributeError, TypeError), "del u.a") + with pytest.raises((AttributeError, TypeError)): + del u.a assert repr(u) == "<cdata 'union foo *' owning %d bytes>" % SIZE_OF_INT def test_union_opaque(self): @@ -591,7 +607,8 @@ class BackendTests: p[3] = b'\x00' assert ffi.string(p) == b"hel" assert ffi.string(p, 2) == b"he" - py.test.raises(IndexError, "p[7] = b'X'") + with pytest.raises(IndexError): + p[7] = b'X' # a = ffi.new("char[]", b"hello\x00world") assert len(a) == 12 @@ -615,7 +632,8 @@ class BackendTests: p[3] = u+'\x00' assert ffi.string(p) == u+"hel" assert ffi.string(p, 123) == u+"hel" - py.test.raises(IndexError, "p[7] = u+'X'") + with pytest.raises(IndexError): + p[7] = u+'X' # a = ffi.new("wchar_t[]", u+"hello\x00world") assert len(a) == 12 @@ -633,7 +651,8 @@ class BackendTests: s = ffi.new("struct foo*", [t]) assert type(s.name) not in (bytes, str, unicode) assert ffi.string(s.name) == b"testing" - py.test.raises(TypeError, "s.name = None") + with pytest.raises(TypeError): + s.name = None s.name = ffi.NULL assert s.name == ffi.NULL @@ -657,18 +676,21 @@ class BackendTests: a = ffi.new("int[]", [10, 11, 12]) p = ffi.new("void **", a) vp = p[0] - py.test.raises(TypeError, "vp[0]") + with pytest.raises(TypeError): + vp[0] py.test.raises(TypeError, ffi.new, "short **", a) # ffi.cdef("struct foo { void *p; int *q; short *r; };") s = ffi.new("struct foo *") s.p = a # works s.q = a # works - py.test.raises(TypeError, "s.r = a") # fails + with pytest.raises(TypeError): + s.r = a # fails b = ffi.cast("int *", a) s.p = b # works s.q = b # works - py.test.raises(TypeError, "s.r = b") # fails + with pytest.raises(TypeError): + s.r = b # fails def test_functionptr_simple(self): ffi = FFI(backend=self.Backend()) @@ -687,7 +709,8 @@ class BackendTests: q = ffi.new("int(**)(int)", p) assert repr(q) == "<cdata 'int(* *)(int)' owning %d bytes>" % ( SIZE_OF_PTR) - py.test.raises(TypeError, "q(43)") + with pytest.raises(TypeError): + q(43) res = q[0](43) assert res == 44 q = ffi.cast("int(*)(int)", p) @@ -912,10 +935,14 @@ class BackendTests: assert s.e == 4294967295 assert s[0].e == 4294967295 s.e = s.e - py.test.raises(TypeError, "s.e = 'B'") - py.test.raises(TypeError, "s.e = '2'") - py.test.raises(TypeError, "s.e = '#2'") - py.test.raises(TypeError, "s.e = '#7'") + with pytest.raises(TypeError): + s.e = 'B' + with pytest.raises(TypeError): + s.e = '2' + with pytest.raises(TypeError): + s.e = '#2' + with pytest.raises(TypeError): + s.e = '#7' def test_enum_non_contiguous(self): ffi = FFI(backend=self.Backend()) @@ -950,11 +977,14 @@ class BackendTests: ffi = FFI(backend=self.Backend()) ffi.cdef("struct foo { int a, b; };") s = ffi.new("struct foo[1]") - py.test.raises(AttributeError, 's.b') - py.test.raises(AttributeError, 's.b = 412') + with pytest.raises(AttributeError): + s.b + with pytest.raises(AttributeError): + s.b = 412 s[0].b = 412 assert s[0].b == 412 - py.test.raises(IndexError, 's[1]') + with pytest.raises(IndexError): + s[1] def test_pointer_to_array(self): ffi = FFI(backend=self.Backend()) @@ -1011,17 +1041,23 @@ class BackendTests: assert ffi.sizeof("struct foo") == 8 s = ffi.new("struct foo *") s.a = 511 - py.test.raises(OverflowError, "s.a = 512") - py.test.raises(OverflowError, "s[0].a = 512") + with pytest.raises(OverflowError): + s.a = 512 + with pytest.raises(OverflowError): + s[0].a = 512 assert s.a == 511 s.a = -512 - py.test.raises(OverflowError, "s.a = -513") - py.test.raises(OverflowError, "s[0].a = -513") + with pytest.raises(OverflowError): + s.a = -513 + with pytest.raises(OverflowError): + s[0].a = -513 assert s.a == -512 s.c = 3 assert s.c == 3 - py.test.raises(OverflowError, "s.c = 4") - py.test.raises(OverflowError, "s[0].c = 4") + with pytest.raises(OverflowError): + s.c = 4 + with pytest.raises(OverflowError): + s[0].c = 4 s.c = -4 assert s.c == -4 @@ -1205,7 +1241,7 @@ class BackendTests: py.test.skip(str(e)) f.write(ffi.buffer(a, 1000 * ffi.sizeof("int"))) f.seek(0) - assert f.read() == array.array('i', range(1000)).tostring() + assert f.read() == arraytostring(array.array('i', range(1000))) f.seek(0) b = ffi.new("int[]", 1005) f.readinto(ffi.buffer(b, 1000 * ffi.sizeof("int"))) @@ -1224,7 +1260,7 @@ class BackendTests: py.test.skip(str(e)) f.write(ffi.buffer(a, 1000 * ffi.sizeof("int"))) f.seek(0) - assert f.read() == array.array('i', range(1000)).tostring() + assert f.read() == arraytostring(array.array('i', range(1000))) f.seek(0) b = ffi.new("int[]", 1005) f.readinto(ffi.buffer(b, 1000 * ffi.sizeof("int"))) @@ -1279,7 +1315,8 @@ class BackendTests: p = ffi.new("struct foo_s *", 10) # a single integer is the length assert p.len == 0 assert p.data[9] == 0 - py.test.raises(IndexError, "p.data[10]") + with pytest.raises(IndexError): + p.data[10] def test_ffi_typeof_getcname(self): ffi = FFI(backend=self.Backend()) diff --git a/testing/cffi0/test_ffi_backend.py b/testing/cffi0/test_ffi_backend.py index 12ecaee..8e29bc4 100644 --- a/testing/cffi0/test_ffi_backend.py +++ b/testing/cffi0/test_ffi_backend.py @@ -129,6 +129,36 @@ class TestFFI(backend_tests.BackendTests, alloc5 = ffi.new_allocator(myalloc5) py.test.raises(MemoryError, alloc5, "int[5]") + def test_new_struct_containing_struct_containing_array_varsize(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + struct foo_s { int len[100]; short data[]; }; + struct bar_s { int abc[100]; struct foo_s tail; }; + """) + # loop to try to detect heap overwrites, if the size allocated + # is too small + for i in range(1, 501, 100): + p = ffi.new("struct bar_s *", [[10], [[20], [3,4,5,6,7,8,9] * i]]) + assert p.abc[0] == 10 + assert p.tail.len[0] == 20 + assert p.tail.data[0] == 3 + assert p.tail.data[6] == 9 + assert p.tail.data[7 * i - 1] == 9 + + def test_bogus_struct_containing_struct_containing_array_varsize(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + struct foo_s { signed char len; signed char data[]; }; + struct bar_s { struct foo_s foo; int bcd; }; + """) + p = ffi.new("struct bar_s *", [[123, [45, 56, 67, 78]], 9999999]) + assert p.foo.len == 123 + assert p.foo.data[0] == 45 + assert p.foo.data[1] == 56 + assert p.foo.data[2] == 67 + assert p.bcd == 9999999 + assert p.foo.data[3] != 78 # has been overwritten with 9999999 + class TestBitfield: def check(self, source, expected_ofs_y, expected_align, expected_size): @@ -149,6 +179,7 @@ class TestBitfield: setters = ['case %d: s.%s = value; break;' % iname for iname in enumerate(fnames)] lib = ffi1.verify(""" + #include <string.h> struct s1 { %s }; struct sa { char a; struct s1 b; }; #define Gofs_y offsetof(struct s1, y) @@ -216,7 +247,10 @@ class TestBitfield: self.check("int a:2; short b:15; char c:2; char y;", 5, 4, 8) self.check("int a:2; char b:1; char c:1; char y;", 1, 4, 4) - @pytest.mark.skipif("platform.machine().startswith(('arm', 'aarch64'))") + @pytest.mark.skipif( + "not (sys.platform == 'darwin' and platform.machine() == 'arm64')" + " and " + "platform.machine().startswith(('arm', 'aarch64'))") def test_bitfield_anonymous_no_align(self): L = FFI().alignof("long long") self.check("char y; int :1;", 0, 1, 2) @@ -230,6 +264,8 @@ class TestBitfield: self.check("char x; long long :57; char y;", L + 8, 1, L + 9) @pytest.mark.skipif( + "(sys.platform == 'darwin' and platform.machine() == 'arm64')" + " or " "not platform.machine().startswith(('arm', 'aarch64'))") def test_bitfield_anonymous_align_arm(self): L = FFI().alignof("long long") @@ -243,7 +279,10 @@ class TestBitfield: self.check("char x; long long z:57; char y;", L + 8, L, L + 8 + L) self.check("char x; long long :57; char y;", L + 8, L, L + 8 + L) - @pytest.mark.skipif("platform.machine().startswith(('arm', 'aarch64'))") + @pytest.mark.skipif( + "not (sys.platform == 'darwin' and platform.machine() == 'arm64')" + " and " + "platform.machine().startswith(('arm', 'aarch64'))") def test_bitfield_zero(self): L = FFI().alignof("long long") self.check("char y; int :0;", 0, 1, 4) @@ -255,6 +294,8 @@ class TestBitfield: self.check("int a:1; int :0; int b:1; char y;", 5, 4, 8) @pytest.mark.skipif( + "(sys.platform == 'darwin' and platform.machine() == 'arm64')" + " or " "not platform.machine().startswith(('arm', 'aarch64'))") def test_bitfield_zero_arm(self): L = FFI().alignof("long long") @@ -268,12 +309,15 @@ class TestBitfield: def test_error_cases(self): ffi = FFI() - py.test.raises(TypeError, - 'ffi.cdef("struct s1 { float x:1; };"); ffi.new("struct s1 *")') - py.test.raises(TypeError, - 'ffi.cdef("struct s2 { char x:0; };"); ffi.new("struct s2 *")') - py.test.raises(TypeError, - 'ffi.cdef("struct s3 { char x:9; };"); ffi.new("struct s3 *")') + ffi.cdef("struct s1 { float x:1; };") + with pytest.raises(TypeError): + ffi.new("struct s1 *") + ffi.cdef("struct s2 { char x:0; };") + with pytest.raises(TypeError): + ffi.new("struct s2 *") + ffi.cdef("struct s3 { char x:9; };") + with pytest.raises(TypeError): + ffi.new("struct s3 *") def test_struct_with_typedef(self): ffi = FFI() diff --git a/testing/cffi0/test_function.py b/testing/cffi0/test_function.py index ca2353f..b4bb23d 100644 --- a/testing/cffi0/test_function.py +++ b/testing/cffi0/test_function.py @@ -1,10 +1,11 @@ import py +import pytest 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 testing.support import FdWriteCapture, StdErrCapture from .backend_tests import needs_dlopen_none try: @@ -90,7 +91,8 @@ class TestFunction(object): """) m = ffi.dlopen(lib_m) assert m.FOOBAR == 42 - py.test.raises(NotImplementedError, "m.baz") + with pytest.raises(NotImplementedError): + m.baz def test_tlsalloc(self): if sys.platform != 'win32': @@ -111,7 +113,7 @@ class TestFunction(object): ffi = FFI(backend=self.Backend()) ffi.cdef(""" int fputs(const char *, void *); - void *stderr; + extern void *stderr; """) needs_dlopen_none() ffi.C = ffi.dlopen(None) @@ -128,7 +130,7 @@ class TestFunction(object): ffi = FFI(backend=self.Backend()) ffi.cdef(""" int fputs(char *, void *); - void *stderr; + extern void *stderr; """) needs_dlopen_none() ffi.C = ffi.dlopen(None) @@ -145,7 +147,7 @@ class TestFunction(object): ffi = FFI(backend=self.Backend()) ffi.cdef(""" int fprintf(void *, const char *format, ...); - void *stderr; + extern void *stderr; """) needs_dlopen_none() ffi.C = ffi.dlopen(None) @@ -207,7 +209,7 @@ class TestFunction(object): py.test.skip("probably no symbol 'stderr' in the lib") ffi.cdef(""" int fputs(const char *, void *); - void *stderr; + extern void *stderr; """) needs_dlopen_none() ffi.C = ffi.dlopen(None) @@ -225,19 +227,32 @@ class TestFunction(object): def cb(): return returnvalue fptr = ffi.callback("void(*)(void)", cb) - old_stderr = sys.stderr - try: - sys.stderr = StringIO() + with StdErrCapture() as f: returned = fptr() - printed = sys.stderr.getvalue() - finally: - sys.stderr = old_stderr + printed = f.getvalue() assert returned is None if returnvalue is None: assert printed == '' else: assert "None" in printed + def test_callback_returning_struct_three_bytes(self): + if self.Backend is CTypesBackend: + py.test.skip("not supported with the ctypes backend") + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + typedef struct { + unsigned char a, b, c; + } THREEBYTES; + """) + def cb(): + return (12, 34, 56) + fptr = ffi.callback("THREEBYTES(*)(void)", cb) + tb = fptr() + assert tb.a == 12 + assert tb.b == 34 + assert tb.c == 56 + def test_passing_array(self): ffi = FFI(backend=self.Backend()) ffi.cdef(""" @@ -254,7 +269,7 @@ class TestFunction(object): py.test.skip("probably no symbol 'stdout' in the lib") ffi = FFI(backend=self.Backend()) ffi.cdef(""" - void *stdout; + extern void *stdout; """) needs_dlopen_none() C = ffi.dlopen(None) @@ -494,7 +509,7 @@ class TestFunction(object): ffi.cdef(""" typedef enum { MYE1, MYE2 } myenum_t; double myfunc(double); - double myvar; + extern double myvar; const double myconst; #define MYFOO 42 """) @@ -505,7 +520,7 @@ class TestFunction(object): 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;") + ffi.cdef("int foobar(void); extern int foobaz;") lib = ffi.dlopen(lib_m) ffi.dlclose(lib) e = py.test.raises(ValueError, getattr, lib, 'foobar') @@ -518,3 +533,16 @@ class TestFunction(object): assert str(e.value).startswith("library '") assert str(e.value).endswith("' has already been closed") ffi.dlclose(lib) # does not raise + + def test_passing_large_list(self): + if self.Backend is CTypesBackend: + py.test.skip("the ctypes backend doesn't support this") + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + void getenv(char *); + """) + needs_dlopen_none() + m = ffi.dlopen(None) + arg = [b"F", b"O", b"O"] + [b"\x00"] * 20000000 + x = m.getenv(arg) + assert x is None diff --git a/testing/cffi0/test_ownlib.py b/testing/cffi0/test_ownlib.py index a06df20..ffad879 100644 --- a/testing/cffi0/test_ownlib.py +++ b/testing/cffi0/test_ownlib.py @@ -35,6 +35,10 @@ typedef struct { long bottom; } RECT; +typedef struct { + unsigned char a, b, c; +} THREEBYTES; + EXPORT int PointInRect(RECT *prc, POINT pt) { @@ -107,6 +111,15 @@ EXPORT void modify_struct_value(RECT r) { r.left = r.right = r.top = r.bottom = 500; } + +EXPORT THREEBYTES return_three_bytes(void) +{ + THREEBYTES result; + result.a = 12; + result.b = 34; + result.c = 56; + return result; +} """ class TestOwnLib(object): @@ -201,7 +214,7 @@ class TestOwnLib(object): py.test.skip("fix the auto-generation of the tiny test lib") ffi = FFI(backend=self.Backend()) ffi.cdef(""" - int my_array[7]; + extern int my_array[7]; """) ownlib = ffi.dlopen(self.module) for i in range(7): @@ -223,7 +236,7 @@ class TestOwnLib(object): py.test.skip("not supported by the ctypes backend") ffi = FFI(backend=self.Backend()) ffi.cdef(""" - int my_array[]; + extern int my_array[]; """) ownlib = ffi.dlopen(self.module) for i in range(7): @@ -291,7 +304,7 @@ class TestOwnLib(object): long bottom; } RECT; - long left, top, right, bottom; + extern long left, top, right, bottom; RECT ReturnRect(int i, RECT ar, RECT* br, POINT cp, RECT dr, RECT *er, POINT fp, RECT gr); @@ -321,7 +334,7 @@ class TestOwnLib(object): if self.Backend is CTypesBackend: py.test.skip("not implemented with the ctypes backend") ffi = FFI(backend=self.Backend()) - ffi.cdef("long left; int test_getting_errno(void);") + ffi.cdef("extern long left; int test_getting_errno(void);") lib = ffi.dlopen(self.module) lib.left = 123456 p = ffi.addressof(lib, "left") @@ -371,3 +384,48 @@ class TestOwnLib(object): assert s.top == 22 assert s.right == 33 assert s.bottom == 44 + + def test_dlopen_handle(self): + if self.module is None: + py.test.skip("fix the auto-generation of the tiny test lib") + if sys.platform == 'win32': + py.test.skip("uses 'dl' explicitly") + if self.__class__.Backend is CTypesBackend: + py.test.skip("not for the ctypes backend") + backend = self.Backend() + ffi1 = FFI(backend=backend) + ffi1.cdef("""void *dlopen(const char *filename, int flags); + int dlclose(void *handle);""") + lib1 = ffi1.dlopen('dl') + handle = lib1.dlopen(self.module.encode(sys.getfilesystemencoding()), + backend.RTLD_LAZY) + assert ffi1.typeof(handle) == ffi1.typeof("void *") + assert handle + + ffi = FFI(backend=backend) + ffi.cdef("""unsigned short foo_2bytes(unsigned short a);""") + lib = ffi.dlopen(handle) + x = lib.foo_2bytes(1000) + assert x == 1042 + + err = lib1.dlclose(handle) + assert err == 0 + + def test_return_three_bytes(self): + if self.module is None: + py.test.skip("fix the auto-generation of the tiny test lib") + if self.__class__.Backend is CTypesBackend: + py.test.skip("not working on win32 on the ctypes backend") + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + typedef struct { + unsigned char a, b, c; + } THREEBYTES; + + THREEBYTES return_three_bytes(void); + """) + lib = ffi.dlopen(self.module) + tb = lib.return_three_bytes() + assert tb.a == 12 + assert tb.b == 34 + assert tb.c == 56 diff --git a/testing/cffi0/test_parsing.py b/testing/cffi0/test_parsing.py index 2d75850..a5e4587 100644 --- a/testing/cffi0/test_parsing.py +++ b/testing/cffi0/test_parsing.py @@ -174,7 +174,7 @@ def test_remove_line_continuation_comments(): double // blah \\ more comments x(void); - double // blah\\\\ + double // blah // blah\\\\ y(void); double // blah\\ \ etc @@ -185,6 +185,93 @@ def test_remove_line_continuation_comments(): m.y m.z +def test_dont_remove_comment_in_line_directives(): + ffi = FFI(backend=FakeBackend()) + e = py.test.raises(CDefError, ffi.cdef, """ + \t # \t line \t 8 \t "baz.c" \t + + some syntax error here + """) + assert str(e.value) == "parse error\nbaz.c:9:14: before: syntax" + # + e = py.test.raises(CDefError, ffi.cdef, """ + #line 7 "foo//bar.c" + + some syntax error here + """) + # + assert str(e.value) == "parse error\nfoo//bar.c:8:14: before: syntax" + ffi = FFI(backend=FakeBackend()) + e = py.test.raises(CDefError, ffi.cdef, """ + \t # \t 8 \t "baz.c" \t + + some syntax error here + """) + assert str(e.value) == "parse error\nbaz.c:9:14: before: syntax" + # + e = py.test.raises(CDefError, ffi.cdef, """ + # 7 "foo//bar.c" + + some syntax error here + """) + assert str(e.value) == "parse error\nfoo//bar.c:8:14: before: syntax" + +def test_multiple_line_directives(): + ffi = FFI(backend=FakeBackend()) + e = py.test.raises(CDefError, ffi.cdef, + """ #line 5 "foo.c" + extern int xx; + #line 6 "bar.c" + extern int yy; + #line 7 "baz.c" + some syntax error here + #line 8 "yadda.c" + extern int zz; + """) + assert str(e.value) == "parse error\nbaz.c:7:14: before: syntax" + # + e = py.test.raises(CDefError, ffi.cdef, + """ # 5 "foo.c" + extern int xx; + # 6 "bar.c" + extern int yy; + # 7 "baz.c" + some syntax error here + # 8 "yadda.c" + extern int zz; + """) + assert str(e.value) == "parse error\nbaz.c:7:14: before: syntax" + +def test_commented_line_directive(): + ffi = FFI(backend=FakeBackend()) + e = py.test.raises(CDefError, ffi.cdef, """ + /* + #line 5 "foo.c" + */ + void xx(void); + + #line 6 "bar.c" + /* + #line 35 "foo.c" + */ + some syntax error + """) + # + assert str(e.value) == "parse error\nbar.c:9:14: before: syntax" + e = py.test.raises(CDefError, ffi.cdef, """ + /* + # 5 "foo.c" + */ + void xx(void); + + # 6 "bar.c" + /* + # 35 "foo.c" + */ + some syntax error + """) + assert str(e.value) == "parse error\nbar.c:9:14: before: syntax" + def test_line_continuation_in_defines(): ffi = FFI(backend=FakeBackend()) ffi.cdef(""" @@ -324,6 +411,7 @@ def test_WPARAM_on_windows(): assert value == sys.maxsize * 2 - 40 def test__is_constant_globalvar(): + import warnings for input, expected_output in [ ("int a;", False), ("const int a;", True), @@ -341,10 +429,13 @@ def test__is_constant_globalvar(): ("const int a[5][6];", False), ]: ffi = FFI() - ffi.cdef(input) + with warnings.catch_warnings(record=True) as log: + warnings.simplefilter("always") + ffi.cdef(input) declarations = ffi._parser._declarations assert ('constant a' in declarations) == expected_output assert ('variable a' in declarations) == (not expected_output) + assert len(log) == (1 - expected_output) def test_restrict(): from cffi import model @@ -354,7 +445,7 @@ def test_restrict(): ("int *a;", False), ]: ffi = FFI() - ffi.cdef(input) + ffi.cdef("extern " + input) tp, quals = ffi._parser._declarations['variable a'] assert bool(quals & model.Q_RESTRICT) == expected_output @@ -409,7 +500,17 @@ def test_const_pointer_to_pointer(): def test_enum(): ffi = FFI() ffi.cdef(""" - enum Enum { POS = +1, TWO = 2, NIL = 0, NEG = -1, OP = (POS+TWO)-1}; + enum Enum { + POS = +1, + TWO = 2, + NIL = 0, + NEG = -1, + ADDSUB = (POS+TWO)-1, + DIVMULINT = (3 * 3) / 2, + SHIFT = (1 << 3) >> 1, + BINOPS = (0x7 & 0x1) | 0x8, + XOR = 0xf ^ 0xa + }; """) needs_dlopen_none() C = ffi.dlopen(None) @@ -417,7 +518,11 @@ def test_enum(): assert C.TWO == 2 assert C.NIL == 0 assert C.NEG == -1 - assert C.OP == 2 + assert C.ADDSUB == 2 + assert C.DIVMULINT == 4 + assert C.SHIFT == 4 + assert C.BINOPS == 0b1001 + assert C.XOR == 0b0101 def test_stdcall(): ffi = FFI() @@ -466,3 +571,40 @@ def test_error_invalid_syntax_for_cdef(): e = py.test.raises(CDefError, ffi.cdef, 'void foo(void) {}') assert str(e.value) == ('<cdef source string>:1: unexpected <FuncDef>: ' 'this construct is valid C but not valid in cdef()') + +def test_unsigned_int_suffix_for_constant(): + ffi = FFI() + ffi.cdef("""enum e { + bin_0=0b10, + bin_1=0b10u, + bin_2=0b10U, + bin_3=0b10l, + bin_4=0b10L, + bin_5=0b10ll, + bin_6=0b10LL, + oct_0=010, + oct_1=010u, + oct_2=010U, + oct_3=010l, + oct_4=010L, + oct_5=010ll, + oct_6=010LL, + dec_0=10, + dec_1=10u, + dec_2=10U, + dec_3=10l, + dec_4=10L, + dec_5=10ll, + dec_6=10LL, + hex_0=0x10, + hex_1=0x10u, + hex_2=0x10U, + hex_3=0x10l, + hex_4=0x10L, + hex_5=0x10ll, + hex_6=0x10LL,};""") + needs_dlopen_none() + C = ffi.dlopen(None) + for base, expected_result in (('bin', 2), ('oct', 8), ('dec', 10), ('hex', 16)): + for index in range(7): + assert getattr(C, '{base}_{index}'.format(base=base, index=index)) == expected_result diff --git a/testing/cffi0/test_verify.py b/testing/cffi0/test_verify.py index 79e1c6c..3a1c0b9 100644 --- a/testing/cffi0/test_verify.py +++ b/testing/cffi0/test_verify.py @@ -1,7 +1,9 @@ import py, re +import pytest import sys, os, math, weakref from cffi import FFI, VerificationError, VerificationMissing, model, FFIError from testing.support import * +from testing.support import extra_compile_args lib_m = ['m'] @@ -12,16 +14,6 @@ if sys.platform == 'win32': lib_m = ['msvcrt'] pass # no obvious -Werror equivalent on MSVC else: - if (sys.platform == 'darwin' and - [int(x) for x in os.uname()[2].split('.')] >= [11, 0, 0]): - # assume a standard clang or gcc - extra_compile_args = ['-Werror', '-Wall', '-Wextra', '-Wconversion'] - # special things for clang - extra_compile_args.append('-Qunused-arguments') - else: - # assume a standard gcc - extra_compile_args = ['-Werror', '-Wall', '-Wextra', '-Wconversion'] - class FFI(FFI): def verify(self, *args, **kwds): return super(FFI, self).verify( @@ -284,7 +276,7 @@ def test_all_integer_and_float_types(): def test_var_signed_integer_types(): ffi = FFI() lst = all_signed_integer_types(ffi) - csource = "\n".join(["%s somevar_%s;" % (tp, tp.replace(' ', '_')) + csource = "\n".join(["static %s somevar_%s;" % (tp, tp.replace(' ', '_')) for tp in lst]) ffi.cdef(csource) lib = ffi.verify(csource) @@ -303,7 +295,7 @@ def test_var_signed_integer_types(): def test_var_unsigned_integer_types(): ffi = FFI() lst = all_unsigned_integer_types(ffi) - csource = "\n".join(["%s somevar_%s;" % (tp, tp.replace(' ', '_')) + csource = "\n".join(["static %s somevar_%s;" % (tp, tp.replace(' ', '_')) for tp in lst]) ffi.cdef(csource) lib = ffi.verify(csource) @@ -589,7 +581,8 @@ def test_struct_array_guess_length(): assert ffi.sizeof('struct foo_s') == 19 * ffi.sizeof('int') s = ffi.new("struct foo_s *") assert ffi.sizeof(s.a) == 17 * ffi.sizeof('int') - py.test.raises(IndexError, 's.a[17]') + with pytest.raises(IndexError): + s.a[17] def test_struct_array_c99_1(): if sys.platform == 'win32': @@ -647,7 +640,8 @@ def test_struct_with_bitfield_exact(): ffi.verify("struct foo_s { int a:2, b:3; };") s = ffi.new("struct foo_s *") s.b = 3 - py.test.raises(OverflowError, "s.b = 4") + with pytest.raises(OverflowError): + s.b = 4 assert s.b == 3 def test_struct_with_bitfield_enum(): @@ -813,8 +807,8 @@ def test_define_int(): def test_access_variable(): ffi = FFI() - ffi.cdef("int foo(void);\n" - "int somenumber;") + ffi.cdef("static int foo(void);\n" + "static int somenumber;") lib = ffi.verify(""" static int somenumber = 2; static int foo(void) { @@ -831,7 +825,7 @@ def test_access_variable(): def test_access_address_of_variable(): # access the address of 'somenumber': need a trick ffi = FFI() - ffi.cdef("int somenumber; static int *const somenumberptr;") + ffi.cdef("static int somenumber; static int *const somenumberptr;") lib = ffi.verify(""" static int somenumber = 2; #define somenumberptr (&somenumber) @@ -844,7 +838,7 @@ def test_access_address_of_variable(): def test_access_array_variable(length=5): ffi = FFI() ffi.cdef("int foo(int);\n" - "int somenumber[%s];" % (length,)) + "static int somenumber[%s];" % (length,)) lib = ffi.verify(""" static int somenumber[] = {2, 2, 3, 4, 5}; static int foo(int i) { @@ -876,7 +870,7 @@ def test_access_struct_variable(): ffi = FFI() ffi.cdef("struct foo { int x; ...; };\n" "int foo(int);\n" - "struct foo stuff;") + "static struct foo stuff;") lib = ffi.verify(""" struct foo { int x, y, z; }; static struct foo stuff = {2, 5, 8}; @@ -900,9 +894,9 @@ def test_access_struct_variable(): def test_access_callback(): ffi = FFI() - ffi.cdef("int (*cb)(int);\n" - "int foo(int);\n" - "void reset_cb(void);") + ffi.cdef("static int (*cb)(int);\n" + "static int foo(int);\n" + "static void reset_cb(void);") lib = ffi.verify(""" static int g(int x) { return x * 7; } static int (*cb)(int); @@ -918,9 +912,9 @@ def test_access_callback(): def test_access_callback_function_typedef(): ffi = FFI() ffi.cdef("typedef int mycallback_t(int);\n" - "mycallback_t *cb;\n" - "int foo(int);\n" - "void reset_cb(void);") + "static mycallback_t *cb;\n" + "static int foo(int);\n" + "static void reset_cb(void);") lib = ffi.verify(""" static int g(int x) { return x * 7; } static int (*cb)(int); @@ -1070,7 +1064,7 @@ def test_autofilled_struct_as_argument(): def test_autofilled_struct_as_argument_dynamic(): ffi = FFI() ffi.cdef("struct foo_s { long a; ...; };\n" - "int (*foo)(struct foo_s);") + "static int (*foo)(struct foo_s);") lib = ffi.verify(""" struct foo_s { double b; @@ -1079,7 +1073,7 @@ def test_autofilled_struct_as_argument_dynamic(): int foo1(struct foo_s s) { return (int)s.a - (int)s.b; } - int (*foo)(struct foo_s s) = &foo1; + static int (*foo)(struct foo_s s) = &foo1; """) e = py.test.raises(NotImplementedError, lib.foo, "?") msg = ("ctype 'struct foo_s' not supported as argument. It is a struct " @@ -1449,7 +1443,7 @@ def test_bool(): py.test.skip("_Bool not in MSVC") ffi = FFI() ffi.cdef("struct foo_s { _Bool x; };" - "_Bool foo(_Bool); _Bool (*foop)(_Bool);") + "_Bool foo(_Bool); static _Bool (*foop)(_Bool);") lib = ffi.verify(""" struct foo_s { _Bool x; }; int foo(int arg) { @@ -1458,13 +1452,15 @@ def test_bool(): _Bool _foofunc(_Bool x) { return !x; } - _Bool (*foop)(_Bool) = _foofunc; + static _Bool (*foop)(_Bool) = _foofunc; """) p = ffi.new("struct foo_s *") p.x = 1 assert p.x is True - py.test.raises(OverflowError, "p.x = -1") - py.test.raises(TypeError, "p.x = 0.0") + with pytest.raises(OverflowError): + p.x = -1 + with pytest.raises(TypeError): + p.x = 0.0 assert lib.foop(1) is False assert lib.foop(True) is False assert lib.foop(0) is True @@ -1532,7 +1528,8 @@ def test_cannot_pass_float(): } """ % (type, type)) p = ffi.new("struct foo_s *") - py.test.raises(TypeError, "p.x = 0.0") + with pytest.raises(TypeError): + p.x = 0.0 assert lib.foo(42) == 0 assert lib.foo(0) == 1 py.test.raises(TypeError, lib.foo, 0.0) @@ -1646,7 +1643,7 @@ def test_FILE_stored_in_stdout(): def test_FILE_stored_explicitly(): ffi = FFI() - ffi.cdef("int myprintf11(const char *, int); FILE *myfile;") + ffi.cdef("int myprintf11(const char *, int); extern FILE *myfile;") lib = ffi.verify(""" #include <stdio.h> FILE *myfile; @@ -1672,19 +1669,19 @@ def test_FILE_stored_explicitly(): def test_global_array_with_missing_length(): ffi = FFI() - ffi.cdef("int fooarray[];") + ffi.cdef("extern int fooarray[];") lib = ffi.verify("int fooarray[50];") assert repr(lib.fooarray).startswith("<cdata 'int *'") def test_global_array_with_dotdotdot_length(): ffi = FFI() - ffi.cdef("int fooarray[...];") + ffi.cdef("extern int fooarray[...];") lib = ffi.verify("int fooarray[50];") assert repr(lib.fooarray).startswith("<cdata 'int[50]'") def test_bad_global_array_with_dotdotdot_length(): ffi = FFI() - ffi.cdef("int fooarray[...];") + ffi.cdef("extern int fooarray[...];") py.test.raises(VerificationError, ffi.verify, "char fooarray[23];") def test_struct_containing_struct(): @@ -1805,7 +1802,7 @@ def test_string_to_voidp_arg(): def test_callback_indirection(): ffi = FFI() ffi.cdef(""" - int (*python_callback)(int how_many, int *values); + static int (*python_callback)(int how_many, int *values); int (*const c_callback)(int,...); /* pass this ptr to C routines */ int some_c_function(int(*cb)(int,...)); """) @@ -1939,24 +1936,24 @@ def test_typeof_func_with_struct_argument(): def test_bug_const_char_ptr_array_1(): ffi = FFI() - ffi.cdef("""const char *a[...];""") + ffi.cdef("""extern const char *a[...];""") lib = ffi.verify("""const char *a[5];""") assert repr(ffi.typeof(lib.a)) == "<ctype 'char *[5]'>" def test_bug_const_char_ptr_array_2(): from cffi import FFI # ignore warnings ffi = FFI() - ffi.cdef("""const int a[];""") + ffi.cdef("""extern const int a[];""") lib = ffi.verify("""const int a[5];""") assert repr(ffi.typeof(lib.a)) == "<ctype 'int *'>" def _test_various_calls(force_libffi): cdef_source = """ - int xvalue; - long long ivalue, rvalue; - float fvalue; - double dvalue; - long double Dvalue; + extern int xvalue; + extern long long ivalue, rvalue; + extern float fvalue; + extern double dvalue; + extern long double Dvalue; signed char tf_bb(signed char x, signed char c); unsigned char tf_bB(signed char x, unsigned char c); short tf_bh(signed char x, short c); @@ -2098,6 +2095,11 @@ def _run_in_multiple_threads(test1): raise errors[0][1] def test_errno_working_even_with_pypys_jit(): + # NOTE: on some platforms, to work correctly, this test needs to be + # compiled with -pthread. Otherwise, the accesses to errno done from f() + # are compiled by assuming this small library won't be used from multiple + # threads, which is wrong. If you see failures _and_ if you pass your + # own CFLAGS environment variable, please make sure "-pthread" is in it. ffi = FFI() ffi.cdef("int f(int);") lib = ffi.verify(""" @@ -2135,7 +2137,7 @@ def test_verify_dlopen_flags(): # exported symbols as well. So we must not export a simple name # like 'foo'! ffi1 = FFI() - ffi1.cdef("int foo_verify_dlopen_flags;") + ffi1.cdef("extern int foo_verify_dlopen_flags;") lib1 = ffi1.verify("int foo_verify_dlopen_flags;", flags=ffi1.RTLD_GLOBAL | ffi1.RTLD_LAZY) @@ -2149,7 +2151,7 @@ def test_verify_dlopen_flags(): def get_second_lib(): # Hack, using modulename makes the test fail ffi2 = FFI() - ffi2.cdef("int foo_verify_dlopen_flags;") + ffi2.cdef("extern int foo_verify_dlopen_flags;") lib2 = ffi2.verify("int foo_verify_dlopen_flags;", flags=ffi2.RTLD_GLOBAL | ffi2.RTLD_LAZY) return lib2 @@ -2522,3 +2524,39 @@ def test_ffi_new_with_cycles(): x.p = p x.cyclic = x del p, x + +def test_arithmetic_in_cdef(): + for a in [0, 11, 15]: + ffi = FFI() + ffi.cdef(""" + enum FOO { + DIVNN = ((-?) / (-3)), + DIVNP = ((-?) / (+3)), + DIVPN = ((+?) / (-3)), + MODNN = ((-?) % (-3)), + MODNP = ((-?) % (+3)), + MODPN = ((+?) % (-3)), + }; + """.replace('?', str(a))) + lib = ffi.verify(""" + enum FOO { + DIVNN = ((-?) / (-3)), + DIVNP = ((-?) / (+3)), + DIVPN = ((+?) / (-3)), + MODNN = ((-?) % (-3)), + MODNP = ((-?) % (+3)), + MODPN = ((+?) % (-3)), + }; + """.replace('?', str(a))) + # the verify() crashes if the values in the enum are different from + # the values we computed ourselves from the cdef() + +def test_passing_large_list(): + ffi = FFI() + ffi.cdef("""void passing_large_list(long[]);""") + lib = ffi.verify(""" + static void passing_large_list(long a[]) { } + """) + arg = list(range(20000000)) + lib.passing_large_list(arg) + # assert did not segfault diff --git a/testing/cffi0/test_version.py b/testing/cffi0/test_version.py index 9325685..facb84c 100644 --- a/testing/cffi0/test_version.py +++ b/testing/cffi0/test_version.py @@ -18,6 +18,7 @@ def test_version(): version_info = '.'.join(str(i) for i in cffi.__version_info__) version_info = version_info.replace('.beta.', 'b') version_info = version_info.replace('.plus', '+') + version_info = version_info.replace('.rc', 'rc') assert v == version_info #v = BACKEND_VERSIONS.get(v, v) assert v == _cffi_backend.__version__ @@ -36,7 +37,12 @@ def test_doc_version_file(): v = cffi.__version__.replace('+', '') p = os.path.join(parent, 'doc', 'source', 'installation.rst') content = open(p).read() - assert (" package version %s:" % v) in content + if " package version %s:" % v not in content: + for i in range(5): + if " package version %s-%d:" % (v, i) in content: + break + else: + assert 0, "doc/source/installation.rst needs updating" def test_setup_version(): parent = os.path.dirname(os.path.dirname(cffi.__file__)) diff --git a/testing/cffi0/test_zdistutils.py b/testing/cffi0/test_zdistutils.py index b67b105..35b3d0c 100644 --- a/testing/cffi0/test_zdistutils.py +++ b/testing/cffi0/test_zdistutils.py @@ -89,7 +89,7 @@ class DistUtilsTest(object): csrc = '/*hi there %s!2*/\n#include <math.h>\n' % self v = Verifier(ffi, csrc, force_generic_engine=self.generic, libraries=[self.lib_m]) - basename = self.__class__.__name__ + 'test_compile_module' + basename = self.__class__.__name__[:10] + '_test_compile_module' v.modulefilename = filename = str(udir.join(basename + '.so')) v.compile_module() assert filename == v.modulefilename diff --git a/testing/cffi0/test_zintegration.py b/testing/cffi0/test_zintegration.py index d56dac2..ce925b8 100644 --- a/testing/cffi0/test_zintegration.py +++ b/testing/cffi0/test_zintegration.py @@ -1,11 +1,13 @@ import py, os, sys, shutil import subprocess from testing.udir import udir +import pytest if sys.platform == 'win32': - py.test.skip('snippets do not run on win32') + pytestmark = pytest.mark.skip('snippets do not run on win32') if sys.version_info < (2, 7): - py.test.skip('fails e.g. on a Debian/Ubuntu which patches virtualenv' + pytestmark = pytest.mark.skip( + 'fails e.g. on a Debian/Ubuntu which patches virtualenv' ' in a non-2.6-friendly way') def create_venv(name): @@ -75,7 +77,10 @@ def really_run_setup_and_program(dirname, venv_dir_and_paths, python_snippet): env = os.environ.copy() env['PYTHONPATH'] = paths subprocess.check_call((vp, 'setup.py', 'clean'), env=env) - subprocess.check_call((vp, 'setup.py', 'install'), env=env) + # there's a setuptools/easy_install bug that causes this to fail when the build/install occur together and + # we're in the same directory with the build (it tries to look up dependencies for itself on PyPI); + # subsequent runs will succeed because this test doesn't properly clean up the build- use pip for now. + subprocess.check_call((vp, '-m', 'pip', 'install', '.'), env=env) subprocess.check_call((vp, str(python_f)), env=env) finally: os.chdir(olddir) diff --git a/testing/cffi1/test_cffi_binary.py b/testing/cffi1/test_cffi_binary.py index 25953db..7cfbace 100644 --- a/testing/cffi1/test_cffi_binary.py +++ b/testing/cffi1/test_cffi_binary.py @@ -10,6 +10,8 @@ def test_no_unknown_exported_symbols(): for line in g: if not line.startswith('0'): continue + if line[line.find(' ') + 1] == 'l': + continue if '*UND*' in line: continue name = line.split()[-1] diff --git a/testing/cffi1/test_dlopen.py b/testing/cffi1/test_dlopen.py index 1c20550..26a2717 100644 --- a/testing/cffi1/test_dlopen.py +++ b/testing/cffi1/test_dlopen.py @@ -6,7 +6,7 @@ from testing.udir import udir def test_simple(): ffi = FFI() - ffi.cdef("int close(int); static const int BB = 42; int somevar;") + ffi.cdef("int close(int); static const int BB = 42; extern int somevar;") target = udir.join('test_simple.py') make_py_source(ffi, 'test_simple', str(target)) assert target.read() == r"""# auto-generated file @@ -196,7 +196,7 @@ def test_array_overflow(): def test_global_var(): ffi = FFI() - ffi.cdef("int myglob;") + ffi.cdef("extern int myglob;") target = udir.join('test_global_var.py') make_py_source(ffi, 'test_global_var', str(target)) assert target.read() == r"""# auto-generated file diff --git a/testing/cffi1/test_ffi_obj.py b/testing/cffi1/test_ffi_obj.py index e07d6f9..0d29290 100644 --- a/testing/cffi1/test_ffi_obj.py +++ b/testing/cffi1/test_ffi_obj.py @@ -1,4 +1,5 @@ import py, sys +import pytest import _cffi_backend as _cffi1_backend @@ -85,9 +86,12 @@ def test_ffi_NULL(): def test_ffi_no_attr(): ffi = _cffi1_backend.FFI() - py.test.raises(AttributeError, "ffi.no_such_name") - py.test.raises(AttributeError, "ffi.no_such_name = 42") - py.test.raises(AttributeError, "del ffi.no_such_name") + with pytest.raises(AttributeError): + ffi.no_such_name + with pytest.raises(AttributeError): + ffi.no_such_name = 42 + with pytest.raises(AttributeError): + del ffi.no_such_name def test_ffi_string(): ffi = _cffi1_backend.FFI() diff --git a/testing/cffi1/test_function_args.py b/testing/cffi1/test_function_args.py new file mode 100644 index 0000000..30c6fed --- /dev/null +++ b/testing/cffi1/test_function_args.py @@ -0,0 +1,208 @@ +import pytest, sys +try: + # comment out the following line to run this test. + # the latest on x86-64 linux: https://github.com/libffi/libffi/issues/574 + if sys.platform != 'win32': + raise ImportError("this test is skipped because it keeps finding " + "failures in libffi, instead of cffi") + + from hypothesis import given, settings, example + from hypothesis import strategies as st +except ImportError as e: + e1 = e + def test_types(): + pytest.skip(str(e1)) +else: + + from cffi import FFI + import sys, random + from .test_recompiler import verify + + ALL_PRIMITIVES = [ + 'unsigned char', + 'short', + 'int', + 'long', + 'long long', + 'float', + 'double', + #'long double', --- on x86 it can give libffi crashes + ] + def _make_struct(s): + return st.lists(s, min_size=1) + types = st.one_of(st.sampled_from(ALL_PRIMITIVES), + st.lists(st.sampled_from(ALL_PRIMITIVES), min_size=1)) + # NB. 'types' could be st.recursive instead, but it doesn't + # really seem useful + + def draw_primitive(ffi, typename): + value = random.random() * 2**40 + if typename != 'long double': + return ffi.cast(typename, value) + else: + return value + + TEST_RUN_COUNTER = 0 + + + @given(st.lists(types), types) + @settings(max_examples=100, deadline=5000) # 5000ms + def test_types(tp_args, tp_result): + global TEST_RUN_COUNTER + print(tp_args, tp_result) + cdefs = [] + structs = {} + + def build_type(tp): + if type(tp) is list: + field_types = [build_type(tp1) for tp1 in tp] + fields = ['%s f%d;' % (ftp, j) + for (j, ftp) in enumerate(field_types)] + fields = '\n '.join(fields) + name = 's%d' % len(cdefs) + cdefs.append("typedef struct {\n %s\n} %s;" % (fields, name)) + structs[name] = field_types + return name + else: + return tp + + args = [build_type(tp) for tp in tp_args] + result = build_type(tp_result) + + TEST_RUN_COUNTER += 1 + signature = "%s testfargs(%s)" % (result, + ', '.join(['%s a%d' % (arg, i) for (i, arg) in enumerate(args)]) + or 'void') + + source = list(cdefs) + + cdefs.append("%s;" % signature) + cdefs.append("extern %s testfargs_result;" % result) + for i, arg in enumerate(args): + cdefs.append("extern %s testfargs_arg%d;" % (arg, i)) + source.append("%s testfargs_result;" % result) + for i, arg in enumerate(args): + source.append("%s testfargs_arg%d;" % (arg, i)) + source.append(signature) + source.append("{") + for i, arg in enumerate(args): + source.append(" testfargs_arg%d = a%d;" % (i, i)) + source.append(" return testfargs_result;") + source.append("}") + + typedef_line = "typedef %s;" % (signature.replace('testfargs', + '(*mycallback_t)'),) + assert signature.endswith(')') + sig_callback = "%s testfcallback(mycallback_t callback)" % result + cdefs.append(typedef_line) + cdefs.append("%s;" % sig_callback) + source.append(typedef_line) + source.append(sig_callback) + source.append("{") + source.append(" return callback(%s);" % + ', '.join(["testfargs_arg%d" % i for i in range(len(args))])) + source.append("}") + + ffi = FFI() + ffi.cdef("\n".join(cdefs)) + lib = verify(ffi, 'test_function_args_%d' % TEST_RUN_COUNTER, + "\n".join(source), no_cpp=True) + + # when getting segfaults, enable this: + if False: + from testing.udir import udir + import subprocess + f = open(str(udir.join('run1.py')), 'w') + f.write('import sys; sys.path = %r\n' % (sys.path,)) + f.write('from _CFFI_test_function_args_%d import ffi, lib\n' % + TEST_RUN_COUNTER) + for i in range(len(args)): + f.write('a%d = ffi.new("%s *")\n' % (i, args[i])) + aliststr = ', '.join(['a%d[0]' % i for i in range(len(args))]) + f.write('lib.testfargs(%s)\n' % aliststr) + f.write('ffi.addressof(lib, "testfargs")(%s)\n' % aliststr) + f.close() + print("checking for segfault for direct call...") + rc = subprocess.call([sys.executable, 'run1.py'], cwd=str(udir)) + assert rc == 0, rc + + def make_arg(tp): + if tp in structs: + return [make_arg(tp1) for tp1 in structs[tp]] + else: + return draw_primitive(ffi, tp) + + passed_args = [make_arg(arg) for arg in args] + returned_value = make_arg(result) + + def write(p, v): + if type(v) is list: + for i, v1 in enumerate(v): + write(ffi.addressof(p, 'f%d' % i), v1) + else: + p[0] = v + + write(ffi.addressof(lib, 'testfargs_result'), returned_value) + + ## CALL forcing libffi + print("CALL forcing libffi") + received_return = ffi.addressof(lib, 'testfargs')(*passed_args) + ## + + _tp_long_double = ffi.typeof("long double") + def check(p, v): + if type(v) is list: + for i, v1 in enumerate(v): + check(ffi.addressof(p, 'f%d' % i), v1) + else: + if ffi.typeof(p).item is _tp_long_double: + assert ffi.cast("double", p[0]) == v + else: + assert p[0] == v + + for i, arg in enumerate(passed_args): + check(ffi.addressof(lib, 'testfargs_arg%d' % i), arg) + ret = ffi.new(result + "*", received_return) + check(ret, returned_value) + + ## CALLBACK + def expand(value): + if isinstance(value, ffi.CData): + t = ffi.typeof(value) + if t is _tp_long_double: + return float(ffi.cast("double", value)) + return [expand(getattr(value, 'f%d' % i)) + for i in range(len(t.fields))] + else: + return value + + # when getting segfaults, enable this: + if False: + from testing.udir import udir + import subprocess + f = open(str(udir.join('run1.py')), 'w') + f.write('import sys; sys.path = %r\n' % (sys.path,)) + f.write('from _CFFI_test_function_args_%d import ffi, lib\n' % + TEST_RUN_COUNTER) + f.write('def callback(*args): return ffi.new("%s *")[0]\n' % result) + f.write('fptr = ffi.callback("%s(%s)", callback)\n' % (result, + ','.join(args))) + f.write('print(lib.testfcallback(fptr))\n') + f.close() + print("checking for segfault for callback...") + rc = subprocess.call([sys.executable, 'run1.py'], cwd=str(udir)) + assert rc == 0, rc + + seen_args = [] + def callback(*args): + seen_args.append([expand(arg) for arg in args]) + return returned_value + + fptr = ffi.callback("%s(%s)" % (result, ','.join(args)), callback) + print("CALL with callback") + received_return = lib.testfcallback(fptr) + + assert len(seen_args) == 1 + assert passed_args == seen_args[0] + ret = ffi.new(result + "*", received_return) + check(ret, returned_value) diff --git a/testing/cffi1/test_new_ffi_1.py b/testing/cffi1/test_new_ffi_1.py index 209cb30..640830b 100644 --- a/testing/cffi1/test_new_ffi_1.py +++ b/testing/cffi1/test_new_ffi_1.py @@ -1,4 +1,5 @@ import py +import pytest import platform, imp import sys, os, ctypes import cffi @@ -186,10 +187,14 @@ class TestNewFFI1: p[9] = 43 assert p[0] == 42 assert p[9] == 43 - py.test.raises(IndexError, "p[10]") - py.test.raises(IndexError, "p[10] = 44") - py.test.raises(IndexError, "p[-1]") - py.test.raises(IndexError, "p[-1] = 44") + with pytest.raises(IndexError): + p[10] + with pytest.raises(IndexError): + p[10] = 44 + with pytest.raises(IndexError): + p[-1] + with pytest.raises(IndexError): + p[-1] = 44 def test_new_array_args(self): # this tries to be closer to C: where we say "int x[5] = {10, 20, ..}" @@ -212,18 +217,21 @@ class TestNewFFI1: def test_new_array_varsize(self): p = ffi.new("int[]", 10) # a single integer is the length assert p[9] == 0 - py.test.raises(IndexError, "p[10]") + with pytest.raises(IndexError): + p[10] # py.test.raises(TypeError, ffi.new, "int[]") # p = ffi.new("int[]", [-6, -7]) # a list is all the items, like C assert p[0] == -6 assert p[1] == -7 - py.test.raises(IndexError, "p[2]") + with pytest.raises(IndexError): + p[2] assert repr(p) == "<cdata 'int[]' owning %d bytes>" % (2*SIZE_OF_INT) # p = ffi.new("int[]", 0) - py.test.raises(IndexError, "p[0]") + with pytest.raises(IndexError): + p[0] py.test.raises(ValueError, ffi.new, "int[]", -1) assert repr(p) == "<cdata 'int[]' owning 0 bytes>" @@ -324,7 +332,8 @@ class TestNewFFI1: p[2][3] = 33 assert p[0][0] == 10 assert p[2][3] == 33 - py.test.raises(IndexError, "p[1][-1]") + with pytest.raises(IndexError): + p[1][-1] def test_constructor_array_of_array(self): p = ffi.new("int[3][2]", [[10, 11], [12, 13], [14, 15]]) @@ -445,7 +454,8 @@ class TestNewFFI1: n = ffi.new("int*", 99) p = ffi.new("int*[]", [n]) assert p[0][0] == 99 - py.test.raises(TypeError, "p[0] = None") + with pytest.raises(TypeError): + p[0] = None p[0] = ffi.NULL assert p[0] == ffi.NULL @@ -478,13 +488,15 @@ class TestNewFFI1: assert s.a == s.b == s.c == 0 s.b = -23 assert s.b == -23 - py.test.raises(OverflowError, "s.b = 32768") + with pytest.raises(OverflowError): + s.b = 32768 # s = ffi.new("struct simple*", [-2, -3]) assert s.a == -2 assert s.b == -3 assert s.c == 0 - py.test.raises((AttributeError, TypeError), "del s.a") + with pytest.raises((AttributeError, TypeError)): + del s.a assert repr(s) == "<cdata 'struct simple *' owning %d bytes>" % ( SIZE_OF_INT + 2 * SIZE_OF_SHORT) # @@ -502,8 +514,10 @@ class TestNewFFI1: assert s[0].a == s[0].b == s[0].c == 0 s[0].b = -23 assert s[0].b == s.b == -23 - py.test.raises(OverflowError, "s[0].b = -32769") - py.test.raises(IndexError, "s[1]") + with pytest.raises(OverflowError): + s[0].b = -32769 + with pytest.raises(IndexError): + s[1] def test_struct_opaque(self): py.test.raises(ffi.error, ffi.new, "struct baz*") @@ -555,11 +569,13 @@ class TestNewFFI1: u.b = -23 assert u.b == -23 assert u.a != 0 - py.test.raises(OverflowError, "u.b = 32768") + with pytest.raises(OverflowError): + u.b = 32768 # u = ffi.new("union simple_u*", [-2]) assert u.a == -2 - py.test.raises((AttributeError, TypeError), "del u.a") + with pytest.raises((AttributeError, TypeError)): + del u.a assert repr(u) == "<cdata 'union simple_u *' owning %d bytes>" % ( SIZE_OF_INT,) @@ -625,7 +641,8 @@ class TestNewFFI1: p[3] = b'\x00' assert ffi.string(p) == b"hel" assert ffi.string(p, 2) == b"he" - py.test.raises(IndexError, "p[7] = b'X'") + with pytest.raises(IndexError): + p[7] = b'X' # a = ffi.new("char[]", b"hello\x00world") assert len(a) == 12 @@ -648,7 +665,8 @@ class TestNewFFI1: p[3] = u+'\x00' assert ffi.string(p) == u+"hel" assert ffi.string(p, 123) == u+"hel" - py.test.raises(IndexError, "p[7] = u+'X'") + with pytest.raises(IndexError): + p[7] = u+'X' # a = ffi.new("wchar_t[]", u+"hello\x00world") assert len(a) == 12 @@ -664,7 +682,8 @@ class TestNewFFI1: s = ffi.new("struct string*", [t]) assert type(s.name) not in (bytes, str, unicode) assert ffi.string(s.name) == b"testing" - py.test.raises(TypeError, "s.name = None") + with pytest.raises(TypeError): + s.name = None s.name = ffi.NULL assert s.name == ffi.NULL @@ -685,17 +704,20 @@ class TestNewFFI1: a = ffi.new("int[]", [10, 11, 12]) p = ffi.new("void **", a) vp = p[0] - py.test.raises(TypeError, "vp[0]") + with pytest.raises(TypeError): + vp[0] py.test.raises(TypeError, ffi.new, "short **", a) # s = ffi.new("struct voidp *") s.p = a # works s.q = a # works - py.test.raises(TypeError, "s.r = a") # fails + with pytest.raises(TypeError): + s.r = a # fails b = ffi.cast("int *", a) s.p = b # works s.q = b # works - py.test.raises(TypeError, "s.r = b") # fails + with pytest.raises(TypeError): + s.r = b # fails def test_functionptr_simple(self): py.test.raises(TypeError, ffi.callback, "int(*)(int)", 0) @@ -713,7 +735,8 @@ class TestNewFFI1: q = ffi.new("int(**)(int)", p) assert repr(q) == "<cdata 'int(* *)(int)' owning %d bytes>" % ( SIZE_OF_PTR) - py.test.raises(TypeError, "q(43)") + with pytest.raises(TypeError): + q(43) res = q[0](43) assert res == 44 q = ffi.cast("int(*)(int)", p) @@ -922,10 +945,14 @@ class TestNewFFI1: assert s.e in (4294967295, -1) # two choices assert s[0].e in (4294967295, -1) s.e = s.e - py.test.raises(TypeError, "s.e = 'B3'") - py.test.raises(TypeError, "s.e = '2'") - py.test.raises(TypeError, "s.e = '#2'") - py.test.raises(TypeError, "s.e = '#7'") + with pytest.raises(TypeError): + s.e = 'B3' + with pytest.raises(TypeError): + s.e = '2' + with pytest.raises(TypeError): + s.e = '#2' + with pytest.raises(TypeError): + s.e = '#7' def test_enum_non_contiguous(self): # enum noncont { A4, B4=42, C4 }; @@ -947,11 +974,14 @@ class TestNewFFI1: def test_array_of_struct(self): s = ffi.new("struct ab[1]") - py.test.raises(AttributeError, 's.b') - py.test.raises(AttributeError, 's.b = 412') + with pytest.raises(AttributeError): + s.b + with pytest.raises(AttributeError): + s.b = 412 s[0].b = 412 assert s[0].b == 412 - py.test.raises(IndexError, 's[1]') + with pytest.raises(IndexError): + s[1] def test_pointer_to_array(self): p = ffi.new("int(**)[5]") @@ -1000,17 +1030,23 @@ class TestNewFFI1: assert ffi.sizeof("struct bitfield") == 8 s = ffi.new("struct bitfield *") s.a = 511 - py.test.raises(OverflowError, "s.a = 512") - py.test.raises(OverflowError, "s[0].a = 512") + with pytest.raises(OverflowError): + s.a = 512 + with pytest.raises(OverflowError): + s[0].a = 512 assert s.a == 511 s.a = -512 - py.test.raises(OverflowError, "s.a = -513") - py.test.raises(OverflowError, "s[0].a = -513") + with pytest.raises(OverflowError): + s.a = -513 + with pytest.raises(OverflowError): + s[0].a = -513 assert s.a == -512 s.c = 3 assert s.c == 3 - py.test.raises(OverflowError, "s.c = 4") - py.test.raises(OverflowError, "s[0].c = 4") + with pytest.raises(OverflowError): + s.c = 4 + with pytest.raises(OverflowError): + s[0].c = 4 s.c = -4 assert s.c == -4 @@ -1184,7 +1220,7 @@ class TestNewFFI1: py.test.skip(str(e)) f.write(ffi.buffer(a, 1000 * ffi.sizeof("int"))) f.seek(0) - assert f.read() == array.array('i', range(1000)).tostring() + assert f.read() == arraytostring(array.array('i', range(1000))) f.seek(0) b = ffi.new("int[]", 1005) f.readinto(ffi.buffer(b, 1000 * ffi.sizeof("int"))) @@ -1202,7 +1238,7 @@ class TestNewFFI1: py.test.skip(str(e)) f.write(ffi.buffer(a, 1000 * ffi.sizeof("int"))) f.seek(0) - assert f.read() == array.array('i', range(1000)).tostring() + assert f.read() == arraytostring(array.array('i', range(1000))) f.seek(0) b = ffi.new("int[]", 1005) f.readinto(ffi.buffer(b, 1000 * ffi.sizeof("int"))) @@ -1235,7 +1271,8 @@ class TestNewFFI1: p = ffi.new("struct foo_s *", 10) # a single integer is the length assert p.len == 0 assert p.data[9] == 0 - py.test.raises(IndexError, "p.data[10]") + with pytest.raises(IndexError): + p.data[10] def test_ffi_typeof_getcname(self): assert ffi.getctype("int") == "int" @@ -1742,7 +1779,7 @@ class TestNewFFI1: def test_import_from_lib(self): ffi2 = cffi.FFI() - ffi2.cdef("int myfunc(int); int myvar;\n#define MYFOO ...\n") + ffi2.cdef("int myfunc(int); extern int myvar;\n#define MYFOO ...\n") outputfilename = recompile(ffi2, "_test_import_from_lib", "int myfunc(int x) { return x + 1; }\n" "int myvar = -5;\n" @@ -1752,7 +1789,8 @@ class TestNewFFI1: assert MYFOO == 42 assert myfunc(43) == 44 assert myvar == -5 # but can't be changed, so not very useful - py.test.raises(ImportError, "from _test_import_from_lib.lib import bar") + with pytest.raises(ImportError): + from _test_import_from_lib.lib import bar d = {} exec("from _test_import_from_lib.lib import *", d) assert (set(key for key in d if not key.startswith('_')) == diff --git a/testing/cffi1/test_re_python.py b/testing/cffi1/test_re_python.py index 377c29b..2ae0dd1 100644 --- a/testing/cffi1/test_re_python.py +++ b/testing/cffi1/test_re_python.py @@ -63,18 +63,20 @@ def setup_module(mod): #define BIGNEG -420000000000L int add42(int); int add43(int, ...); - int globalvar42; + extern int globalvar42; const int globalconst42; - const char *const globalconsthello = "hello"; + const char *const globalconsthello; int no_such_function(int); - int no_such_globalvar; + extern int no_such_globalvar; struct foo_s; typedef struct bar_s { int x; signed char a[]; } bar_t; enum foo_e { AA, BB, CC }; int strlen(const char *); struct with_union { union { int a; char b; }; }; union with_struct { struct { int a; char b; }; }; + struct with_struct_with_union { struct { union { int x; }; } cp; }; struct NVGcolor { union { float rgba[4]; struct { float r,g,b,a; }; }; }; + typedef struct selfref { struct selfref *next; } *selfref_ptr_t; """) ffi.set_source('re_python_pysrc', None) ffi.emit_python_code(str(tmpdir.join('re_python_pysrc.py'))) @@ -247,6 +249,10 @@ def test_anonymous_union_inside_struct(): assert ffi.offsetof("union with_struct", "b") == INT assert ffi.sizeof("union with_struct") >= INT + 1 # + assert ffi.sizeof("struct with_struct_with_union") == INT + p = ffi.new("struct with_struct_with_union *") + assert p.cp.x == 0 + # FLOAT = ffi.sizeof("float") assert ffi.sizeof("struct NVGcolor") == FLOAT * 4 assert ffi.offsetof("struct NVGcolor", "rgba") == 0 @@ -254,3 +260,29 @@ def test_anonymous_union_inside_struct(): assert ffi.offsetof("struct NVGcolor", "g") == FLOAT assert ffi.offsetof("struct NVGcolor", "b") == FLOAT * 2 assert ffi.offsetof("struct NVGcolor", "a") == FLOAT * 3 + +def test_selfref(): + # based on issue #429 + from re_python_pysrc import ffi + ffi.new("selfref_ptr_t") + +def test_dlopen_handle(): + import _cffi_backend + from re_python_pysrc import ffi + if sys.platform == 'win32': + py.test.skip("uses 'dl' explicitly") + ffi1 = FFI() + ffi1.cdef("""void *dlopen(const char *filename, int flags); + int dlclose(void *handle);""") + lib1 = ffi1.dlopen('dl') + handle = lib1.dlopen(extmod.encode(sys.getfilesystemencoding()), + _cffi_backend.RTLD_LAZY) + assert ffi1.typeof(handle) == ffi1.typeof("void *") + assert handle + + lib = ffi.dlopen(handle) + assert lib.add42(-10) == 32 + assert type(lib.add42) is _cffi_backend.FFI.CData + + err = lib1.dlclose(handle) + assert err == 0 diff --git a/testing/cffi1/test_recompiler.py b/testing/cffi1/test_recompiler.py index 6a31110..fdb4d5a 100644 --- a/testing/cffi1/test_recompiler.py +++ b/testing/cffi1/test_recompiler.py @@ -1,5 +1,6 @@ import sys, os, py +import pytest from cffi import FFI, VerificationError, FFIError, CDefError from cffi import recompiler from testing.udir import udir @@ -25,16 +26,21 @@ def check_type_table(input, expected_output, included=None): def verify(ffi, module_name, source, *args, **kwds): no_cpp = kwds.pop('no_cpp', False) + ignore_warnings = kwds.pop('ignore_warnings', False) kwds.setdefault('undef_macros', ['NDEBUG']) module_name = '_CFFI_' + module_name ffi.set_source(module_name, source) if not os.environ.get('NO_CPP') and not no_cpp: # test the .cpp mode too kwds.setdefault('source_extension', '.cpp') source = 'extern "C" {\n%s\n}' % (source,) - elif sys.platform != 'win32': + elif sys.platform != 'win32' and not ignore_warnings: # add '-Werror' to the existing 'extra_compile_args' flags + from testing.support import extra_compile_args kwds['extra_compile_args'] = (kwds.get('extra_compile_args', []) + - ['-Werror']) + extra_compile_args) + if sys.platform == 'darwin': + kwds['extra_link_args'] = (kwds.get('extra_link_args', []) + + ['-stdlib=libc++']) return _verify(ffi, module_name, source, *args, **kwds) def test_set_source_no_slashes(): @@ -82,7 +88,7 @@ def test_type_table_variadic_function(): "(FUNCTION 1)(PRIMITIVE 7)(FUNCTION_END 1)(POINTER 0)") def test_type_table_array(): - check_type_table("int a[100];", + check_type_table("extern int a[100];", "(PRIMITIVE 7)(ARRAY 0)(None 100)") def test_type_table_typedef(): @@ -134,7 +140,8 @@ def test_math_sin(): import math ffi = FFI() ffi.cdef("float sin(double); double cos(double);") - lib = verify(ffi, 'test_math_sin', '#include <math.h>') + lib = verify(ffi, 'test_math_sin', '#include <math.h>', + ignore_warnings=True) assert lib.cos(1.43) == math.cos(1.43) def test_repr_lib(): @@ -157,7 +164,7 @@ def test_funcres_ptr(): def test_global_var_array(): ffi = FFI() - ffi.cdef("int a[100];") + ffi.cdef("extern int a[100];") lib = verify(ffi, 'test_global_var_array', 'int a[100] = { 9999 };') lib.a[42] = 123456 assert lib.a[42] == 123456 @@ -181,27 +188,33 @@ def test_verify_typedef_star_dotdotdot(): def test_global_var_int(): ffi = FFI() - ffi.cdef("int a, b, c;") + ffi.cdef("extern int a, b, c;") lib = verify(ffi, 'test_global_var_int', 'int a = 999, b, c;') assert lib.a == 999 lib.a -= 1001 assert lib.a == -2 lib.a = -2147483648 assert lib.a == -2147483648 - py.test.raises(OverflowError, "lib.a = 2147483648") - py.test.raises(OverflowError, "lib.a = -2147483649") + with pytest.raises(OverflowError): + lib.a = 2147483648 + with pytest.raises(OverflowError): + lib.a = -2147483649 lib.b = 525 # try with the first access being in setattr, too assert lib.b == 525 - py.test.raises(AttributeError, "del lib.a") - py.test.raises(AttributeError, "del lib.c") - py.test.raises(AttributeError, "del lib.foobarbaz") + with pytest.raises(AttributeError): + del lib.a + with pytest.raises(AttributeError): + del lib.c + with pytest.raises(AttributeError): + del lib.foobarbaz def test_macro(): ffi = FFI() ffi.cdef("#define FOOBAR ...") lib = verify(ffi, 'test_macro', "#define FOOBAR (-6912)") assert lib.FOOBAR == -6912 - py.test.raises(AttributeError, "lib.FOOBAR = 2") + with pytest.raises(AttributeError): + lib.FOOBAR = 2 def test_macro_check_value(): # the value '-0x80000000' in C sources does not have a clear meaning @@ -247,7 +260,8 @@ def test_constant(): ffi.cdef("static const int FOOBAR;") lib = verify(ffi, 'test_constant', "#define FOOBAR (-6912)") assert lib.FOOBAR == -6912 - py.test.raises(AttributeError, "lib.FOOBAR = 2") + with pytest.raises(AttributeError): + lib.FOOBAR = 2 def test_check_value_of_static_const(): ffi = FFI() @@ -263,7 +277,8 @@ def test_constant_nonint(): ffi.cdef("static const double FOOBAR;") lib = verify(ffi, 'test_constant_nonint', "#define FOOBAR (-6912.5)") assert lib.FOOBAR == -6912.5 - py.test.raises(AttributeError, "lib.FOOBAR = 2") + with pytest.raises(AttributeError): + lib.FOOBAR = 2 def test_constant_ptr(): ffi = FFI() @@ -274,7 +289,7 @@ def test_constant_ptr(): def test_dir(): ffi = FFI() - ffi.cdef("int ff(int); int aa; static const int my_constant;") + ffi.cdef("int ff(int); extern int aa; static const int my_constant;") lib = verify(ffi, 'test_dir', """ #define my_constant (-45) int aa; @@ -315,8 +330,10 @@ def test_verify_struct(): p = ffi.new("struct foo_s *", {'a': -32768, 'b': -2147483648}) assert p.a == -32768 assert p.b == -2147483648 - py.test.raises(OverflowError, "p.a -= 1") - py.test.raises(OverflowError, "p.b -= 1") + with pytest.raises(OverflowError): + p.a -= 1 + with pytest.raises(OverflowError): + p.b -= 1 q = ffi.new("struct bar_s *", {'f': p}) assert q.f == p # @@ -336,9 +353,9 @@ def test_verify_exact_field_offset(): lib = verify(ffi, 'test_verify_exact_field_offset', """struct foo_s { short a; int b; };""") e = py.test.raises(ffi.error, ffi.new, "struct foo_s *", []) # lazily - assert str(e.value) == ("struct foo_s: wrong offset for field 'b' (cdef " - 'says 0, but C compiler says 4). fix it or use "...;" ' - "in the cdef for struct foo_s to make it flexible") + assert str(e.value).startswith( + "struct foo_s: wrong offset for field 'b' (cdef " + 'says 0, but C compiler says 4). fix it or use "...;" ') def test_type_caching(): ffi1 = FFI(); ffi1.cdef("struct foo_s;") @@ -387,19 +404,23 @@ def test_dotdotdot_length_of_array_field(): assert ffi.sizeof("struct foo_s") == (42 + 11) * 4 p = ffi.new("struct foo_s *") assert p.a[41] == p.b[10] == 0 - py.test.raises(IndexError, "p.a[42]") - py.test.raises(IndexError, "p.b[11]") + with pytest.raises(IndexError): + p.a[42] + with pytest.raises(IndexError): + p.b[11] def test_dotdotdot_global_array(): ffi = FFI() - ffi.cdef("int aa[...]; int bb[...];") + ffi.cdef("extern int aa[...]; extern int bb[...];") lib = verify(ffi, 'test_dotdotdot_global_array', "int aa[41]; int bb[12];") assert ffi.sizeof(lib.aa) == 41 * 4 assert ffi.sizeof(lib.bb) == 12 * 4 assert lib.aa[40] == lib.bb[11] == 0 - py.test.raises(IndexError, "lib.aa[41]") - py.test.raises(IndexError, "lib.bb[12]") + with pytest.raises(IndexError): + lib.aa[41] + with pytest.raises(IndexError): + lib.bb[12] def test_misdeclared_field_1(): ffi = FFI() @@ -545,37 +566,37 @@ def test_module_name_in_package(): def test_bad_size_of_global_1(): ffi = FFI() - ffi.cdef("short glob;") + ffi.cdef("extern short glob;") py.test.raises(VerificationError, verify, ffi, "test_bad_size_of_global_1", "long glob;") def test_bad_size_of_global_2(): ffi = FFI() - ffi.cdef("int glob[10];") + ffi.cdef("extern int glob[10];") py.test.raises(VerificationError, verify, ffi, "test_bad_size_of_global_2", "int glob[9];") def test_unspecified_size_of_global_1(): ffi = FFI() - ffi.cdef("int glob[];") + ffi.cdef("extern int glob[];") lib = verify(ffi, "test_unspecified_size_of_global_1", "int glob[10];") assert ffi.typeof(lib.glob) == ffi.typeof("int *") def test_unspecified_size_of_global_2(): ffi = FFI() - ffi.cdef("int glob[][5];") + ffi.cdef("extern int glob[][5];") lib = verify(ffi, "test_unspecified_size_of_global_2", "int glob[10][5];") assert ffi.typeof(lib.glob) == ffi.typeof("int(*)[5]") def test_unspecified_size_of_global_3(): ffi = FFI() - ffi.cdef("int glob[][...];") + ffi.cdef("extern int glob[][...];") lib = verify(ffi, "test_unspecified_size_of_global_3", "int glob[10][5];") assert ffi.typeof(lib.glob) == ffi.typeof("int(*)[5]") def test_unspecified_size_of_global_4(): ffi = FFI() - ffi.cdef("int glob[...][...];") + ffi.cdef("extern int glob[...][...];") lib = verify(ffi, "test_unspecified_size_of_global_4", "int glob[10][5];") assert ffi.typeof(lib.glob) == ffi.typeof("int[10][5]") @@ -629,7 +650,7 @@ def test_include_3(): ffi.cdef("sshort_t ff3(sshort_t);") lib = verify(ffi, "test_include_3", "typedef short sshort_t; //usually from a #include\n" - "sshort_t ff3(sshort_t x) { return x + 42; }") + "sshort_t ff3(sshort_t x) { return (sshort_t)(x + 42); }") assert lib.ff3(10) == 52 assert ffi.typeof(ffi.cast("sshort_t", 42)) is ffi.typeof("short") assert ffi1.typeof("sshort_t") is ffi.typeof("sshort_t") @@ -738,7 +759,7 @@ def test_unicode_libraries(): ffi = FFI() ffi.cdef(unicode("float sin(double); double cos(double);")) lib = verify(ffi, 'test_math_sin_unicode', unicode('#include <math.h>'), - libraries=[unicode(lib_m)]) + libraries=[unicode(lib_m)], ignore_warnings=True) assert lib.cos(1.43) == math.cos(1.43) def test_incomplete_struct_as_arg(): @@ -798,7 +819,7 @@ def test_name_of_unnamed_struct(): def test_address_of_global_var(): ffi = FFI() ffi.cdef(""" - long bottom, bottoms[2]; + extern long bottom, bottoms[2]; long FetchRectBottom(void); long FetchRectBottoms1(void); #define FOOBAR 42 @@ -866,15 +887,20 @@ def test_unpack_args(): e5 = py.test.raises(TypeError, lib.foo2) e6 = py.test.raises(TypeError, lib.foo2, 42) e7 = py.test.raises(TypeError, lib.foo2, 45, 46, 47) - assert str(e1.value) == "foo0() takes no arguments (1 given)" - assert str(e2.value) == "foo0() takes no arguments (2 given)" - assert str(e3.value) == "foo1() takes exactly one argument (0 given)" - assert str(e4.value) == "foo1() takes exactly one argument (2 given)" - assert str(e5.value) in ["foo2 expected 2 arguments, got 0", + def st1(s): + s = str(s) + if s.startswith("_CFFI_test_unpack_args.Lib."): + s = s[len("_CFFI_test_unpack_args.Lib."):] + return s + assert st1(e1.value) == "foo0() takes no arguments (1 given)" + assert st1(e2.value) == "foo0() takes no arguments (2 given)" + assert st1(e3.value) == "foo1() takes exactly one argument (0 given)" + assert st1(e4.value) == "foo1() takes exactly one argument (2 given)" + assert st1(e5.value) in ["foo2 expected 2 arguments, got 0", "foo2() takes exactly 2 arguments (0 given)"] - assert str(e6.value) in ["foo2 expected 2 arguments, got 1", + assert st1(e6.value) in ["foo2 expected 2 arguments, got 1", "foo2() takes exactly 2 arguments (1 given)"] - assert str(e7.value) in ["foo2 expected 2 arguments, got 3", + assert st1(e7.value) in ["foo2 expected 2 arguments, got 3", "foo2() takes exactly 2 arguments (3 given)"] def test_address_of_function(): @@ -882,7 +908,7 @@ def test_address_of_function(): ffi.cdef("long myfunc(long x);") lib = verify(ffi, "test_addressof_function", """ char myfunc(char x) { return (char)(x + 42); } - """) + """, ignore_warnings=True) assert lib.myfunc(5) == 47 assert lib.myfunc(0xABC05) == 47 assert not isinstance(lib.myfunc, ffi.CData) @@ -953,7 +979,7 @@ def test_variable_of_unknown_size(): ffi = FFI() ffi.cdef(""" typedef ... opaque_t; - opaque_t globvar; + extern opaque_t globvar; """) lib = verify(ffi, 'test_variable_of_unknown_size', """ typedef char opaque_t[6]; @@ -998,7 +1024,7 @@ def test_dotdot_in_source_file_names(): def test_call_with_incomplete_structs(): ffi = FFI() ffi.cdef("typedef struct {...;} foo_t; " - "foo_t myglob; " + "extern foo_t myglob; " "foo_t increment(foo_t s); " "double getx(foo_t s);") lib = verify(ffi, 'test_call_with_incomplete_structs', """ @@ -1020,8 +1046,10 @@ def test_struct_array_guess_length_2(): assert ffi.typeof(s.a) == ffi.typeof("int[5][8]") assert ffi.sizeof(s.a) == 40 * ffi.sizeof('int') assert s.a[4][7] == 0 - py.test.raises(IndexError, 's.a[4][8]') - py.test.raises(IndexError, 's.a[5][0]') + with pytest.raises(IndexError): + s.a[4][8] + with pytest.raises(IndexError): + s.a[5][0] assert ffi.typeof(s.a) == ffi.typeof("int[5][8]") assert ffi.typeof(s.a[0]) == ffi.typeof("int[8]") @@ -1034,38 +1062,44 @@ def test_struct_array_guess_length_3(): s = ffi.new("struct foo_s *") assert ffi.typeof(s.a) == ffi.typeof("int[][7]") assert s.a[4][6] == 0 - py.test.raises(IndexError, 's.a[4][7]') + with pytest.raises(IndexError): + s.a[4][7] assert ffi.typeof(s.a[0]) == ffi.typeof("int[7]") def test_global_var_array_2(): ffi = FFI() - ffi.cdef("int a[...][...];") + ffi.cdef("extern int a[...][...];") lib = verify(ffi, 'test_global_var_array_2', 'int a[10][8];') lib.a[9][7] = 123456 assert lib.a[9][7] == 123456 - py.test.raises(IndexError, 'lib.a[0][8]') - py.test.raises(IndexError, 'lib.a[10][0]') + with pytest.raises(IndexError): + lib.a[0][8] + with pytest.raises(IndexError): + lib.a[10][0] assert ffi.typeof(lib.a) == ffi.typeof("int[10][8]") assert ffi.typeof(lib.a[0]) == ffi.typeof("int[8]") def test_global_var_array_3(): ffi = FFI() - ffi.cdef("int a[][...];") + ffi.cdef("extern int a[][...];") lib = verify(ffi, 'test_global_var_array_3', 'int a[10][8];') lib.a[9][7] = 123456 assert lib.a[9][7] == 123456 - py.test.raises(IndexError, 'lib.a[0][8]') + with pytest.raises(IndexError): + lib.a[0][8] assert ffi.typeof(lib.a) == ffi.typeof("int(*)[8]") assert ffi.typeof(lib.a[0]) == ffi.typeof("int[8]") def test_global_var_array_4(): ffi = FFI() - ffi.cdef("int a[10][...];") + ffi.cdef("extern int a[10][...];") lib = verify(ffi, 'test_global_var_array_4', 'int a[10][8];') lib.a[9][7] = 123456 assert lib.a[9][7] == 123456 - py.test.raises(IndexError, 'lib.a[0][8]') - py.test.raises(IndexError, 'lib.a[10][8]') + with pytest.raises(IndexError): + lib.a[0][8] + with pytest.raises(IndexError): + lib.a[10][8] assert ffi.typeof(lib.a) == ffi.typeof("int[10][8]") assert ffi.typeof(lib.a[0]) == ffi.typeof("int[8]") @@ -1147,7 +1181,7 @@ def test_some_float_invalid_3(): lib = verify(ffi, 'test_some_float_invalid_3', """ typedef long double foo_t; foo_t neg(foo_t x) { return -x; } - """) + """, ignore_warnings=True) if ffi.sizeof("long double") == ffi.sizeof("double"): assert lib.neg(12.3) == -12.3 else: @@ -1181,7 +1215,7 @@ def test_alignment_of_longlong(): def test_import_from_lib(): ffi = FFI() - ffi.cdef("int mybar(int); int myvar;\n#define MYFOO ...") + ffi.cdef("int mybar(int); static int myvar;\n#define MYFOO ...") lib = verify(ffi, 'test_import_from_lib', "#define MYFOO 42\n" "static int mybar(int x) { return x + 1; }\n" @@ -1197,7 +1231,7 @@ def test_import_from_lib(): def test_macro_var_callback(): ffi = FFI() - ffi.cdef("int my_value; int *(*get_my_value)(void);") + ffi.cdef("extern int my_value; extern int *(*get_my_value)(void);") lib = verify(ffi, 'test_macro_var_callback', "int *(*get_my_value)(void);\n" "#define my_value (*get_my_value())") @@ -1312,7 +1346,7 @@ def test_const_function_args(): def test_const_function_type_args(): ffi = FFI() - ffi.cdef("""int (*foobar)(const int a, const int *b, const int c[]);""") + ffi.cdef("""extern int(*foobar)(const int a,const int*b,const int c[]);""") lib = verify(ffi, 'test_const_function_type_args', """ int (*foobar)(const int a, const int *b, const int c[]); """) @@ -1338,7 +1372,8 @@ def test_const_via_typedef(): #define aaa 42 """) assert lib.aaa == 42 - py.test.raises(AttributeError, "lib.aaa = 43") + with pytest.raises(AttributeError): + lib.aaa = 43 def test_win32_calling_convention_0(): ffi = FFI() @@ -1601,7 +1636,7 @@ def test_extern_python_1(): def test_extern_python_bogus_name(): ffi = FFI() - ffi.cdef("int abc;") + ffi.cdef("extern int abc;") lib = verify(ffi, 'test_extern_python_bogus_name', "int abc;") def fn(): pass @@ -1634,9 +1669,10 @@ def test_extern_python_bogus_result_type(): with StdErrCapture() as f: res = lib.bar(321) assert res is None - assert f.getvalue() == ( - "From cffi callback %r:\n" % (bar,) + - "Trying to convert the result back to C:\n" + msg = f.getvalue() + assert "rom cffi callback %r" % (bar,) in msg + assert "rying to convert the result back to C:\n" in msg + assert msg.endswith( "TypeError: callback with the return type 'void' must return None\n") def test_extern_python_redefine(): @@ -1762,8 +1798,8 @@ def test_extern_python_stdcall(): ffi.cdef(""" extern "Python" int __stdcall foo(int); extern "Python" int WINAPI bar(int); - int (__stdcall * mycb1)(int); - int indirect_call(int); + static int (__stdcall * mycb1)(int); + static int indirect_call(int); """) lib = verify(ffi, 'test_extern_python_stdcall', """ #ifndef _MSC_VER @@ -1820,7 +1856,7 @@ def test_introspect_function(): ffi = FFI() ffi.cdef("float f1(double);") lib = verify(ffi, 'test_introspect_function', """ - float f1(double x) { return x; } + float f1(double x) { return (float)x; } """) assert dir(lib) == ['f1'] FUNC = ffi.typeof(lib.f1) @@ -1831,7 +1867,7 @@ def test_introspect_function(): def test_introspect_global_var(): ffi = FFI() - ffi.cdef("float g1;") + ffi.cdef("extern float g1;") lib = verify(ffi, 'test_introspect_global_var', """ float g1; """) @@ -1842,7 +1878,7 @@ def test_introspect_global_var(): def test_introspect_global_var_array(): ffi = FFI() - ffi.cdef("float g1[100];") + ffi.cdef("extern float g1[100];") lib = verify(ffi, 'test_introspect_global_var_array', """ float g1[100]; """) @@ -2014,7 +2050,7 @@ def test_function_returns_float_complex(): ffi.cdef("float _Complex f1(float a, float b);"); lib = verify(ffi, "test_function_returns_float_complex", """ #include <complex.h> - static float _Complex f1(float a, float b) { return a + I*2.0*b; } + static float _Complex f1(float a, float b) { return a + I*2.0f*b; } """, no_cpp=True) # <complex.h> fails on some systems with C++ result = lib.f1(1.25, 5.1) assert type(result) == complex @@ -2065,7 +2101,7 @@ def test_typedef_array_dotdotdot(): ffi = FFI() ffi.cdef(""" typedef int foo_t[...], bar_t[...]; - int gv[...]; + extern int gv[...]; typedef int mat_t[...][...]; typedef int vmat_t[][...]; """) @@ -2087,6 +2123,40 @@ def test_typedef_array_dotdotdot(): p = ffi.new("vmat_t", 4) assert ffi.sizeof(p[3]) == 8 * ffi.sizeof("int") +def test_typedef_array_dotdotdot_usage(): + ffi = FFI() + ffi.cdef(""" + typedef int foo_t[...]; + typedef int mat_t[...][...]; + struct s { foo_t a; foo_t *b; foo_t **c; }; + int myfunc(foo_t a, foo_t *b, foo_t **c); + struct sm { mat_t a; mat_t *b; mat_t **c; }; + int myfuncm(mat_t a, mat_t *b, mat_t **c); + """) + lib = verify(ffi, "test_typedef_array_dotdotdot_usage", """ + typedef int foo_t[50]; + typedef int mat_t[6][7]; + struct s { foo_t a; foo_t *b; foo_t **c; }; + static int myfunc(foo_t a, foo_t *b, foo_t **c) { return (**c)[49]; } + struct sm { mat_t a; mat_t *b; mat_t **c; }; + static int myfuncm(mat_t a, mat_t *b, mat_t **c) { return (**c)[5][6]; } + """) + assert ffi.sizeof("foo_t") == 50 * ffi.sizeof("int") + p = ffi.new("struct s *") + assert ffi.sizeof(p[0]) == 50 * ffi.sizeof("int") + 2 * ffi.sizeof("void *") + p.a[49] = 321 + p.b = ffi.addressof(p, 'a') + p.c = ffi.addressof(p, 'b') + assert lib.myfunc(ffi.NULL, ffi.NULL, p.c) == 321 + # + assert ffi.sizeof("mat_t") == 42 * ffi.sizeof("int") + p = ffi.new("struct sm *") + assert ffi.sizeof(p[0]) == 42 * ffi.sizeof("int") + 2 * ffi.sizeof("void *") + p.a[5][6] = -321 + p.b = ffi.addressof(p, 'a') + p.c = ffi.addressof(p, 'b') + assert lib.myfuncm(ffi.NULL, ffi.NULL, p.c) == -321 + def test_call_with_custom_field_pos(): ffi = FFI() ffi.cdef(""" @@ -2125,7 +2195,8 @@ def test_call_with_nested_anonymous_struct(): lib = verify(ffi, "test_call_with_nested_anonymous_struct", """ struct foo { int a; union { int b, c; }; }; struct foo f(void) { - struct foo s = { 40 }; + struct foo s; + s.a = 40; s.b = 200; return s; } @@ -2314,3 +2385,111 @@ def test_realize_struct_error(): typedef int foo_t; struct foo_s { void (*x)(foo_t); }; """) py.test.raises(TypeError, ffi.new, "struct foo_s *") + +def test_from_buffer_struct(): + ffi = FFI() + ffi.cdef("""struct foo_s { int a, b; };""") + lib = verify(ffi, "test_from_buffer_struct_p", """ + struct foo_s { int a, b; }; + """) + p = ffi.new("struct foo_s *", [-219239, 58974983]) + q = ffi.from_buffer("struct foo_s[]", ffi.buffer(p)) + assert ffi.typeof(q) == ffi.typeof("struct foo_s[]") + assert len(q) == 1 + assert q[0].a == p.a + assert q[0].b == p.b + assert q == p + q = ffi.from_buffer("struct foo_s *", ffi.buffer(p)) + assert ffi.typeof(q) == ffi.typeof("struct foo_s *") + assert q.a == p.a + assert q.b == p.b + assert q[0].a == p.a + assert q[0].b == p.b + assert q == p + +def test_unnamed_bitfield_1(): + ffi = FFI() + ffi.cdef("""struct A { char : 1; };""") + lib = verify(ffi, "test_unnamed_bitfield_1", """ + struct A { char : 1; }; + """) + p = ffi.new("struct A *") + assert ffi.sizeof(p[0]) == 1 + # Note: on gcc, the type name is ignored for anonymous bitfields + # and that's why the result is 1. On MSVC, the result is + # sizeof("char") which is also 1. + +def test_unnamed_bitfield_2(): + ffi = FFI() + ffi.cdef("""struct A { + short c : 1; short : 1; short d : 1; short : 1; };""") + lib = verify(ffi, "test_unnamed_bitfield_2", """ + struct A { + short c : 1; short : 1; short d : 1; short : 1; + }; + """) + p = ffi.new("struct A *") + assert ffi.sizeof(p[0]) == ffi.sizeof("short") + +def test_unnamed_bitfield_3(): + ffi = FFI() + ffi.cdef("""struct A { struct { char : 1; char : 1; } b; };""") + lib = verify(ffi, "test_unnamed_bitfield_3", """ + struct A { struct { char : 1; char : 1; } b; }; + """) + p = ffi.new("struct A *") + assert ffi.sizeof(p[0]) == 1 + # Note: on gcc, the type name is ignored for anonymous bitfields + # and that's why the result is 1. On MSVC, the result is + # sizeof("char") which is also 1. + +def test_unnamed_bitfield_4(): + ffi = FFI() + ffi.cdef("""struct A { struct { + unsigned c : 1; unsigned : 1; unsigned d : 1; unsigned : 1; } a; + }; + struct B { struct A a; };""") + lib = verify(ffi, "test_unnamed_bitfield_4", """ + struct A { struct { + unsigned c : 1; unsigned : 1; unsigned d : 1; unsigned : 1; } a; + }; + struct B { struct A a; }; + """) + b = ffi.new("struct B *") + a = ffi.new("struct A *") + assert ffi.sizeof(a[0]) == ffi.sizeof("unsigned") + assert ffi.sizeof(b[0]) == ffi.sizeof(a[0]) + +def test_struct_with_func_with_struct_pointer_arg(): + ffi = FFI() + ffi.cdef("""struct BinaryTree { + int (* CompareKey)(struct BinaryTree *tree); + };""") + lib = verify(ffi, "test_struct_with_func_with_struct_pointer_arg", """ + struct BinaryTree { + int (* CompareKey)(struct BinaryTree *tree); + }; + """) + ffi.new("struct BinaryTree *") + +def test_struct_with_func_with_struct_arg(): + ffi = FFI() + ffi.cdef("""struct BinaryTree { + int (* CompareKey)(struct BinaryTree tree); + };""") + lib = verify(ffi, "test_struct_with_func_with_struct_arg", """ + struct BinaryTree { + int (* CompareKey)(struct BinaryTree tree); + }; + """) + py.test.raises(RuntimeError, ffi.new, "struct BinaryTree *") + +def test_passing_large_list(): + ffi = FFI() + ffi.cdef("""void passing_large_list(long[]);""") + lib = verify(ffi, "test_passing_large_list", """ + static void passing_large_list(long a[]) { } + """) + arg = list(range(20000000)) + lib.passing_large_list(arg) + # assert did not segfault diff --git a/testing/cffi1/test_verify1.py b/testing/cffi1/test_verify1.py index 75f113d..33244cc 100644 --- a/testing/cffi1/test_verify1.py +++ b/testing/cffi1/test_verify1.py @@ -1,9 +1,10 @@ import os, sys, math, py +import pytest from cffi import FFI, FFIError, VerificationError, VerificationMissing, model from cffi import CDefError from cffi import recompiler from testing.support import * -from testing.support import _verify +from testing.support import _verify, extra_compile_args import _cffi_backend lib_m = ['m'] @@ -12,17 +13,6 @@ if sys.platform == 'win32': import distutils.ccompiler if distutils.ccompiler.get_default_compiler() == 'msvc': lib_m = ['msvcrt'] - extra_compile_args = [] # no obvious -Werror equivalent on MSVC -else: - if (sys.platform == 'darwin' and - [int(x) for x in os.uname()[2].split('.')] >= [11, 0, 0]): - # assume a standard clang or gcc - extra_compile_args = ['-Werror', '-Wall', '-Wextra', '-Wconversion'] - # special things for clang - extra_compile_args.append('-Qunused-arguments') - else: - # assume a standard gcc - extra_compile_args = ['-Werror', '-Wall', '-Wextra', '-Wconversion'] class FFI(FFI): error = _cffi_backend.FFI.error @@ -265,7 +255,7 @@ def test_all_integer_and_float_types(): def test_var_signed_integer_types(): ffi = FFI() lst = all_signed_integer_types(ffi) - csource = "\n".join(["%s somevar_%s;" % (tp, tp.replace(' ', '_')) + csource = "\n".join(["static %s somevar_%s;" % (tp, tp.replace(' ', '_')) for tp in lst]) ffi.cdef(csource) lib = ffi.verify(csource) @@ -284,7 +274,7 @@ def test_var_signed_integer_types(): def test_var_unsigned_integer_types(): ffi = FFI() lst = all_unsigned_integer_types(ffi) - csource = "\n".join(["%s somevar_%s;" % (tp, tp.replace(' ', '_')) + csource = "\n".join(["static %s somevar_%s;" % (tp, tp.replace(' ', '_')) for tp in lst]) ffi.cdef(csource) lib = ffi.verify(csource) @@ -571,7 +561,8 @@ def test_struct_array_guess_length(): assert ffi.sizeof('struct foo_s') == 19 * ffi.sizeof('int') s = ffi.new("struct foo_s *") assert ffi.sizeof(s.a) == 17 * ffi.sizeof('int') - py.test.raises(IndexError, 's.a[17]') + with pytest.raises(IndexError): + s.a[17] def test_struct_array_c99_1(): if sys.platform == 'win32': @@ -629,7 +620,8 @@ def test_struct_with_bitfield_exact(): ffi.verify("struct foo_s { int a:2, b:3; };") s = ffi.new("struct foo_s *") s.b = 3 - py.test.raises(OverflowError, "s.b = 4") + with pytest.raises(OverflowError): + s.b = 4 assert s.b == 3 def test_struct_with_bitfield_enum(): @@ -786,8 +778,8 @@ def test_define_int(): def test_access_variable(): ffi = FFI() - ffi.cdef("int foo(void);\n" - "int somenumber;") + ffi.cdef("static int foo(void);\n" + "static int somenumber;") lib = ffi.verify(""" static int somenumber = 2; static int foo(void) { @@ -804,7 +796,7 @@ def test_access_variable(): def test_access_address_of_variable(): # access the address of 'somenumber': need a trick ffi = FFI() - ffi.cdef("int somenumber; static int *const somenumberptr;") + ffi.cdef("static int somenumber; static int *const somenumberptr;") lib = ffi.verify(""" static int somenumber = 2; #define somenumberptr (&somenumber) @@ -816,8 +808,8 @@ def test_access_address_of_variable(): def test_access_array_variable(length=5): ffi = FFI() - ffi.cdef("int foo(int);\n" - "int somenumber[%s];" % (length,)) + ffi.cdef("static int foo(int);\n" + "static int somenumber[%s];" % (length,)) lib = ffi.verify(""" static int somenumber[] = {2, 2, 3, 4, 5}; static int foo(int i) { @@ -848,8 +840,8 @@ def test_access_array_variable_length_hidden(): def test_access_struct_variable(): ffi = FFI() ffi.cdef("struct foo { int x; ...; };\n" - "int foo(int);\n" - "struct foo stuff;") + "static int foo(int);\n" + "static struct foo stuff;") lib = ffi.verify(""" struct foo { int x, y, z; }; static struct foo stuff = {2, 5, 8}; @@ -873,9 +865,9 @@ def test_access_struct_variable(): def test_access_callback(): ffi = FFI() - ffi.cdef("int (*cb)(int);\n" - "int foo(int);\n" - "void reset_cb(void);") + ffi.cdef("static int (*cb)(int);\n" + "static int foo(int);\n" + "static void reset_cb(void);") lib = ffi.verify(""" static int g(int x) { return x * 7; } static int (*cb)(int); @@ -891,9 +883,9 @@ def test_access_callback(): def test_access_callback_function_typedef(): ffi = FFI() ffi.cdef("typedef int mycallback_t(int);\n" - "mycallback_t *cb;\n" - "int foo(int);\n" - "void reset_cb(void);") + "static mycallback_t *cb;\n" + "static int foo(int);\n" + "static void reset_cb(void);") lib = ffi.verify(""" static int g(int x) { return x * 7; } static int (*cb)(int); @@ -1034,7 +1026,7 @@ def test_autofilled_struct_as_argument(): def test_autofilled_struct_as_argument_dynamic(): ffi = FFI() ffi.cdef("struct foo_s { long a; ...; };\n" - "int (*foo)(struct foo_s);") + "static int (*foo)(struct foo_s);") lib = ffi.verify(""" struct foo_s { double b; @@ -1043,7 +1035,7 @@ def test_autofilled_struct_as_argument_dynamic(): int foo1(struct foo_s s) { return (int)s.a - (int)s.b; } - int (*foo)(struct foo_s s) = &foo1; + static int (*foo)(struct foo_s s) = &foo1; """) e = py.test.raises(NotImplementedError, lib.foo, "?") msg = ("ctype 'struct foo_s' not supported as argument. It is a struct " @@ -1419,7 +1411,7 @@ def test_bool(): py.test.skip("_Bool not in MSVC") ffi = FFI() ffi.cdef("struct foo_s { _Bool x; };" - "_Bool foo(_Bool); _Bool (*foop)(_Bool);") + "_Bool foo(_Bool); static _Bool (*foop)(_Bool);") lib = ffi.verify(""" struct foo_s { _Bool x; }; int foo(int arg) { @@ -1428,13 +1420,15 @@ def test_bool(): _Bool _foofunc(_Bool x) { return !x; } - _Bool (*foop)(_Bool) = _foofunc; + static _Bool (*foop)(_Bool) = _foofunc; """) p = ffi.new("struct foo_s *") p.x = 1 assert p.x is True - py.test.raises(OverflowError, "p.x = -1") - py.test.raises(TypeError, "p.x = 0.0") + with pytest.raises(OverflowError): + p.x = -1 + with pytest.raises(TypeError): + p.x = 0.0 assert lib.foop(1) is False assert lib.foop(True) is False assert lib.foop(0) is True @@ -1502,7 +1496,8 @@ def test_cannot_pass_float(): } """ % (type, type)) p = ffi.new("struct foo_s *") - py.test.raises(TypeError, "p.x = 0.0") + with pytest.raises(TypeError): + p.x = 0.0 assert lib.foo(42) == 0 assert lib.foo(0) == 1 py.test.raises(TypeError, lib.foo, 0.0) @@ -1610,7 +1605,7 @@ def test_FILE_stored_in_stdout(): def test_FILE_stored_explicitly(): ffi = FFI() - ffi.cdef("int myprintf11(const char *, int); FILE *myfile;") + ffi.cdef("int myprintf11(const char *, int); extern FILE *myfile;") lib = ffi.verify(""" #include <stdio.h> FILE *myfile; @@ -1636,13 +1631,13 @@ def test_FILE_stored_explicitly(): def test_global_array_with_missing_length(): ffi = FFI() - ffi.cdef("int fooarray[];") + ffi.cdef("extern int fooarray[];") lib = ffi.verify("int fooarray[50];") assert repr(lib.fooarray).startswith("<cdata 'int *'") def test_global_array_with_dotdotdot_length(): ffi = FFI() - ffi.cdef("int fooarray[...];") + ffi.cdef("extern int fooarray[...];") lib = ffi.verify("int fooarray[50];") assert repr(lib.fooarray).startswith("<cdata 'int[50]'") @@ -1650,7 +1645,7 @@ def test_bad_global_array_with_dotdotdot_length(): py.test.xfail("was detected only because 23 bytes cannot be divided by 4; " "redo more generally") ffi = FFI() - ffi.cdef("int fooarray[...];") + ffi.cdef("extern int fooarray[...];") py.test.raises(VerificationError, ffi.verify, "char fooarray[23];") def test_struct_containing_struct(): @@ -1771,7 +1766,7 @@ def test_string_to_voidp_arg(): def test_callback_indirection(): ffi = FFI() ffi.cdef(""" - int (*python_callback)(int how_many, int *values); + static int (*python_callback)(int how_many, int *values); int (*const c_callback)(int,...); /* pass this ptr to C routines */ int some_c_function(int(*cb)(int,...)); """) @@ -1905,23 +1900,23 @@ def test_typeof_func_with_struct_argument(): def test_bug_const_char_ptr_array_1(): ffi = FFI() - ffi.cdef("""const char *a[...];""") + ffi.cdef("""extern const char *a[...];""") lib = ffi.verify("""const char *a[5];""") assert repr(ffi.typeof(lib.a)) == "<ctype 'char *[5]'>" def test_bug_const_char_ptr_array_2(): ffi = FFI() - ffi.cdef("""const int a[];""") + ffi.cdef("""extern const int a[];""") lib = ffi.verify("""const int a[5];""") assert repr(ffi.typeof(lib.a)) == "<ctype 'int *'>" def _test_various_calls(force_libffi): cdef_source = """ - int xvalue; - long long ivalue, rvalue; - float fvalue; - double dvalue; - long double Dvalue; + extern int xvalue; + extern long long ivalue, rvalue; + extern float fvalue; + extern double dvalue; + extern long double Dvalue; signed char tf_bb(signed char x, signed char c); unsigned char tf_bB(signed char x, unsigned char c); short tf_bh(signed char x, short c); @@ -2104,7 +2099,7 @@ def test_verify_dlopen_flags(): old = sys.getdlopenflags() try: ffi1 = FFI() - ffi1.cdef("int foo_verify_dlopen_flags_1;") + ffi1.cdef("extern int foo_verify_dlopen_flags_1;") sys.setdlopenflags(ffi1.RTLD_GLOBAL | ffi1.RTLD_NOW) lib1 = ffi1.verify("int foo_verify_dlopen_flags_1;") finally: @@ -2193,7 +2188,8 @@ def test_define_wrong_value(): ffi = FFI() ffi.cdef("#define FOO 123") lib = ffi.verify("#define FOO 124") # used to complain - e = py.test.raises(ffi.error, "lib.FOO") + with pytest.raises(ffi.error) as e: + lib.FOO assert str(e.value) == ("the C compiler says 'FOO' is equal to 124 (0x7c)," " but the cdef disagrees") @@ -2244,7 +2240,7 @@ def test_windows_dllimport_data(): def test_macro_var(): ffi = FFI() - ffi.cdef("int myarray[50], my_value;") + ffi.cdef("extern int myarray[50], my_value;") lib = ffi.verify(""" int myarray[50]; int *get_my_value(void) { diff --git a/testing/embedding/add1.py b/testing/embedding/add1.py index e5b3de1..6f89ae9 100644 --- a/testing/embedding/add1.py +++ b/testing/embedding/add1.py @@ -11,7 +11,11 @@ ffi.embedding_init_code(r""" sys.stdout.write("preparing") for i in range(3): sys.stdout.flush() - time.sleep(0.2) + # Windows: sometimes time.sleep() doesn't sleep at all. + # This appears to occur on recent versions of python only. + t_end = time.time() + 0.19 + while time.time() < t_end: + time.sleep(0.2) sys.stdout.write(".") sys.stdout.write("\n") diff --git a/testing/embedding/add_recursive.py b/testing/embedding/add_recursive.py index 9fa463d..a88aa8f 100644 --- a/testing/embedding/add_recursive.py +++ b/testing/embedding/add_recursive.py @@ -3,7 +3,7 @@ import cffi ffi = cffi.FFI() ffi.embedding_api(""" - int (*my_callback)(int); + extern int (*my_callback)(int); int add_rec(int, int); """) diff --git a/testing/embedding/test_basic.py b/testing/embedding/test_basic.py index 8463c3f..8d2e776 100644 --- a/testing/embedding/test_basic.py +++ b/testing/embedding/test_basic.py @@ -63,8 +63,8 @@ class EmbeddingTests: output = popen.stdout.read() err = popen.wait() if err: - raise OSError("popen failed with exit code %r: %r" % ( - err, args)) + raise OSError(("popen failed with exit code %r: %r\n\n%s" % ( + err, args, output)).rstrip()) print(output.rstrip()) return output @@ -172,7 +172,8 @@ if sys.platform == 'win32': result = popen.stdout.read() err = popen.wait() if err: - raise OSError("%r failed with exit code %r" % (name, err)) + raise OSError("%r failed with exit code %r" % ( + os.path.join(path, executable_name), err)) return result @@ -205,3 +206,9 @@ class TestBasic(EmbeddingTests): self.compile('add1-test', [initerror_cffi]) output = self.execute('add1-test') assert output == "got: 0 0\n" # plus lots of info to stderr + + def test_embedding_with_unicode(self): + withunicode_cffi = self.prepare_module('withunicode') + self.compile('add1-test', [withunicode_cffi]) + output = self.execute('add1-test') + assert output == "255\n4660\n65244\ngot: 0 0\n" diff --git a/testing/embedding/test_performance.py b/testing/embedding/test_performance.py index f9f2605..a0e8458 100644 --- a/testing/embedding/test_performance.py +++ b/testing/embedding/test_performance.py @@ -2,8 +2,8 @@ import sys from testing.embedding.test_basic import EmbeddingTests if sys.platform == 'win32': - import py - py.test.skip("written with POSIX functions") + import pytest + pytestmark = pytest.mark.skip("written with POSIX functions") class TestPerformance(EmbeddingTests): diff --git a/testing/embedding/test_thread.py b/testing/embedding/test_thread.py index 1895076..9a5936d 100644 --- a/testing/embedding/test_thread.py +++ b/testing/embedding/test_thread.py @@ -21,17 +21,21 @@ class TestThread(EmbeddingTests): add1_cffi = self.prepare_module('add1') add2_cffi = self.prepare_module('add2') self.compile('thread2-test', [add1_cffi, add2_cffi], threads=True) - output = self.execute('thread2-test') - output = self._take_out(output, "preparing") - output = self._take_out(output, ".") - output = self._take_out(output, ".") - # at least the 3rd dot should be after everything from ADD2 - assert output == ("starting\n" - "prepADD2\n" - "adding 1000 and 200 and 30\n" - ".\n" - "adding 40 and 2\n" - "done\n") + for i in range(3): + output = self.execute('thread2-test') + print('='*79) + print(output) + print('='*79) + output = self._take_out(output, "preparing") + output = self._take_out(output, ".") + output = self._take_out(output, ".") + # at least the 3rd dot should be after everything from ADD2 + assert output == ("starting\n" + "prepADD2\n" + "adding 1000 and 200 and 30\n" + ".\n" + "adding 40 and 2\n" + "done\n") def test_alt_issue(self): add1_cffi = self.prepare_module('add1') diff --git a/testing/embedding/withunicode.py b/testing/embedding/withunicode.py new file mode 100644 index 0000000..839c6cd --- /dev/null +++ b/testing/embedding/withunicode.py @@ -0,0 +1,26 @@ +import sys, cffi +if sys.version_info < (3,): + u_prefix = "u" +else: + u_prefix = "" + unichr = chr + + +ffi = cffi.FFI() + +ffi.embedding_api(u""" + int add1(int, int); +""") + +ffi.embedding_init_code((""" + import sys, time + for c in %s'""" + unichr(0x00ff) + unichr(0x1234) + unichr(0xfedc) + """': + sys.stdout.write(str(ord(c)) + '\\n') + sys.stdout.flush() +""") % u_prefix) + +ffi.set_source("_withunicode_cffi", """ +""") + +fn = ffi.compile(verbose=True) +print('FILENAME: %s' % (fn,)) diff --git a/testing/support.py b/testing/support.py index 65f010c..6339a94 100644 --- a/testing/support.py +++ b/testing/support.py @@ -1,7 +1,7 @@ -import sys +import sys, os if sys.version_info < (3,): - __all__ = ['u'] + __all__ = ['u', 'arraytostring'] class U(object): def __add__(self, other): @@ -12,12 +12,16 @@ if sys.version_info < (3,): assert u+'a\x00b' == eval(r"u'a\x00b'") assert u+'a\u1234b' == eval(r"u'a\u1234b'") assert u+'a\U00012345b' == eval(r"u'a\U00012345b'") + def arraytostring(a): + return a.tostring() else: - __all__ = ['u', 'unicode', 'long'] + __all__ = ['u', 'unicode', 'long', 'arraytostring'] u = "" unicode = str long = int + def arraytostring(a): + return a.tobytes() class StdErrCapture(object): @@ -29,9 +33,14 @@ class StdErrCapture(object): from io import StringIO self.old_stderr = sys.stderr sys.stderr = f = StringIO() + if hasattr(sys, '__unraisablehook__'): # work around pytest + self.old_unraisablebook = sys.unraisablehook # on recent CPythons + sys.unraisablehook = sys.__unraisablehook__ return f def __exit__(self, *args): sys.stderr = self.old_stderr + if hasattr(self, 'old_unraisablebook'): + sys.unraisablehook = self.old_unraisablebook class FdWriteCapture(object): @@ -86,3 +95,25 @@ def _verify(ffi, module_name, preamble, *args, **kwds): if not name.startswith('_') and not hasattr(module.ffi, name): setattr(ffi, name, NotImplemented) return module.lib + + +# For testing, we call gcc with "-Werror". This is fragile because newer +# versions of gcc are always better at producing warnings, particularly for +# auto-generated code. We need here to adapt and silence them as needed. + +if sys.platform == 'win32': + extra_compile_args = [] # no obvious -Werror equivalent on MSVC +else: + if (sys.platform == 'darwin' and + [int(x) for x in os.uname()[2].split('.')] >= [11, 0, 0]): + # assume a standard clang or gcc + extra_compile_args = ['-Werror', '-Wall', '-Wextra', '-Wconversion', + '-Wno-unused-parameter', + '-Wno-unreachable-code'] + # special things for clang + extra_compile_args.append('-Qunused-arguments') + else: + # assume a standard gcc + extra_compile_args = ['-Werror', '-Wall', '-Wextra', '-Wconversion', + '-Wno-unused-parameter', + '-Wno-unreachable-code'] diff --git a/testing/udir.py b/testing/udir.py index 4dd0a11..59db1c4 100644 --- a/testing/udir.py +++ b/testing/udir.py @@ -1,7 +1,134 @@ import py -import sys +import sys, os, atexit -udir = py.path.local.make_numbered_dir(prefix = 'ffi-') + +# This is copied from PyPy's vendored py lib. The latest py lib release +# (1.8.1) contains a bug and crashes if it sees another temporary directory +# in which we don't have write permission (e.g. because it's owned by someone +# else). +def make_numbered_dir(prefix='session-', rootdir=None, keep=3, + lock_timeout = 172800, # two days + min_timeout = 300): # five minutes + """ return unique directory with a number greater than the current + maximum one. The number is assumed to start directly after prefix. + if keep is true directories with a number less than (maxnum-keep) + will be removed. + """ + if rootdir is None: + rootdir = py.path.local.get_temproot() + + def parse_num(path): + """ parse the number out of a path (if it matches the prefix) """ + bn = path.basename + if bn.startswith(prefix): + try: + return int(bn[len(prefix):]) + except ValueError: + pass + + # compute the maximum number currently in use with the + # prefix + lastmax = None + while True: + maxnum = -1 + for path in rootdir.listdir(): + num = parse_num(path) + if num is not None: + maxnum = max(maxnum, num) + + # make the new directory + try: + udir = rootdir.mkdir(prefix + str(maxnum+1)) + except py.error.EEXIST: + # race condition: another thread/process created the dir + # in the meantime. Try counting again + if lastmax == maxnum: + raise + lastmax = maxnum + continue + break + + # put a .lock file in the new directory that will be removed at + # process exit + if lock_timeout: + lockfile = udir.join('.lock') + mypid = os.getpid() + if hasattr(lockfile, 'mksymlinkto'): + lockfile.mksymlinkto(str(mypid)) + else: + lockfile.write(str(mypid)) + def try_remove_lockfile(): + # in a fork() situation, only the last process should + # remove the .lock, otherwise the other processes run the + # risk of seeing their temporary dir disappear. For now + # we remove the .lock in the parent only (i.e. we assume + # that the children finish before the parent). + if os.getpid() != mypid: + return + try: + lockfile.remove() + except py.error.Error: + pass + atexit.register(try_remove_lockfile) + + # prune old directories + if keep: + for path in rootdir.listdir(): + num = parse_num(path) + if num is not None and num <= (maxnum - keep): + if min_timeout: + # NB: doing this is needed to prevent (or reduce + # a lot the chance of) the following situation: + # 'keep+1' processes call make_numbered_dir() at + # the same time, they create dirs, but then the + # last process notices the first dir doesn't have + # (yet) a .lock in it and kills it. + try: + t1 = path.lstat().mtime + t2 = lockfile.lstat().mtime + if abs(t2-t1) < min_timeout: + continue # skip directories too recent + except py.error.Error: + continue # failure to get a time, better skip + lf = path.join('.lock') + try: + t1 = lf.lstat().mtime + t2 = lockfile.lstat().mtime + if not lock_timeout or abs(t2-t1) < lock_timeout: + continue # skip directories still locked + except py.error.Error: + pass # assume that it means that there is no 'lf' + try: + path.remove(rec=1) + except KeyboardInterrupt: + raise + except: # this might be py.error.Error, WindowsError ... + pass + + # make link... + try: + username = os.environ['USER'] #linux, et al + except KeyError: + try: + username = os.environ['USERNAME'] #windows + except KeyError: + username = 'current' + + src = str(udir) + dest = src[:src.rfind('-')] + '-' + username + try: + os.unlink(dest) + except OSError: + pass + try: + os.symlink(src, dest) + except (OSError, AttributeError, NotImplementedError): + pass + + return udir + + +udir = make_numbered_dir(prefix = 'ffi-') # Windows-only workaround for some configurations: see |