diff options
Diffstat (limited to 'tests/memoryview')
-rw-r--r-- | tests/memoryview/cfunc_convert_with_memoryview.pyx | 51 | ||||
-rw-r--r-- | tests/memoryview/cythonarray.pyx | 115 | ||||
-rw-r--r-- | tests/memoryview/error_declarations.pyx | 18 | ||||
-rw-r--r-- | tests/memoryview/memoryview.pyx | 179 | ||||
-rw-r--r-- | tests/memoryview/memoryview_acq_count.srctree | 2 | ||||
-rw-r--r-- | tests/memoryview/memoryview_annotation_typing.py | 68 | ||||
-rw-r--r-- | tests/memoryview/memoryview_cache_builtins.srctree | 21 | ||||
-rw-r--r-- | tests/memoryview/memoryview_no_binding_T3613.pyx | 21 | ||||
-rw-r--r-- | tests/memoryview/memoryview_no_withgil_check.pyx | 11 | ||||
-rw-r--r-- | tests/memoryview/memoryview_pep484_typing.pyx (renamed from tests/memoryview/memoryview_pep489_typing.pyx) | 2 | ||||
-rw-r--r-- | tests/memoryview/memoryviewattrs.pyx | 22 | ||||
-rw-r--r-- | tests/memoryview/memslice.pyx | 126 | ||||
-rw-r--r-- | tests/memoryview/numpy_memoryview.pyx | 77 | ||||
-rw-r--r-- | tests/memoryview/relaxed_strides.pyx | 14 | ||||
-rw-r--r-- | tests/memoryview/view_return_errors.pyx | 26 |
15 files changed, 630 insertions, 123 deletions
diff --git a/tests/memoryview/cfunc_convert_with_memoryview.pyx b/tests/memoryview/cfunc_convert_with_memoryview.pyx new file mode 100644 index 000000000..55bd02205 --- /dev/null +++ b/tests/memoryview/cfunc_convert_with_memoryview.pyx @@ -0,0 +1,51 @@ +# mode: run +# tag: autowrap +# cython: always_allow_keywords=True + +cdef void memoryview_func_a(double [:] x): + x[0] = 1 + +cdef void memoryview_func_b(double [::1] x): + x[0] = 2 + +cdef void memoryview_func_c(int [:] x): + x[0] = 1 + +cdef void memoryview_func_d(int [:] x): + x[0] = 2 + +cdef void memoryview_func_e(int [:,::1] x): + x[0,0] = 4 + +cdef void memoryview_func_f(int [::1,:] x): + x[0,0] = 4 + + +def test_memview_wrapping(): + """ + We're mainly concerned that the code compiles without the names clashing + >>> test_memview_wrapping() + 1.0 + 2.0 + 1 + 2 + """ + cdef a = memoryview_func_a + cdef b = memoryview_func_b + cdef c = memoryview_func_c + cdef d = memoryview_func_d + cdef e = memoryview_func_e + cdef f = memoryview_func_f + cdef double[1] double_arr = [0] + cdef int[1] int_arr = [0] + + a(<double[:1]> double_arr) + print(double_arr[0]) + b(<double[:1:1]> double_arr) + print(double_arr[0]) + c(<int[:1]> int_arr) + print(int_arr[0]) + d(<int[:1:1]> int_arr) + print(int_arr[0]) + # don't call e and f because it's harder without needing extra dependencies + # it's mostly a compile test for them diff --git a/tests/memoryview/cythonarray.pyx b/tests/memoryview/cythonarray.pyx index 83ff09754..15d61d086 100644 --- a/tests/memoryview/cythonarray.pyx +++ b/tests/memoryview/cythonarray.pyx @@ -130,7 +130,7 @@ cdef int *getp(int dim1=10, int dim2=10, dim3=1) except NULL: return p -cdef void callback_free_data(void *p): +cdef void callback_free_data(void *p) noexcept: print 'callback free data called' free(p) @@ -209,3 +209,116 @@ def test_cyarray_from_carray(): mslice = a print mslice[0, 0], mslice[1, 0], mslice[2, 5] + +class InheritFrom(v.array): + """ + Test is just to confirm it works, not to do anything meaningful with it + (Be aware that itemsize isn't necessarily right) + >>> inst = InheritFrom(shape=(3, 3, 3), itemsize=4, format="i") + """ + pass + + +def test_char_array_in_python_api(*shape): + """ + >>> import sys + >>> if sys.version_info[0] < 3: + ... def bytes(b): return memoryview(b).tobytes() # don't call str() + + >>> arr1d = test_char_array_in_python_api(10) + >>> print(bytes(arr1d).decode('ascii')) + xxxxxxxxxx + >>> len(bytes(arr1d)) + 10 + >>> arr2d = test_char_array_in_python_api(10, 2) + >>> print(bytes(arr2d).decode('ascii')) + xxxxxxxxxxxxxxxxxxxx + >>> len(bytes(arr2d)) + 20 + + # memoryview + >>> len(bytes(memoryview(arr1d))) + 10 + >>> bytes(memoryview(arr1d)) == bytes(arr1d) + True + >>> len(bytes(memoryview(arr2d))) + 20 + >>> bytes(memoryview(arr2d)) == bytes(arr2d) + True + + # BytesIO + >>> from io import BytesIO + >>> BytesIO(arr1d).read() == bytes(arr1d) + True + >>> BytesIO(arr2d).read() == bytes(arr2d) + True + + >>> b = BytesIO() + >>> print(b.write(arr1d)) + 10 + >>> b.getvalue() == bytes(arr1d) or b.getvalue() + True + + >>> b = BytesIO() + >>> print(b.write(arr2d)) + 20 + >>> b.getvalue() == bytes(arr2d) or b.getvalue() + True + + # BufferedWriter (uses PyBUF_SIMPLE, see https://github.com/cython/cython/issues/3775) + >>> from io import BufferedWriter + >>> b = BytesIO() + >>> bw = BufferedWriter(b) + >>> print(bw.write(arr1d)) + 10 + >>> bw.flush() + >>> b.getvalue() == bytes(arr1d) + True + + >>> b = BytesIO() + >>> bw = BufferedWriter(b) + >>> print(bw.write(arr2d)) + 20 + >>> bw.flush() + >>> b.getvalue() == bytes(arr2d) + True + """ + arr = array(shape=shape, itemsize=sizeof(char), format='c', mode='c') + arr[:] = b'x' + return arr + +def test_is_Sequence(): + """ + >>> test_is_Sequence() + 1 + 1 + True + """ + import sys + if sys.version_info < (3, 3): + from collections import Sequence + else: + from collections.abc import Sequence + + arr = array(shape=(5,), itemsize=sizeof(char), format='c', mode='c') + for i in range(arr.shape[0]): + arr[i] = f'{i}'.encode('ascii') + print(arr.count(b'1')) # test for presence of added collection method + print(arr.index(b'1')) # test for presence of added collection method + + if sys.version_info >= (3, 10): + # test structural pattern match in Python + # (because Cython hasn't implemented it yet, and because the details + # of what Python considers a sequence are important) + globs = {'arr': arr} + exec(""" +match arr: + case [*_]: + res = True + case _: + res = False +""", globs) + assert globs['res'] + + return isinstance(arr, Sequence) + diff --git a/tests/memoryview/error_declarations.pyx b/tests/memoryview/error_declarations.pyx index 0f6c52043..8c4f12a56 100644 --- a/tests/memoryview/error_declarations.pyx +++ b/tests/memoryview/error_declarations.pyx @@ -73,24 +73,24 @@ _ERRORS = u''' 13:19: Step must be omitted, 1, or a valid specifier. 14:20: Step must be omitted, 1, or a valid specifier. 15:20: Step must be omitted, 1, or a valid specifier. -16:17: Start must not be given. -17:18: Start must not be given. +16:15: Start must not be given. +17:17: Start must not be given. 18:22: Axis specification only allowed in the 'step' slot. -19:19: Fortran contiguous specifier must follow an indirect dimension +19:18: Fortran contiguous specifier must follow an indirect dimension 20:22: Invalid axis specification. 21:19: Invalid axis specification. 22:22: no expressions allowed in axis spec, only names and literals. 25:37: Memoryview 'object[::1, :]' not conformable to memoryview 'object[:, ::1]'. 28:17: Different base types for memoryviews (int, Python object) -31:9: Dimension may not be contiguous -37:9: Only one direct contiguous axis may be specified. -38:9:Only dimensions 3 and 2 may be contiguous and direct -44:10: Invalid base type for memoryview slice: intp +31:8: Dimension may not be contiguous +37:8: Only one direct contiguous axis may be specified. +38:8:Only dimensions 3 and 2 may be contiguous and direct +44:9: Invalid base type for memoryview slice: intp 46:35: Can only create cython.array from pointer or array 47:24: Cannot assign type 'double' to 'Py_ssize_t' -55:13: Invalid base type for memoryview slice: Invalid +55:12: Invalid base type for memoryview slice: Invalid 58:6: More dimensions than the maximum number of buffer dimensions were used. 59:6: More dimensions than the maximum number of buffer dimensions were used. -61:9: More dimensions than the maximum number of buffer dimensions were used. +61:8: More dimensions than the maximum number of buffer dimensions were used. 64:13: Cannot take address of memoryview slice ''' diff --git a/tests/memoryview/memoryview.pyx b/tests/memoryview/memoryview.pyx index 15c71d481..344d11840 100644 --- a/tests/memoryview/memoryview.pyx +++ b/tests/memoryview/memoryview.pyx @@ -1,5 +1,7 @@ # mode: run +# Test declarations, behaviour and coercions of the memoryview type itself. + u''' >>> f() >>> g() @@ -155,6 +157,7 @@ def assignmvs(): cdef int[:] mv3 mv1 = array((10,), itemsize=sizeof(int), format='i') mv2 = mv1 + mv1 = mv1 mv1 = mv2 mv3 = mv2 @@ -247,7 +250,7 @@ def basic_struct(MyStruct[:] mslice): >>> basic_struct(MyStructMockBuffer(None, [(1, 2, 3, 4, 5)], format="ccqii")) [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)] """ - buf = mslice + cdef object buf = mslice print sorted([(k, int(v)) for k, v in buf[0].items()]) def nested_struct(NestedStruct[:] mslice): @@ -259,7 +262,7 @@ def nested_struct(NestedStruct[:] mslice): >>> nested_struct(NestedStructMockBuffer(None, [(1, 2, 3, 4, 5)], format="T{ii}T{2i}i")) 1 2 3 4 5 """ - buf = mslice + cdef object buf = mslice d = buf[0] print d['x']['a'], d['x']['b'], d['y']['a'], d['y']['b'], d['z'] @@ -275,7 +278,7 @@ def packed_struct(PackedStruct[:] mslice): 1 2 """ - buf = mslice + cdef object buf = mslice print buf[0]['a'], buf[0]['b'] def nested_packed_struct(NestedPackedStruct[:] mslice): @@ -289,7 +292,7 @@ def nested_packed_struct(NestedPackedStruct[:] mslice): >>> nested_packed_struct(NestedPackedStructMockBuffer(None, [(1, 2, 3, 4, 5)], format="^c@i^ci@i")) 1 2 3 4 5 """ - buf = mslice + cdef object buf = mslice d = buf[0] print d['a'], d['b'], d['sub']['a'], d['sub']['b'], d['c'] @@ -299,7 +302,7 @@ def complex_dtype(long double complex[:] mslice): >>> complex_dtype(LongComplexMockBuffer(None, [(0, -1)])) -1j """ - buf = mslice + cdef object buf = mslice print buf[0] def complex_inplace(long double complex[:] mslice): @@ -307,7 +310,7 @@ def complex_inplace(long double complex[:] mslice): >>> complex_inplace(LongComplexMockBuffer(None, [(0, -1)])) (1+1j) """ - buf = mslice + cdef object buf = mslice buf[0] = buf[0] + 1 + 2j print buf[0] @@ -318,7 +321,7 @@ def complex_struct_dtype(LongComplex[:] mslice): >>> complex_struct_dtype(LongComplexMockBuffer(None, [(0, -1)])) 0.0 -1.0 """ - buf = mslice + cdef object buf = mslice print buf[0]['real'], buf[0]['imag'] # @@ -356,7 +359,7 @@ def get_int_2d(int[:, :] mslice, int i, int j): ... IndexError: Out of bounds on buffer access (axis 1) """ - buf = mslice + cdef object buf = mslice return buf[i, j] def set_int_2d(int[:, :] mslice, int i, int j, int value): @@ -409,11 +412,50 @@ def set_int_2d(int[:, :] mslice, int i, int j, int value): IndexError: Out of bounds on buffer access (axis 1) """ - buf = mslice + cdef object buf = mslice buf[i, j] = value # +# auto type inference +# (note that for most numeric types "might_overflow" stops the type inference from working well) +# +def type_infer(double[:, :] arg): + """ + >>> type_infer(DoubleMockBuffer(None, range(6), (2,3))) + double + double[:] + double[:] + double[:, :] + """ + a = arg[0,0] + print(cython.typeof(a)) + b = arg[0] + print(cython.typeof(b)) + c = arg[0,:] + print(cython.typeof(c)) + d = arg[:,:] + print(cython.typeof(d)) + +# +# Loop optimization +# +@cython.test_fail_if_path_exists("//CoerceToPyTypeNode") +def memview_iter(double[:, :] arg): + """ + >>> memview_iter(DoubleMockBuffer("C", range(6), (2,3))) + acquired C + released C + True + """ + cdef double total = 0 + for mview1d in arg: + for val in mview1d: + total += val + if total == 15: + return True + +# # Test all kinds of indexing and flags # @@ -426,7 +468,7 @@ def writable(unsigned short int[:, :, :] mslice): >>> [str(x) for x in R.received_flags] # Py2/3 ['FORMAT', 'ND', 'STRIDES', 'WRITABLE'] """ - buf = mslice + cdef object buf = mslice buf[2, 2, 1] = 23 def strided(int[:] mslice): @@ -441,7 +483,7 @@ def strided(int[:] mslice): >>> A.release_ok True """ - buf = mslice + cdef object buf = mslice return buf[2] def c_contig(int[::1] mslice): @@ -450,7 +492,7 @@ def c_contig(int[::1] mslice): >>> c_contig(A) 2 """ - buf = mslice + cdef object buf = mslice return buf[2] def c_contig_2d(int[:, ::1] mslice): @@ -461,7 +503,7 @@ def c_contig_2d(int[:, ::1] mslice): >>> c_contig_2d(A) 7 """ - buf = mslice + cdef object buf = mslice return buf[1, 3] def f_contig(int[::1, :] mslice): @@ -470,7 +512,7 @@ def f_contig(int[::1, :] mslice): >>> f_contig(A) 2 """ - buf = mslice + cdef object buf = mslice return buf[0, 1] def f_contig_2d(int[::1, :] mslice): @@ -481,7 +523,7 @@ def f_contig_2d(int[::1, :] mslice): >>> f_contig_2d(A) 7 """ - buf = mslice + cdef object buf = mslice return buf[3, 1] def generic(int[::view.generic, ::view.generic] mslice1, @@ -552,7 +594,7 @@ def printbuf_td_cy_int(td_cy_int[:] mslice, shape): ... ValueError: Buffer dtype mismatch, expected 'td_cy_int' but got 'short' """ - buf = mslice + cdef object buf = mslice cdef int i for i in range(shape[0]): print buf[i], @@ -567,7 +609,7 @@ def printbuf_td_h_short(td_h_short[:] mslice, shape): ... ValueError: Buffer dtype mismatch, expected 'td_h_short' but got 'int' """ - buf = mslice + cdef object buf = mslice cdef int i for i in range(shape[0]): print buf[i], @@ -582,7 +624,7 @@ def printbuf_td_h_cy_short(td_h_cy_short[:] mslice, shape): ... ValueError: Buffer dtype mismatch, expected 'td_h_cy_short' but got 'int' """ - buf = mslice + cdef object buf = mslice cdef int i for i in range(shape[0]): print buf[i], @@ -597,7 +639,7 @@ def printbuf_td_h_ushort(td_h_ushort[:] mslice, shape): ... ValueError: Buffer dtype mismatch, expected 'td_h_ushort' but got 'short' """ - buf = mslice + cdef object buf = mslice cdef int i for i in range(shape[0]): print buf[i], @@ -612,7 +654,7 @@ def printbuf_td_h_double(td_h_double[:] mslice, shape): ... ValueError: Buffer dtype mismatch, expected 'td_h_double' but got 'float' """ - buf = mslice + cdef object buf = mslice cdef int i for i in range(shape[0]): print buf[i], @@ -626,6 +668,8 @@ def addref(*args): def decref(*args): for item in args: Py_DECREF(item) +@cython.binding(False) +@cython.always_allow_keywords(False) def get_refcount(x): return (<PyObject*>x).ob_refcnt @@ -647,7 +691,7 @@ def printbuf_object(object[:] mslice, shape): {4: 23} 2 [34, 3] 2 """ - buf = mslice + cdef object buf = mslice cdef int i for i in range(shape[0]): print repr(buf[i]), (<PyObject*>buf[i]).ob_refcnt @@ -668,7 +712,7 @@ def assign_to_object(object[:] mslice, int idx, obj): (2, 3) >>> decref(b) """ - buf = mslice + cdef object buf = mslice buf[idx] = obj def assign_temporary_to_object(object[:] mslice): @@ -695,7 +739,7 @@ def assign_temporary_to_object(object[:] mslice): >>> assign_to_object(A, 1, a) >>> decref(a) """ - buf = mslice + cdef object buf = mslice buf[1] = {3-2: 2+(2*4)-2} @@ -743,7 +787,7 @@ def test_generic_slicing(arg, indirect=False): """ cdef int[::view.generic, ::view.generic, :] _a = arg - a = _a + cdef object a = _a b = a[2:8:2, -4:1:-1, 1:3] print b.shape @@ -826,7 +870,7 @@ def test_direct_slicing(arg): released A """ cdef int[:, :, :] _a = arg - a = _a + cdef object a = _a b = a[2:8:2, -4:1:-1, 1:3] print b.shape @@ -854,7 +898,7 @@ def test_slicing_and_indexing(arg): released A """ cdef int[:, :, :] _a = arg - a = _a + cdef object a = _a b = a[-5:, 1, 1::2] c = b[4:1:-1, ::-1] d = c[2, 1:2] @@ -1099,7 +1143,7 @@ def optimised_index_of_slice(int[:,:,:] arr, int x, int y, int z): def test_assign_from_byteslike(byteslike): - # Once http://python3statement.org is accepted, should be just + # Once https://python3statement.org/ is accepted, should be just # >>> test_assign_from_byteslike(bytes(b'hello')) # b'hello' # ... @@ -1130,3 +1174,84 @@ def test_assign_from_byteslike(byteslike): return (<unsigned char*>buf)[:5] finally: free(buf) + +def multiple_memoryview_def(double[:] a, double[:] b): + return a[0] + b[0] + +cpdef multiple_memoryview_cpdef(double[:] a, double[:] b): + return a[0] + b[0] + +cdef multiple_memoryview_cdef(double[:] a, double[:] b): + return a[0] + b[0] + +multiple_memoryview_cdef_wrapper = multiple_memoryview_cdef + +def test_conversion_failures(): + """ + What we're concerned with here is that we don't lose references if one + of several memoryview arguments fails to convert. + + >>> test_conversion_failures() + """ + imb = IntMockBuffer("", range(1), shape=(1,)) + dmb = DoubleMockBuffer("", range(1), shape=(1,)) + for first, second in [(imb, dmb), (dmb, imb)]: + for func in [multiple_memoryview_def, multiple_memoryview_cpdef, multiple_memoryview_cdef_wrapper]: + # note - using python call of "multiple_memoryview_cpdef" deliberately + imb_before = get_refcount(imb) + dmb_before = get_refcount(dmb) + try: + func(first, second) + except: + assert get_refcount(imb) == imb_before, "before %s after %s" % (imb_before, get_refcount(imb)) + assert get_refcount(dmb) == dmb_before, "before %s after %s" % (dmb_before, get_refcount(dmb)) + else: + assert False, "Conversion should fail!" + +def test_is_Sequence(double[:] a): + """ + >>> test_is_Sequence(DoubleMockBuffer(None, range(6), shape=(6,))) + 1 + 1 + True + """ + if sys.version_info < (3, 3): + from collections import Sequence + else: + from collections.abc import Sequence + + for i in range(a.shape[0]): + a[i] = i + print(a.count(1.0)) # test for presence of added collection method + print(a.index(1.0)) # test for presence of added collection method + + if sys.version_info >= (3, 10): + # test structural pattern match in Python + # (because Cython hasn't implemented it yet, and because the details + # of what Python considers a sequence are important) + globs = {'arr': a} + exec(""" +match arr: + case [*_]: + res = True + case _: + res = False +""", globs) + assert globs['res'] + + return isinstance(<object>a, Sequence) + + +ctypedef int aliasT +def test_assignment_typedef(): + """ + >>> test_assignment_typedef() + 1 + 2 + """ + cdef int[2] x + cdef aliasT[:] y + x[:] = [1, 2] + y = x + for v in y: + print(v) diff --git a/tests/memoryview/memoryview_acq_count.srctree b/tests/memoryview/memoryview_acq_count.srctree index e7e6dfc69..3bc2f1cc9 100644 --- a/tests/memoryview/memoryview_acq_count.srctree +++ b/tests/memoryview/memoryview_acq_count.srctree @@ -35,7 +35,7 @@ cdef Py_ssize_t i for i in prange(1000000, nogil=True, num_threads=16): use_slice(m[::2]) -cdef int use_slice(int[:] m) nogil except -1: +cdef int use_slice(int[:] m) except -1 nogil: cdef int[:] m2 = m[1:] m = m2[:-1] del m, m2 diff --git a/tests/memoryview/memoryview_annotation_typing.py b/tests/memoryview/memoryview_annotation_typing.py index 260e9ffeb..57299c835 100644 --- a/tests/memoryview/memoryview_annotation_typing.py +++ b/tests/memoryview/memoryview_annotation_typing.py @@ -1,7 +1,9 @@ # mode: run -# tag: pep484, numpy, pure3.0 +# tag: pep484, numpy, pure3.7 ##, warnings +from __future__ import annotations # object[:] cannot be evaluated + import cython import numpy @@ -52,3 +54,67 @@ def one_dim_nogil_cfunc(a: cython.double[:]): with cython.nogil: result = _one_dim_nogil_cfunc(a) return result + + +def generic_object_memoryview(a: object[:]): + """ + >>> a = numpy.ones((10,), dtype=object) + >>> generic_object_memoryview(a) + 10 + """ + sum = 0 + for ai in a: + sum += ai + if cython.compiled: + assert cython.typeof(a) == "object[:]", cython.typeof(a) + return sum + + +def generic_object_memoryview_contig(a: object[::1]): + """ + >>> a = numpy.ones((10,), dtype=object) + >>> generic_object_memoryview_contig(a) + 10 + """ + sum = 0 + for ai in a: + sum += ai + if cython.compiled: + assert cython.typeof(a) == "object[::1]", cython.typeof(a) + return sum + + +@cython.cclass +class C: + x: cython.int + + def __init__(self, value): + self.x = value + + +def ext_type_object_memoryview(a: C[:]): + """ + >>> a = numpy.array([C(i) for i in range(10)], dtype=object) + >>> ext_type_object_memoryview(a) + 45 + """ + sum = 0 + for ai in a: + sum += ai.x + if cython.compiled: + assert cython.typeof(a) == "C[:]", cython.typeof(a) + return sum + + +def ext_type_object_memoryview_contig(a: C[::1]): + """ + >>> a = numpy.array([C(i) for i in range(10)], dtype=object) + >>> ext_type_object_memoryview_contig(a) + 45 + """ + sum = 0 + for ai in a: + sum += ai.x + if cython.compiled: + assert cython.typeof(a) == "C[::1]", cython.typeof(a) + return sum diff --git a/tests/memoryview/memoryview_cache_builtins.srctree b/tests/memoryview/memoryview_cache_builtins.srctree new file mode 100644 index 000000000..91b1c4753 --- /dev/null +++ b/tests/memoryview/memoryview_cache_builtins.srctree @@ -0,0 +1,21 @@ +PYTHON setup.py build_ext --inplace + +################ setup.py ##################### + +from distutils.core import setup +from Cython.Build import cythonize +from Cython.Compiler import Options + +Options.cache_builtins = False + +setup( + ext_modules = cythonize("mview.pyx") +) + +############### mview.pyx ################ + +# https://github.com/cython/cython/issues/3406 +# Failure was at Cython compilation stage + +def f(double [::1] x): + pass diff --git a/tests/memoryview/memoryview_no_binding_T3613.pyx b/tests/memoryview/memoryview_no_binding_T3613.pyx new file mode 100644 index 000000000..1c736359e --- /dev/null +++ b/tests/memoryview/memoryview_no_binding_T3613.pyx @@ -0,0 +1,21 @@ +# mode: compile +# tag: memoryview + +# cython: binding=False + +# See GH 3613 - when memoryviews were compiled with binding off they ended up in an +# inconsistent state where different directives were applied at different stages +# of the pipeline + +import cython + +def f(double[:] a): + pass + +@cython.binding(False) +def g(double[:] a): + pass + +@cython.binding(True) +def h(double[:] a): + pass diff --git a/tests/memoryview/memoryview_no_withgil_check.pyx b/tests/memoryview/memoryview_no_withgil_check.pyx new file mode 100644 index 000000000..4753bab12 --- /dev/null +++ b/tests/memoryview/memoryview_no_withgil_check.pyx @@ -0,0 +1,11 @@ +# mode: compile + +# cython: test_fail_if_c_code_has = __Pyx_ErrOccurredWithGIL + +# cython-generated memoryview code shouldn't resort to +# __Pyx_ErrOccurredWithGIL for error checking (because it's inefficient +# inside a nogil block) + +def assign(double[:] a, double[:] b): + with nogil: + a[:] = b[:] diff --git a/tests/memoryview/memoryview_pep489_typing.pyx b/tests/memoryview/memoryview_pep484_typing.pyx index 0a62ff624..66445a1b7 100644 --- a/tests/memoryview/memoryview_pep489_typing.pyx +++ b/tests/memoryview/memoryview_pep484_typing.pyx @@ -1,5 +1,5 @@ # mode: run -# tag: pep489, memoryview +# tag: pep484, memoryview cimport cython diff --git a/tests/memoryview/memoryviewattrs.pyx b/tests/memoryview/memoryviewattrs.pyx index 66bc9da56..7322424c3 100644 --- a/tests/memoryview/memoryviewattrs.pyx +++ b/tests/memoryview/memoryviewattrs.pyx @@ -1,13 +1,6 @@ # mode: run # tag: numpy -__test__ = {} - -def testcase(func): - __test__[func.__name__] = func.__doc__ - return func - - cimport cython from cython.view cimport array @@ -15,7 +8,6 @@ import numpy as np cimport numpy as np -@testcase def test_shape_stride_suboffset(): u''' >>> test_shape_stride_suboffset() @@ -49,7 +41,6 @@ def test_shape_stride_suboffset(): print c_contig.suboffsets[0], c_contig.suboffsets[1], c_contig.suboffsets[2] -@testcase def test_copy_to(): u''' >>> test_copy_to() @@ -72,7 +63,6 @@ def test_copy_to(): print ' '.join(str(to_data[i]) for i in range(2*2*2)) -@testcase def test_overlapping_copy(): """ >>> test_overlapping_copy() @@ -88,7 +78,6 @@ def test_overlapping_copy(): assert slice[i] == 10 - 1 - i -@testcase def test_copy_return_type(): """ >>> test_copy_return_type() @@ -103,7 +92,6 @@ def test_copy_return_type(): print(f_contig[2, 2]) -@testcase def test_partly_overlapping(): """ >>> test_partly_overlapping() @@ -119,7 +107,6 @@ def test_partly_overlapping(): for i in range(5): assert slice2[i] == i + 4 -@testcase @cython.nonecheck(True) def test_nonecheck1(): u''' @@ -131,7 +118,6 @@ def test_nonecheck1(): cdef int[:,:,:] uninitialized print uninitialized.is_c_contig() -@testcase @cython.nonecheck(True) def test_nonecheck2(): u''' @@ -143,7 +129,6 @@ def test_nonecheck2(): cdef int[:,:,:] uninitialized print uninitialized.is_f_contig() -@testcase @cython.nonecheck(True) def test_nonecheck3(): u''' @@ -155,7 +140,6 @@ def test_nonecheck3(): cdef int[:,:,:] uninitialized uninitialized.copy() -@testcase @cython.nonecheck(True) def test_nonecheck4(): u''' @@ -167,7 +151,6 @@ def test_nonecheck4(): cdef int[:,:,:] uninitialized uninitialized.copy_fortran() -@testcase @cython.nonecheck(True) def test_nonecheck5(): u''' @@ -179,7 +162,6 @@ def test_nonecheck5(): cdef int[:,:,:] uninitialized uninitialized._data -@testcase def test_copy_mismatch(): u''' >>> test_copy_mismatch() @@ -193,7 +175,6 @@ def test_copy_mismatch(): mv1[...] = mv2 -@testcase def test_is_contiguous(): u""" >>> test_is_contiguous() @@ -222,7 +203,6 @@ def test_is_contiguous(): print 'strided', strided[::2].is_c_contig() -@testcase def call(): u''' >>> call() @@ -265,7 +245,6 @@ def call(): assert len(mv3) == 3 -@testcase def two_dee(): u''' >>> two_dee() @@ -313,7 +292,6 @@ def two_dee(): print (<long*>mv3._data)[0] , (<long*>mv3._data)[1] , (<long*>mv3._data)[2] , (<long*>mv3._data)[3] -@testcase def fort_two_dee(): u''' >>> fort_two_dee() diff --git a/tests/memoryview/memslice.pyx b/tests/memoryview/memslice.pyx index 2d496821f..0de47d9b6 100644 --- a/tests/memoryview/memslice.pyx +++ b/tests/memoryview/memslice.pyx @@ -1,5 +1,7 @@ # mode: run +# Test the behaviour of memoryview slicing and indexing, contiguity, etc. + # Note: see also bufaccess.pyx from __future__ import unicode_literals @@ -1064,6 +1066,8 @@ def addref(*args): def decref(*args): for item in args: Py_DECREF(item) +@cython.binding(False) +@cython.always_allow_keywords(False) def get_refcount(x): return (<PyObject*>x).ob_refcnt @@ -1626,7 +1630,7 @@ def test_index_slicing_away_direct_indirect(): All dimensions preceding dimension 1 must be indexed and not sliced """ cdef int[:, ::view.indirect, :] a = TestIndexSlicingDirectIndirectDims() - a_obj = a + cdef object a_obj = a print a[1][2][3] print a[1, 2, 3] @@ -1724,7 +1728,7 @@ def test_oob(): print a[:, 20] -cdef int nogil_oob(int[:, :] a) nogil except 0: +cdef int nogil_oob(int[:, :] a) except 0 nogil: a[100, 9:] return 1 @@ -1768,7 +1772,7 @@ def test_nogil_oob2(): a[100, 9:] @cython.boundscheck(False) -cdef int cdef_nogil(int[:, :] a) nogil except 0: +cdef int cdef_nogil(int[:, :] a) except 0 nogil: cdef int i, j cdef int[:, :] b = a[::-1, 3:10:2] for i in range(b.shape[0]): @@ -1956,11 +1960,7 @@ def test_struct_attributes_format(): """ cdef TestAttrs[10] array cdef TestAttrs[:] struct_memview = array - - if sys.version_info[:2] >= (2, 7): - print builtins.memoryview(struct_memview).format - else: - print "T{i:int_attrib:c:char_attrib:}" + print builtins.memoryview(struct_memview).format # Test padding at the end of structs in the buffer support @@ -2248,7 +2248,7 @@ def test_object_dtype_copying(): 7 8 9 - 2 5 + 5 1 5 """ cdef int i @@ -2269,10 +2269,12 @@ def test_object_dtype_copying(): print m2[i] obj = m2[5] - print get_refcount(obj), obj + refcount1 = get_refcount(obj) + print obj del m2 - print get_refcount(obj), obj + refcount2 = get_refcount(obj) + print refcount1 - refcount2, obj assert unique_refcount == get_refcount(unique), (unique_refcount, get_refcount(unique)) @@ -2359,7 +2361,9 @@ def test_contig_scalar_to_slice_assignment(): """ >>> test_contig_scalar_to_slice_assignment() 14 14 14 14 + 30 14 30 14 20 20 20 20 + 30 30 20 20 """ cdef int[5][10] a cdef int[:, ::1] m = a @@ -2367,9 +2371,15 @@ def test_contig_scalar_to_slice_assignment(): m[...] = 14 print m[0, 0], m[-1, -1], m[3, 2], m[4, 9] + m[:, :1] = 30 + print m[0, 0], m[0, 1], m[1, 0], m[1, 1] + m[:, :] = 20 print m[0, 0], m[-1, -1], m[3, 2], m[4, 9] + m[:1, :] = 30 + print m[0, 0], m[0, 1], m[1, 0], m[1, 1] + @testcase def test_dtype_object_scalar_assignment(): """ @@ -2558,6 +2568,53 @@ def test_const_buffer(const int[:] a): @testcase +def test_loop(int[:] a, throw_exception): + """ + >>> A = IntMockBuffer("A", range(6), shape=(6,)) + >>> test_loop(A, False) + acquired A + 15 + released A + >>> try: + ... test_loop(A, True) + ... except ValueError: + ... pass + acquired A + released A + """ + cdef int sum = 0 + for ai in a: + sum += ai + if throw_exception: + raise ValueError() + print(sum) + + +@testcase +def test_loop_reassign(int[:] a): + """ + >>> A = IntMockBuffer("A", range(6), shape=(6,)) + >>> test_loop_reassign(A) + acquired A + 0 + 1 + 2 + 3 + 4 + 5 + 15 + released A + """ + cdef int sum = 0 + for ai in a: + sum += ai + print(ai) + a = None # this should not mess up the loop though! + print(sum) + # release happens in the wrapper function + + +@testcase def test_arg_in_closure(int [:] a): """ >>> A = IntMockBuffer("A", range(6), shape=(6,)) @@ -2594,3 +2651,50 @@ def test_arg_in_closure_cdef(a): """ return arg_in_closure_cdef(a) + +@testcase +def test_local_in_closure(a): + """ + >>> A = IntMockBuffer("A", range(6), shape=(6,)) + >>> inner = test_local_in_closure(A) + acquired A + >>> inner() + (0, 1) + + The assignment below is just to avoid printing what was collected + >>> del inner; ignore_me = gc.collect() + released A + """ + cdef int[:] a_view = a + def inner(): + return (a_view[0], a_view[1]) + return inner + +@testcase +def test_local_in_generator_expression(a, initialize, execute_now): + """ + >>> A1 = IntMockBuffer("A1", range(6), shape=(6,)) + >>> A2 = IntMockBuffer("A2", range(6), shape=(6,)) + >>> test_local_in_generator_expression(A1, initialize=False, execute_now=False) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + UnboundLocalError... + + >>> test_local_in_generator_expression(A1, initialize=True, execute_now=True) + acquired A1 + released A1 + True + + >>> genexp = test_local_in_generator_expression(A2, initialize=True, execute_now=False) + acquired A2 + >>> sum(genexp) + released A2 + 2 + """ + cdef int[:] a_view + if initialize: + a_view = a + if execute_now: + return any(ai > 3 for ai in a_view) + else: + return (ai > 3 for ai in a_view) diff --git a/tests/memoryview/numpy_memoryview.pyx b/tests/memoryview/numpy_memoryview.pyx index 3b3c15dba..7f98352a6 100644 --- a/tests/memoryview/numpy_memoryview.pyx +++ b/tests/memoryview/numpy_memoryview.pyx @@ -17,6 +17,10 @@ include "../buffers/mockbuffers.pxi" ctypedef np.int32_t dtype_t +IS_PYPY = hasattr(sys, 'pypy_version_info') +NUMPY_VERSION = tuple(int(v) for v in np.__version__.split('.')[:2]) +print(NUMPY_VERSION) + def get_array(): # We need to type our array to get a __pyx_get_buffer() that typechecks # for np.ndarray and calls __getbuffer__ in numpy.pxd @@ -32,22 +36,13 @@ def ae(*args): if x != args[0]: raise AssertionError(args) -__test__ = {} - -def testcase(f): - __test__[f.__name__] = f.__doc__ +def testcase_no_pypy(f, _is_pypy=hasattr(sys, "pypy_version_info")): + if _is_pypy: + f.__doc__ = "" # disable the tests return f -def testcase_numpy_1_5(f): - major, minor, *rest = np.__version__.split('.') - if (int(major), int(minor)) >= (1, 5): - __test__[f.__name__] = f.__doc__ - return f - - def gc_collect_if_required(): - major, minor, *rest = np.__version__.split('.') - if (int(major), int(minor)) >= (1, 14): + if NUMPY_VERSION >= (1, 14) or IS_PYPY: import gc gc.collect() @@ -56,7 +51,6 @@ def gc_collect_if_required(): ### Test slicing memoryview slices # -@testcase def test_partial_slicing(array): """ >>> test_partial_slicing(a) @@ -72,7 +66,6 @@ def test_partial_slicing(array): ae(b.strides[0], c.strides[0], obj.strides[0]) ae(b.strides[1], c.strides[1], obj.strides[1]) -@testcase def test_ellipsis(array): """ >>> test_ellipsis(a) @@ -114,7 +107,6 @@ def test_ellipsis(array): # ### Test slicing memoryview objects # -@testcase def test_partial_slicing_memoryview(array): """ >>> test_partial_slicing_memoryview(a) @@ -131,7 +123,6 @@ def test_partial_slicing_memoryview(array): ae(b.strides[0], c.strides[0], obj.strides[0]) ae(b.strides[1], c.strides[1], obj.strides[1]) -@testcase def test_ellipsis_memoryview(array): """ >>> test_ellipsis_memoryview(a) @@ -172,7 +163,6 @@ def test_ellipsis_memoryview(array): ae(e.strides[0], e_obj.strides[0]) -@testcase def test_transpose(): """ >>> test_transpose() @@ -186,7 +176,7 @@ def test_transpose(): numpy_obj = np.arange(4 * 3, dtype=np.int32).reshape(4, 3) a = numpy_obj - a_obj = a + cdef object a_obj = a cdef dtype_t[:, :] b = a.T print a.T.shape[0], a.T.shape[1] @@ -203,7 +193,6 @@ def test_transpose(): print a[3, 2], a.T[2, 3], a_obj[3, 2], a_obj.T[2, 3], numpy_obj[3, 2], numpy_obj.T[2, 3] -@testcase def test_transpose_type(a): """ >>> a = np.zeros((5, 10), dtype=np.float64) @@ -216,12 +205,8 @@ def test_transpose_type(a): print m_transpose[6, 4] -@testcase_numpy_1_5 def test_numpy_like_attributes(cyarray): """ - For some reason this fails in numpy 1.4, with shape () and strides (40, 8) - instead of 20, 4 on my machine. Investigate this. - >>> cyarray = create_array(shape=(8, 5), mode="c") >>> test_numpy_like_attributes(cyarray) >>> test_numpy_like_attributes(cyarray.memview) @@ -237,14 +222,13 @@ def test_numpy_like_attributes(cyarray): cdef int[:, :] mslice = numarray assert (<object> mslice).base is numarray -@testcase_numpy_1_5 def test_copy_and_contig_attributes(a): """ >>> a = np.arange(20, dtype=np.int32).reshape(5, 4) >>> test_copy_and_contig_attributes(a) """ cdef np.int32_t[:, :] mslice = a - m = mslice + cdef object m = mslice # object copy # Test object copy attributes assert np.all(a == np.array(m.copy())) @@ -264,7 +248,7 @@ cdef extern from "bufaccess.h": ctypedef unsigned int td_h_ushort # Defined as unsigned short ctypedef td_h_short td_h_cy_short -cdef void dealloc_callback(void *data): +cdef void dealloc_callback(void *data) noexcept: print "deallocating..." def build_numarray(array array): @@ -274,7 +258,7 @@ def build_numarray(array array): def index(array array): print build_numarray(array)[3, 2] -@testcase_numpy_1_5 +@testcase_no_pypy def test_coerce_to_numpy(): """ Test coercion to NumPy arrays, especially with automatically @@ -355,6 +339,7 @@ def test_coerce_to_numpy(): 'e': 800, } + smallstructs[idx] = { 'a': 600, 'b': 700 } nestedstructs[idx] = { @@ -412,7 +397,7 @@ def test_coerce_to_numpy(): index(<td_h_ushort[:4, :5]> <td_h_ushort *> h_ushorts) -@testcase_numpy_1_5 +@testcase_no_pypy def test_memslice_getbuffer(): """ >>> test_memslice_getbuffer(); gc_collect_if_required() @@ -451,7 +436,6 @@ cdef packed struct StructArray: int a[4] signed char b[5] -@testcase_numpy_1_5 def test_memslice_structarray(data, dtype): """ >>> def b(s): return s.encode('ascii') @@ -507,7 +491,6 @@ def test_memslice_structarray(data, dtype): print myslice[i].a[j] print myslice[i].b.decode('ASCII') -@testcase_numpy_1_5 def test_structarray_errors(StructArray[:] a): """ >>> dtype = np.dtype([('a', '4i'), ('b', '5b')]) @@ -554,7 +537,6 @@ def stringstructtest(StringStruct[:] view): def stringtest(String[:] view): pass -@testcase_numpy_1_5 def test_string_invalid_dims(): """ >>> def b(s): return s.encode('ascii') @@ -575,7 +557,6 @@ ctypedef struct AttributesStruct: float attrib2 StringStruct attrib3 -@testcase_numpy_1_5 def test_struct_attributes(): """ >>> test_struct_attributes() @@ -631,7 +612,6 @@ cdef class SuboffsetsNoStridesBuffer(Buffer): getbuffer(self, info) info.suboffsets = self._shape -@testcase def test_null_strides(Buffer buffer_obj): """ >>> test_null_strides(Buffer()) @@ -651,7 +631,6 @@ def test_null_strides(Buffer buffer_obj): assert m2[i, j] == buffer_obj.m[i, j], (i, j, m2[i, j], buffer_obj.m[i, j]) assert m3[i, j] == buffer_obj.m[i, j] -@testcase def test_null_strides_error(buffer_obj): """ >>> test_null_strides_error(Buffer()) @@ -725,7 +704,6 @@ ctypedef struct SameTypeAfterArraysStructSimple: double b[16] double c -@testcase def same_type_after_arrays_simple(): """ >>> same_type_after_arrays_simple() @@ -747,7 +725,6 @@ ctypedef struct SameTypeAfterArraysStructComposite: double h[4] int i -@testcase def same_type_after_arrays_composite(): """ >>> same_type_after_arrays_composite() if sys.version_info[:2] >= (3, 5) else None @@ -757,3 +734,29 @@ def same_type_after_arrays_composite(): cdef SameTypeAfterArraysStructComposite element arr = np.ones(2, np.asarray(<SameTypeAfterArraysStructComposite[:1]>&element).dtype) cdef SameTypeAfterArraysStructComposite[:] memview = arr + +ctypedef fused np_numeric_t: + np.float64_t + +def test_invalid_buffer_fused_memoryview(np_numeric_t[:] A): + """ + >>> import numpy as np + >>> zz = np.zeros([5], dtype='M') + >>> test_invalid_buffer_fused_memoryview(zz) + Traceback (most recent call last): + ... + TypeError: No matching signature found + """ + return + +ctypedef fused np_numeric_object_t: + np.float64_t[:] + object + +def test_valid_buffer_fused_memoryview(np_numeric_object_t A): + """ + >>> import numpy as np + >>> zz = np.zeros([5], dtype='M') + >>> test_valid_buffer_fused_memoryview(zz) + """ + return diff --git a/tests/memoryview/relaxed_strides.pyx b/tests/memoryview/relaxed_strides.pyx index 1743f23d0..6f90f4db5 100644 --- a/tests/memoryview/relaxed_strides.pyx +++ b/tests/memoryview/relaxed_strides.pyx @@ -10,12 +10,12 @@ Thanks to Nathaniel Smith and Sebastian Berg. See also: Mailing list threads: - http://thread.gmane.org/gmane.comp.python.cython.devel/14762 - http://thread.gmane.org/gmane.comp.python.cython.devel/14634 + https://thread.gmane.org/gmane.comp.python.cython.devel/14762 + https://thread.gmane.org/gmane.comp.python.cython.devel/14634 Detailed discussion of the difference between numpy/cython's current definition of "contiguity", and the correct definition: - http://thread.gmane.org/gmane.comp.python.cython.devel/14634/focus=14640 + https://thread.gmane.org/gmane.comp.python.cython.devel/14634/focus=14640 The PR implementing NPY_RELAXED_STRIDES_CHECKING: https://github.com/numpy/numpy/pull/3162 @@ -37,13 +37,6 @@ NUMPY_HAS_RELAXED_STRIDES = ( np.ones((10, 1), order="C").flags.f_contiguous) -def not_py26(f): - import sys - if sys.version_info < (2, 7): - return lambda a: None - return f - - def test_one_sized(array): """ >>> contig = np.ascontiguousarray(np.arange(10, dtype=np.double)[::100]) @@ -81,7 +74,6 @@ def test_zero_sized_multidim_ccontig(array): cdef double[:, :, ::1] a = array return a -@not_py26 def test_zero_sized_multidim_fcontig(array): """ >>> contig = np.ascontiguousarray(np.zeros((4,4,4))[::2, 2:2, ::2]) diff --git a/tests/memoryview/view_return_errors.pyx b/tests/memoryview/view_return_errors.pyx index 8e9443108..6cedef969 100644 --- a/tests/memoryview/view_return_errors.pyx +++ b/tests/memoryview/view_return_errors.pyx @@ -13,7 +13,18 @@ cdef double[:] foo(int i): raise TypeError('dummy') -def propagate(i): +cdef double[:] foo_nogil(int i) nogil: + if i == 1: + raise AttributeError('dummy') + if i == 2: + raise RuntimeError('dummy') + if i == 3: + raise ValueError('dummy') + if i == 4: + raise TypeError('dummy') + + +def propagate(i, bint nogil=False): """ >>> propagate(0) TypeError('Memoryview return value is not initialized') @@ -25,9 +36,20 @@ def propagate(i): ValueError('dummy') >>> propagate(4) TypeError('dummy') + + >>> propagate(0, nogil=True) + TypeError('Memoryview return value is not initialized') + >>> propagate(1, nogil=True) + AttributeError('dummy') + >>> propagate(2, nogil=True) + RuntimeError('dummy') + >>> propagate(3, nogil=True) + ValueError('dummy') + >>> propagate(4, nogil=True) + TypeError('dummy') """ try: - foo(i) + foo_nogil(i) if nogil else foo(i) except Exception as e: print '%s(%r)' % (e.__class__.__name__, e.args[0]) else: |