You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
469 lines
16 KiB
469 lines
16 KiB
import py, sys, re
|
|
from cffi import FFI, FFIError, CDefError, VerificationError
|
|
from .backend_tests import needs_dlopen_none
|
|
|
|
|
|
class FakeBackend(object):
|
|
|
|
def nonstandard_integer_types(self):
|
|
return {}
|
|
|
|
def sizeof(self, name):
|
|
return 1
|
|
|
|
def load_library(self, name, flags):
|
|
if sys.platform == 'win32':
|
|
assert name is None or "msvcr" in name
|
|
else:
|
|
assert name is None or "libc" in name or "libm" in name
|
|
return FakeLibrary()
|
|
|
|
def new_function_type(self, args, result, has_varargs):
|
|
args = [arg.cdecl for arg in args]
|
|
result = result.cdecl
|
|
return FakeType(
|
|
'<func (%s), %s, %s>' % (', '.join(args), result, has_varargs))
|
|
|
|
def new_primitive_type(self, name):
|
|
assert name == name.lower()
|
|
return FakeType('<%s>' % name)
|
|
|
|
def new_pointer_type(self, itemtype):
|
|
return FakeType('<pointer to %s>' % (itemtype,))
|
|
|
|
def new_struct_type(self, name):
|
|
return FakeStruct(name)
|
|
|
|
def complete_struct_or_union(self, s, fields, tp=None,
|
|
totalsize=-1, totalalignment=-1, sflags=0):
|
|
assert isinstance(s, FakeStruct)
|
|
s.fields = fields
|
|
|
|
def new_array_type(self, ptrtype, length):
|
|
return FakeType('<array %s x %s>' % (ptrtype, length))
|
|
|
|
def new_void_type(self):
|
|
return FakeType("<void>")
|
|
def cast(self, x, y):
|
|
return 'casted!'
|
|
def _get_types(self):
|
|
return "CData", "CType"
|
|
|
|
buffer = "buffer type"
|
|
|
|
class FakeType(object):
|
|
def __init__(self, cdecl):
|
|
self.cdecl = cdecl
|
|
def __str__(self):
|
|
return self.cdecl
|
|
|
|
class FakeStruct(object):
|
|
def __init__(self, name):
|
|
self.name = name
|
|
def __str__(self):
|
|
return ', '.join([str(y) + str(x) for x, y, z in self.fields])
|
|
|
|
class FakeLibrary(object):
|
|
|
|
def load_function(self, BType, name):
|
|
return FakeFunction(BType, name)
|
|
|
|
class FakeFunction(object):
|
|
|
|
def __init__(self, BType, name):
|
|
self.BType = str(BType)
|
|
self.name = name
|
|
|
|
lib_m = "m"
|
|
if sys.platform == 'win32':
|
|
#there is a small chance this fails on Mingw via environ $CC
|
|
import distutils.ccompiler
|
|
if distutils.ccompiler.get_default_compiler() == 'msvc':
|
|
lib_m = 'msvcrt'
|
|
|
|
def test_simple():
|
|
ffi = FFI(backend=FakeBackend())
|
|
ffi.cdef("double sin(double x);")
|
|
m = ffi.dlopen(lib_m)
|
|
func = m.sin # should be a callable on real backends
|
|
assert func.name == 'sin'
|
|
assert func.BType == '<func (<double>), <double>, False>'
|
|
|
|
def test_pipe():
|
|
ffi = FFI(backend=FakeBackend())
|
|
ffi.cdef("int pipe(int pipefd[2]);")
|
|
needs_dlopen_none()
|
|
C = ffi.dlopen(None)
|
|
func = C.pipe
|
|
assert func.name == 'pipe'
|
|
assert func.BType == '<func (<pointer to <int>>), <int>, False>'
|
|
|
|
def test_vararg():
|
|
ffi = FFI(backend=FakeBackend())
|
|
ffi.cdef("short foo(int, ...);")
|
|
needs_dlopen_none()
|
|
C = ffi.dlopen(None)
|
|
func = C.foo
|
|
assert func.name == 'foo'
|
|
assert func.BType == '<func (<int>), <short>, True>'
|
|
|
|
def test_no_args():
|
|
ffi = FFI(backend=FakeBackend())
|
|
ffi.cdef("""
|
|
int foo(void);
|
|
""")
|
|
needs_dlopen_none()
|
|
C = ffi.dlopen(None)
|
|
assert C.foo.BType == '<func (), <int>, False>'
|
|
|
|
def test_typedef():
|
|
ffi = FFI(backend=FakeBackend())
|
|
ffi.cdef("""
|
|
typedef unsigned int UInt;
|
|
typedef UInt UIntReally;
|
|
UInt foo(void);
|
|
""")
|
|
needs_dlopen_none()
|
|
C = ffi.dlopen(None)
|
|
assert str(ffi.typeof("UIntReally")) == '<unsigned int>'
|
|
assert C.foo.BType == '<func (), <unsigned int>, False>'
|
|
|
|
def test_typedef_more_complex():
|
|
ffi = FFI(backend=FakeBackend())
|
|
ffi.cdef("""
|
|
typedef struct { int a, b; } foo_t, *foo_p;
|
|
int foo(foo_p[]);
|
|
""")
|
|
needs_dlopen_none()
|
|
C = ffi.dlopen(None)
|
|
assert str(ffi.typeof("foo_t")) == '<int>a, <int>b'
|
|
assert str(ffi.typeof("foo_p")) == '<pointer to <int>a, <int>b>'
|
|
assert C.foo.BType == ('<func (<pointer to <pointer to '
|
|
'<int>a, <int>b>>), <int>, False>')
|
|
|
|
def test_typedef_array_convert_array_to_pointer():
|
|
ffi = FFI(backend=FakeBackend())
|
|
ffi.cdef("""
|
|
typedef int (*fn_t)(int[5]);
|
|
""")
|
|
with ffi._lock:
|
|
type = ffi._parser.parse_type("fn_t")
|
|
BType = ffi._get_cached_btype(type)
|
|
assert str(BType) == '<func (<pointer to <int>>), <int>, False>'
|
|
|
|
def test_remove_comments():
|
|
ffi = FFI(backend=FakeBackend())
|
|
ffi.cdef("""
|
|
double /*comment here*/ sin // blah blah
|
|
/* multi-
|
|
line-
|
|
//comment */ (
|
|
// foo
|
|
double // bar /* <- ignored, because it's in a comment itself
|
|
x, double/*several*//*comment*/y) /*on the same line*/
|
|
;
|
|
""")
|
|
m = ffi.dlopen(lib_m)
|
|
func = m.sin
|
|
assert func.name == 'sin'
|
|
assert func.BType == '<func (<double>, <double>), <double>, False>'
|
|
|
|
def test_remove_line_continuation_comments():
|
|
ffi = FFI(backend=FakeBackend())
|
|
ffi.cdef("""
|
|
double // blah \\
|
|
more comments
|
|
x(void);
|
|
double // blah\\\\
|
|
y(void);
|
|
double // blah\\ \
|
|
etc
|
|
z(void);
|
|
""")
|
|
m = ffi.dlopen(lib_m)
|
|
m.x
|
|
m.y
|
|
m.z
|
|
|
|
def test_line_continuation_in_defines():
|
|
ffi = FFI(backend=FakeBackend())
|
|
ffi.cdef("""
|
|
#define ABC\\
|
|
42
|
|
#define BCD \\
|
|
43
|
|
""")
|
|
m = ffi.dlopen(lib_m)
|
|
assert m.ABC == 42
|
|
assert m.BCD == 43
|
|
|
|
def test_define_not_supported_for_now():
|
|
ffi = FFI(backend=FakeBackend())
|
|
e = py.test.raises(CDefError, ffi.cdef, '#define FOO "blah"')
|
|
assert str(e.value) == (
|
|
'only supports one of the following syntax:\n'
|
|
' #define FOO ... (literally dot-dot-dot)\n'
|
|
' #define FOO NUMBER (with NUMBER an integer'
|
|
' constant, decimal/hex/octal)\n'
|
|
'got:\n'
|
|
' #define FOO "blah"')
|
|
|
|
def test_unnamed_struct():
|
|
ffi = FFI(backend=FakeBackend())
|
|
ffi.cdef("typedef struct { int x; } foo_t;\n"
|
|
"typedef struct { int y; } *bar_p;\n")
|
|
assert 'typedef foo_t' in ffi._parser._declarations
|
|
assert 'typedef bar_p' in ffi._parser._declarations
|
|
assert 'anonymous foo_t' in ffi._parser._declarations
|
|
type_foo = ffi._parser.parse_type("foo_t")
|
|
type_bar = ffi._parser.parse_type("bar_p").totype
|
|
assert repr(type_foo) == "<foo_t>"
|
|
assert repr(type_bar) == "<struct $1>"
|
|
py.test.raises(VerificationError, type_bar.get_c_name)
|
|
assert type_foo.get_c_name() == "foo_t"
|
|
|
|
def test_override():
|
|
ffi = FFI(backend=FakeBackend())
|
|
needs_dlopen_none()
|
|
C = ffi.dlopen(None)
|
|
ffi.cdef("int foo(void);")
|
|
py.test.raises(FFIError, ffi.cdef, "long foo(void);")
|
|
assert C.foo.BType == '<func (), <int>, False>'
|
|
ffi.cdef("long foo(void);", override=True)
|
|
assert C.foo.BType == '<func (), <long>, False>'
|
|
|
|
def test_cannot_have_only_variadic_part():
|
|
# this checks that we get a sensible error if we try "int foo(...);"
|
|
ffi = FFI()
|
|
e = py.test.raises(CDefError, ffi.cdef, "int foo(...);")
|
|
assert str(e.value) == (
|
|
"<cdef source string>:1: foo: a function with only '(...)' "
|
|
"as argument is not correct C")
|
|
|
|
def test_parse_error():
|
|
ffi = FFI()
|
|
e = py.test.raises(CDefError, ffi.cdef, " x y z ")
|
|
assert str(e.value).startswith(
|
|
'cannot parse "x y z"\n<cdef source string>:1:')
|
|
e = py.test.raises(CDefError, ffi.cdef, "\n\n\n x y z ")
|
|
assert str(e.value).startswith(
|
|
'cannot parse "x y z"\n<cdef source string>:4:')
|
|
|
|
def test_error_custom_lineno():
|
|
ffi = FFI()
|
|
e = py.test.raises(CDefError, ffi.cdef, """
|
|
# 42 "foobar"
|
|
|
|
a b c d
|
|
""")
|
|
assert str(e.value).startswith('parse error\nfoobar:43:')
|
|
|
|
def test_cannot_declare_enum_later():
|
|
ffi = FFI()
|
|
e = py.test.raises(NotImplementedError, ffi.cdef,
|
|
"typedef enum foo_e foo_t; enum foo_e { AA, BB };")
|
|
assert str(e.value) == (
|
|
"enum foo_e: the '{}' declaration should appear on the "
|
|
"first time the enum is mentioned, not later")
|
|
|
|
def test_unknown_name():
|
|
ffi = FFI()
|
|
e = py.test.raises(CDefError, ffi.cast, "foobarbazunknown", 0)
|
|
assert str(e.value) == "unknown identifier 'foobarbazunknown'"
|
|
e = py.test.raises(CDefError, ffi.cast, "foobarbazunknown*", 0)
|
|
assert str(e.value).startswith('cannot parse "foobarbazunknown*"')
|
|
e = py.test.raises(CDefError, ffi.cast, "int(*)(foobarbazunknown)", 0)
|
|
assert str(e.value).startswith('cannot parse "int(*)(foobarbazunknown)"')
|
|
|
|
def test_redefine_common_type():
|
|
prefix = "" if sys.version_info < (3,) else "b"
|
|
ffi = FFI()
|
|
ffi.cdef("typedef char FILE;")
|
|
assert repr(ffi.cast("FILE", 123)) == "<cdata 'char' %s'{'>" % prefix
|
|
ffi.cdef("typedef char int32_t;")
|
|
assert repr(ffi.cast("int32_t", 123)) == "<cdata 'char' %s'{'>" % prefix
|
|
ffi = FFI()
|
|
ffi.cdef("typedef int bool, *FILE;")
|
|
assert repr(ffi.cast("bool", 123)) == "<cdata 'int' 123>"
|
|
assert re.match(r"<cdata 'int [*]' 0[xX]?0*7[bB]>",
|
|
repr(ffi.cast("FILE", 123)))
|
|
ffi = FFI()
|
|
ffi.cdef("typedef bool (*fn_t)(bool, bool);") # "bool," but within "( )"
|
|
|
|
def test_bool():
|
|
ffi = FFI()
|
|
ffi.cdef("void f(bool);")
|
|
#
|
|
ffi = FFI()
|
|
ffi.cdef("typedef _Bool bool; void f(bool);")
|
|
|
|
def test_unknown_argument_type():
|
|
ffi = FFI()
|
|
e = py.test.raises(CDefError, ffi.cdef, "void f(foobarbazzz);")
|
|
assert str(e.value) == ("<cdef source string>:1: f arg 1:"
|
|
" unknown type 'foobarbazzz' (if you meant"
|
|
" to use the old C syntax of giving untyped"
|
|
" arguments, it is not supported)")
|
|
|
|
def test_void_renamed_as_only_arg():
|
|
ffi = FFI()
|
|
ffi.cdef("typedef void void_t1;"
|
|
"typedef void_t1 void_t;"
|
|
"typedef int (*func_t)(void_t);")
|
|
assert ffi.typeof("func_t").args == ()
|
|
|
|
def test_WPARAM_on_windows():
|
|
if sys.platform != 'win32':
|
|
py.test.skip("Only for Windows")
|
|
ffi = FFI()
|
|
ffi.cdef("void f(WPARAM);")
|
|
#
|
|
# WPARAM -> UINT_PTR -> unsigned 32/64-bit integer
|
|
ffi = FFI()
|
|
value = int(ffi.cast("WPARAM", -42))
|
|
assert value == sys.maxsize * 2 - 40
|
|
|
|
def test__is_constant_globalvar():
|
|
for input, expected_output in [
|
|
("int a;", False),
|
|
("const int a;", True),
|
|
("int *a;", False),
|
|
("const int *a;", False),
|
|
("int const *a;", False),
|
|
("int *const a;", True),
|
|
("int a[5];", False),
|
|
("const int a[5];", False),
|
|
("int *a[5];", False),
|
|
("const int *a[5];", False),
|
|
("int const *a[5];", False),
|
|
("int *const a[5];", False),
|
|
("int a[5][6];", False),
|
|
("const int a[5][6];", False),
|
|
]:
|
|
ffi = FFI()
|
|
ffi.cdef(input)
|
|
declarations = ffi._parser._declarations
|
|
assert ('constant a' in declarations) == expected_output
|
|
assert ('variable a' in declarations) == (not expected_output)
|
|
|
|
def test_restrict():
|
|
from cffi import model
|
|
for input, expected_output in [
|
|
("int a;", False),
|
|
("restrict int a;", True),
|
|
("int *a;", False),
|
|
]:
|
|
ffi = FFI()
|
|
ffi.cdef(input)
|
|
tp, quals = ffi._parser._declarations['variable a']
|
|
assert bool(quals & model.Q_RESTRICT) == expected_output
|
|
|
|
def test_different_const_funcptr_types():
|
|
lst = []
|
|
for input in [
|
|
"int(*)(int *a)",
|
|
"int(*)(int const *a)",
|
|
"int(*)(int * const a)",
|
|
"int(*)(int const a[])"]:
|
|
ffi = FFI(backend=FakeBackend())
|
|
lst.append(ffi._parser.parse_type(input))
|
|
assert lst[0] != lst[1]
|
|
assert lst[0] == lst[2]
|
|
assert lst[1] == lst[3]
|
|
|
|
def test_const_pointer_to_pointer():
|
|
from cffi import model
|
|
ffi = FFI(backend=FakeBackend())
|
|
#
|
|
tp, qual = ffi._parser.parse_type_and_quals("char * * (* const)")
|
|
assert (str(tp), qual) == ("<char * * *>", model.Q_CONST)
|
|
tp, qual = ffi._parser.parse_type_and_quals("char * (* const (*))")
|
|
assert (str(tp), qual) == ("<char * * const *>", 0)
|
|
tp, qual = ffi._parser.parse_type_and_quals("char (* const (* (*)))")
|
|
assert (str(tp), qual) == ("<char * const * *>", 0)
|
|
tp, qual = ffi._parser.parse_type_and_quals("char const * * *")
|
|
assert (str(tp), qual) == ("<char const * * *>", 0)
|
|
tp, qual = ffi._parser.parse_type_and_quals("const char * * *")
|
|
assert (str(tp), qual) == ("<char const * * *>", 0)
|
|
#
|
|
tp, qual = ffi._parser.parse_type_and_quals("char * * * const const")
|
|
assert (str(tp), qual) == ("<char * * *>", model.Q_CONST)
|
|
tp, qual = ffi._parser.parse_type_and_quals("char * * volatile *")
|
|
assert (str(tp), qual) == ("<char * * volatile *>", 0)
|
|
tp, qual = ffi._parser.parse_type_and_quals("char * volatile restrict * *")
|
|
assert (str(tp), qual) == ("<char * __restrict volatile * *>", 0)
|
|
tp, qual = ffi._parser.parse_type_and_quals("char const volatile * * *")
|
|
assert (str(tp), qual) == ("<char volatile const * * *>", 0)
|
|
tp, qual = ffi._parser.parse_type_and_quals("const char * * *")
|
|
assert (str(tp), qual) == ("<char const * * *>", 0)
|
|
#
|
|
tp, qual = ffi._parser.parse_type_and_quals(
|
|
"int(char*const*, short****const*)")
|
|
assert (str(tp), qual) == (
|
|
"<int()(char * const *, short * * * * const *)>", 0)
|
|
tp, qual = ffi._parser.parse_type_and_quals(
|
|
"char*const*(short*const****)")
|
|
assert (str(tp), qual) == (
|
|
"<char * const *()(short * const * * * *)>", 0)
|
|
|
|
def test_enum():
|
|
ffi = FFI()
|
|
ffi.cdef("""
|
|
enum Enum { POS = +1, TWO = 2, NIL = 0, NEG = -1, OP = (POS+TWO)-1};
|
|
""")
|
|
needs_dlopen_none()
|
|
C = ffi.dlopen(None)
|
|
assert C.POS == 1
|
|
assert C.TWO == 2
|
|
assert C.NIL == 0
|
|
assert C.NEG == -1
|
|
assert C.OP == 2
|
|
|
|
def test_stdcall():
|
|
ffi = FFI()
|
|
tp = ffi.typeof("int(*)(int __stdcall x(int),"
|
|
" long (__cdecl*y)(void),"
|
|
" short(WINAPI *z)(short))")
|
|
if sys.platform == 'win32' and sys.maxsize < 2**32:
|
|
stdcall = '__stdcall '
|
|
else:
|
|
stdcall = ''
|
|
assert str(tp) == (
|
|
"<ctype 'int(*)(int(%s*)(int), "
|
|
"long(*)(), "
|
|
"short(%s*)(short))'>" % (stdcall, stdcall))
|
|
|
|
def test_extern_python():
|
|
ffi = FFI()
|
|
ffi.cdef("""
|
|
int bok(int, int);
|
|
extern "Python" int foobar(int, int);
|
|
int baz(int, int);
|
|
""")
|
|
assert sorted(ffi._parser._declarations) == [
|
|
'extern_python foobar', 'function baz', 'function bok']
|
|
assert (ffi._parser._declarations['function bok'] ==
|
|
ffi._parser._declarations['extern_python foobar'] ==
|
|
ffi._parser._declarations['function baz'])
|
|
|
|
def test_extern_python_group():
|
|
ffi = FFI()
|
|
ffi.cdef("""
|
|
int bok(int);
|
|
extern "Python" {int foobar(int, int);int bzrrr(int);}
|
|
int baz(int, int);
|
|
""")
|
|
assert sorted(ffi._parser._declarations) == [
|
|
'extern_python bzrrr', 'extern_python foobar',
|
|
'function baz', 'function bok']
|
|
assert (ffi._parser._declarations['function baz'] ==
|
|
ffi._parser._declarations['extern_python foobar'] !=
|
|
ffi._parser._declarations['function bok'] ==
|
|
ffi._parser._declarations['extern_python bzrrr'])
|
|
|
|
def test_error_invalid_syntax_for_cdef():
|
|
ffi = FFI()
|
|
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()')
|