diff options
Diffstat (limited to 'tests/run/ext_attr_getter.srctree')
-rw-r--r-- | tests/run/ext_attr_getter.srctree | 305 |
1 files changed, 282 insertions, 23 deletions
diff --git a/tests/run/ext_attr_getter.srctree b/tests/run/ext_attr_getter.srctree index 6fdf7503c..76f059ed7 100644 --- a/tests/run/ext_attr_getter.srctree +++ b/tests/run/ext_attr_getter.srctree @@ -1,17 +1,60 @@ +# mode: run +# tag: cgetter, property + +""" PYTHON setup.py build_ext --inplace -PYTHON -c "import runner" +PYTHON run_failure_tests.py +PYTHON runner.py +""" ######## setup.py ######## from Cython.Build.Dependencies import cythonize from distutils.core import setup -# force the build order -setup(ext_modules= cythonize("foo_extension.pyx")) +# Enforce the right build order +setup(ext_modules = cythonize("foo_extension.pyx", language_level=3)) +setup(ext_modules = cythonize("getter[0-9].pyx", language_level=3)) + + +######## run_failure_tests.py ######## + +import glob +import sys + +from Cython.Build.Dependencies import cythonize +from Cython.Compiler.Errors import CompileError + +# Run the failure tests +failed_tests = [] +passed_tests = [] + +def run_test(name): + title = name + with open(name, 'r') as f: + for line in f: + if 'TEST' in line: + title = line.partition('TEST:')[2].strip() + break + sys.stderr.write("\n### TESTING: %s\n" % title) + + try: + cythonize(name, language_level=3) + except CompileError as e: + sys.stderr.write("\nOK: got expected exception\n") + passed_tests.append(name) + else: + sys.stderr.write("\nFAIL: compilation did not detect the error\n") + failed_tests.append(name) -setup(ext_modules = cythonize("getter*.pyx")) +for name in sorted(glob.glob("getter_fail*.pyx")): + run_test(name) -######## foo_nominal.h ######## +assert not failed_tests, "Failed tests: %s" % failed_tests +assert passed_tests # check that tests were found at all + + +######## foo.h ######## #include <Python.h> @@ -24,30 +67,77 @@ typedef struct { int f0; int f1; int f2; + int v[10]; } FooStructNominal; +typedef struct { + PyObject_HEAD +} FooStructOpaque; + + +#define PyFoo_GET0M(a) (((FooStructNominal*)a)->f0) +#define PyFoo_GET1M(a) (((FooStructNominal*)a)->f1) +#define PyFoo_GET2M(a) (((FooStructNominal*)a)->f2) + +int PyFoo_Get0F(FooStructOpaque *f) +{ + return PyFoo_GET0M(f); +} + +int PyFoo_Get1F(FooStructOpaque *f) +{ + return PyFoo_GET1M(f); +} + +int PyFoo_Get2F(FooStructOpaque *f) +{ + return PyFoo_GET2M(f); +} + +int *PyFoo_GetV(FooStructOpaque *f) +{ + return ((FooStructNominal*)f)->v; +} + #ifdef __cplusplus } #endif + ######## foo_extension.pyx ######## cdef class Foo: - cdef public int field0, field1, field2; - - def __init__(self, f0, f1, f2): - self.field0 = f0 - self.field1 = f1 - self.field2 = f2 + cdef public int _field0, _field1, _field2; + cdef public int _vector[10]; -cdef get_field0(Foo f): - return f.field0 + @property + def field0(self): + return self._field0 -cdef get_field1(Foo f): - return f.field1 + @property + def field1(self): + return self._field1 -cdef get_field2(Foo f): - return f.field2 + @property + def field2(self): + return self._field2 + + def __init__(self, f0, f1, f2, vec=None): + if vec is None: + vec = () + if not isinstance(vec, tuple): + raise ValueError("v must be None or a tuple") + self._field0 = f0 + self._field1 = f1 + self._field2 = f2 + i = 0 + for v in vec: + self._vector[i] = v + if i > 9: + break + i += 1 + for j in range(i,10): + self._vector[j] = 0 # A pure-python class that disallows direct access to fields class OpaqueFoo(Foo): @@ -69,7 +159,7 @@ class OpaqueFoo(Foo): # Access base Foo fields from C via aliased field names -cdef extern from "foo_nominal.h": +cdef extern from "foo.h": ctypedef class foo_extension.Foo [object FooStructNominal]: cdef: @@ -78,13 +168,164 @@ cdef extern from "foo_nominal.h": int field2 "f2" def sum(Foo f): - # the f.__getattr__('field0') is replaced in c by f->f0 + # Note - not a cdef function but compiling the f.__getattr__('field0') + # notices the alias and replaces the __getattr__ in c by f->f0 anyway return f.field0 + f.field1 + f.field2 +def check_pyobj(Foo f): + # compare the c code to the check_pyobj in getter2.pyx + return bool(f.field1) + + +######## getter.pxd ######## + +# Access base Foo fields from C via getter functions + + +cdef extern from "foo.h": + ctypedef class foo_extension.Foo [object FooStructOpaque, check_size ignore]: + @property + cdef inline int fieldM0(self): + return PyFoo_GET0M(self) + + @property + cdef inline int fieldF1(self) except -123: + return PyFoo_Get1F(self) + + @property + cdef inline int fieldM2(self): + return PyFoo_GET2M(self) + + @property + cdef inline int *vector(self): + return PyFoo_GetV(self) + + @property + cdef inline int meaning_of_life(self) except -99: + cdef int ret = 21 + ret *= 2 + return ret + + int PyFoo_GET0M(Foo); # this is actually a macro ! + int PyFoo_Get1F(Foo); + int PyFoo_GET2M(Foo); # this is actually a macro ! + int *PyFoo_GetV(Foo); + + +######## getter1.pyx ######## + +cimport getter + +def sum(getter.Foo f): + # Note - not a cdef function but compiling the f.__getattr__('field0') + # notices the getter and replaces the __getattr__ in c by PyFoo_GET anyway + return f.fieldM0 + f.fieldF1 + f.fieldM2 + +def check_10(getter.Foo f): + return f.fieldF1 != 10 + +def vec0(getter.Foo f): + return f.vector[0] + +def check_binop(getter.Foo f): + return f.fieldF1 / 10 + + +######## getter2.pyx ######## + +cimport getter + +def check_pyobj(getter.Foo f): + return bool(f.fieldF1) + +def check_unary(getter.Foo f): + return -f.fieldF1 + +def check_meaning_of_life(getter.Foo f): + return f.meaning_of_life + + +######## getter_fail_classmethod.pyx ######## + +# TEST: Make sure not all decorators are accepted. + +cdef extern from "foo.h": + ctypedef class foo_extension.Foo [object FooStructOpaque]: + @property + @classmethod + cdef inline int field0(cls): + print('in classmethod of Foo') + + +######## getter_fail_dot_getter.pyx ######## + +# TEST: Make sure not all decorators are accepted. + +cdef extern from "foo.h": + ctypedef class foo_extension.Foo [object FooStructOpaque]: + @property + cdef inline int field0(self): + pass + + @field0.getter + cdef inline void field1(self): + pass + + +######## getter_fail_no_inline.pyx ######## + +# TEST: Properties must be declared "inline". + +cdef extern from "foo.h": + ctypedef class foo_extension.Foo [object FooStructOpaque]: + @property + cdef int field0(self): + pass + + +######## getter_fail_void.pyx ######## + +# TEST: Properties must have a non-void return type. + +cdef extern from "foo.h": + ctypedef class foo_extension.Foo [object FooStructOpaque]: + @property + cdef void field0(self): + pass + + +######## getter_fail_no_args.pyx ######## + +# TEST: Properties must have the right signature. + +cdef extern from "foo.h": + ctypedef class foo_extension.Foo [object FooStructOpaque]: + @property + cdef int field0(): + pass + + +######## getter_fail_too_many_args.pyx ######## + +# TEST: Properties must have the right signature. + +cdef extern from "foo.h": + ctypedef class foo_extension.Foo [object FooStructOpaque]: + @property + cdef int field0(x, y): + pass + + ######## runner.py ######## -import foo_extension, getter0 +import warnings +import foo_extension, getter0, getter1, getter2 +def sum(f): + # pure python field access, but code is identical to cython cdef sum + return f.field0 + f.field1 + f.field2 + +# Baseline test: if this fails something else is wrong foo = foo_extension.Foo(23, 123, 1023) assert foo.field0 == 23 @@ -92,18 +333,36 @@ assert foo.field1 == 123 assert foo.field2 == 1023 ret = getter0.sum(foo) -assert ret == foo.field0 + foo.field1 + foo.field2 +assert ret == sum(foo) + +# Aliasing test. Check 'cdef int field0 "f0" works as advertised: +# - C can access the fields through the aliases +# - Python cannot access the fields at all opaque_foo = foo_extension.OpaqueFoo(23, 123, 1023) -# C can access the fields through the aliases opaque_ret = getter0.sum(opaque_foo) assert opaque_ret == ret + +val = getter2.check_pyobj(opaque_foo) +assert val is True +val = getter2.check_unary(opaque_foo) +assert val == -123 +val = getter2.check_meaning_of_life(opaque_foo) +assert val == 42 + try: - # Python cannot access the fields f0 = opaque_ret.field0 assert False except AttributeError as e: pass +# Getter test. Check C-level getter works as advertised: +# - C accesses the fields through getter calls (maybe macros) +# - Python accesses the fields through attribute lookup + +opaque_foo = foo_extension.OpaqueFoo(23, 123, 1023, (1, 2, 3)) + +opaque_ret = getter1.sum(opaque_foo) +assert opaque_ret == ret |