summaryrefslogtreecommitdiff
path: root/tests/run/ext_attr_getter.srctree
diff options
context:
space:
mode:
Diffstat (limited to 'tests/run/ext_attr_getter.srctree')
-rw-r--r--tests/run/ext_attr_getter.srctree305
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