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.
695 lines
26 KiB
695 lines
26 KiB
|
7 years ago
|
#
|
||
|
|
# This file is part of Magnum.
|
||
|
|
#
|
||
|
|
# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
|
||
|
|
# Vladimír Vondruš <mosra@centrum.cz>
|
||
|
|
#
|
||
|
|
# Permission is hereby granted, free of charge, to any person obtaining a
|
||
|
|
# copy of this software and associated documentation files (the "Software"),
|
||
|
|
# to deal in the Software without restriction, including without limitation
|
||
|
|
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||
|
|
# and/or sell copies of the Software, and to permit persons to whom the
|
||
|
|
# Software is furnished to do so, subject to the following conditions:
|
||
|
|
#
|
||
|
|
# The above copyright notice and this permission notice shall be included
|
||
|
|
# in all copies or substantial portions of the Software.
|
||
|
|
#
|
||
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||
|
|
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||
|
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||
|
|
# DEALINGS IN THE SOFTWARE.
|
||
|
|
#
|
||
|
|
|
||
|
|
import sys
|
||
|
|
import unittest
|
||
|
|
|
||
|
|
from corrade import containers
|
||
|
|
|
||
|
|
class ArrayView(unittest.TestCase):
|
||
|
|
def test_init(self):
|
||
|
|
a = containers.ArrayView()
|
||
|
|
b = containers.MutableArrayView()
|
||
|
|
self.assertIs(a.obj, None)
|
||
|
|
self.assertIs(b.obj, None)
|
||
|
|
self.assertEqual(len(a), 0)
|
||
|
|
self.assertEqual(len(b), 0)
|
||
|
|
self.assertEqual(bytes(a), b'')
|
||
|
|
self.assertEqual(bytes(b), b'')
|
||
|
|
|
||
|
|
def test_init_buffer(self):
|
||
|
|
a = b'hello'
|
||
|
|
a_refcount = sys.getrefcount(a)
|
||
|
|
|
||
|
|
b = containers.ArrayView(a)
|
||
|
|
self.assertIs(b.obj, a)
|
||
|
|
self.assertEqual(len(b), 5)
|
||
|
|
self.assertEqual(bytes(b), b'hello')
|
||
|
|
self.assertEqual(b[2], 'l')
|
||
|
|
self.assertEqual(sys.getrefcount(a), a_refcount + 1)
|
||
|
|
|
||
|
|
# Not mutable
|
||
|
|
with self.assertRaisesRegex(TypeError, "object does not support item assignment"):
|
||
|
|
b[4] = '!'
|
||
|
|
|
||
|
|
# b should keep a reference to a, so deleting the local reference
|
||
|
|
# shouldn't affect it
|
||
|
|
del a
|
||
|
|
self.assertTrue(sys.getrefcount(b.obj), a_refcount)
|
||
|
|
self.assertEqual(b[2], 'l')
|
||
|
|
|
||
|
|
# Now, if we delete b, a should not be referenced by anything anymore
|
||
|
|
a = b.obj
|
||
|
|
del b
|
||
|
|
self.assertTrue(sys.getrefcount(a), a_refcount)
|
||
|
|
|
||
|
|
def test_init_buffer_mutable(self):
|
||
|
|
a = bytearray(b'hello')
|
||
|
|
b = containers.MutableArrayView(a)
|
||
|
|
b[4] = '!'
|
||
|
|
self.assertEqual(b[4], '!')
|
||
|
|
self.assertEqual(bytes(b), b'hell!')
|
||
|
|
|
||
|
|
def test_init_buffer_unexpected_dimensions(self):
|
||
|
|
a = memoryview(b'123456').cast('b', shape=[2, 3])
|
||
|
|
self.assertEqual(bytes(a), b'123456')
|
||
|
|
with self.assertRaisesRegex(BufferError, "expected one dimension but got 2"):
|
||
|
|
b = containers.ArrayView(a)
|
||
|
|
|
||
|
|
def test_init_buffer_unexpected_stride(self):
|
||
|
|
a = memoryview(b'hello')[::2]
|
||
|
|
self.assertEqual(bytes(a), b'hlo')
|
||
|
|
with self.assertRaisesRegex(BufferError, "expected stride of 1 but got 2"):
|
||
|
|
b = containers.ArrayView(a)
|
||
|
|
|
||
|
|
def test_init_buffer_mutable_from_immutable(self):
|
||
|
|
a = b'hello'
|
||
|
|
with self.assertRaisesRegex(BufferError, "Object is not writable."):
|
||
|
|
b = containers.MutableArrayView(a)
|
||
|
|
|
||
|
|
def test_slice(self):
|
||
|
|
a = b'World is hell!'
|
||
|
|
a_refcount = sys.getrefcount(a)
|
||
|
|
|
||
|
|
b = containers.ArrayView(a)
|
||
|
|
b_refcount = sys.getrefcount(b)
|
||
|
|
self.assertEqual(sys.getrefcount(a), a_refcount + 1)
|
||
|
|
|
||
|
|
# When slicing, b's refcount should not change but a's refcount should
|
||
|
|
# increase
|
||
|
|
c = b[4:-4]
|
||
|
|
self.assertIsInstance(c, containers.ArrayView)
|
||
|
|
self.assertEqual(bytes(c), b'd is h')
|
||
|
|
self.assertEqual(sys.getrefcount(b), b_refcount)
|
||
|
|
self.assertEqual(sys.getrefcount(a), a_refcount + 2)
|
||
|
|
|
||
|
|
# Deleting a slice should reduce a's refcount again, keep b's unchanged
|
||
|
|
del c
|
||
|
|
self.assertEqual(sys.getrefcount(b), b_refcount)
|
||
|
|
self.assertEqual(sys.getrefcount(a), a_refcount + 1)
|
||
|
|
|
||
|
|
def test_slice_empty(self):
|
||
|
|
# slice.start = slice.stop
|
||
|
|
a = containers.ArrayView(b'hello')[7:8]
|
||
|
|
self.assertEqual(len(a), 0)
|
||
|
|
|
||
|
|
def test_slice_invalid(self):
|
||
|
|
with self.assertRaisesRegex(ValueError, "slice step cannot be zero"):
|
||
|
|
containers.ArrayView()[::0]
|
||
|
|
|
||
|
|
def test_slice_stride(self):
|
||
|
|
a = b'World_ _i_s_ _hell!'
|
||
|
|
a_refcount = sys.getrefcount(a)
|
||
|
|
|
||
|
|
b = containers.ArrayView(a)
|
||
|
|
b_refcount = sys.getrefcount(b)
|
||
|
|
self.assertEqual(sys.getrefcount(a), a_refcount + 1)
|
||
|
|
|
||
|
|
# When slicing to a strided array view, b's refcount should not change
|
||
|
|
# but a's refcount should increase. Check consistency with slices on
|
||
|
|
# bytes, slicing bytes will make a copy so it doesn't affect the
|
||
|
|
# refcount.
|
||
|
|
c1 = a[4:-4:2]
|
||
|
|
c2 = b[4:-4:2]
|
||
|
|
self.assertIsInstance(c2, containers.StridedArrayView1D)
|
||
|
|
self.assertEqual(len(c1), 6)
|
||
|
|
self.assertEqual(len(c2), 6)
|
||
|
|
self.assertEqual(bytes(c1), b'd is h')
|
||
|
|
self.assertEqual(bytes(c2), b'd is h')
|
||
|
|
self.assertEqual(c2.size, (6,))
|
||
|
|
self.assertEqual(c2.stride, (2,))
|
||
|
|
self.assertEqual(sys.getrefcount(b), b_refcount)
|
||
|
|
self.assertEqual(sys.getrefcount(a), a_refcount + 2)
|
||
|
|
|
||
|
|
# Deleting a slice should reduce a's refcount again, keep b's unchanged
|
||
|
|
del c2
|
||
|
|
self.assertEqual(sys.getrefcount(b), b_refcount)
|
||
|
|
self.assertEqual(sys.getrefcount(a), a_refcount + 1)
|
||
|
|
|
||
|
|
def test_slice_stride_negative(self):
|
||
|
|
a = b'World_ _i_s_ _hell!'
|
||
|
|
b = containers.ArrayView(a)
|
||
|
|
|
||
|
|
# Check consistency with slices on bytes
|
||
|
|
c1 = a[-5:3:-2] # like [4:-4:2] above, but reverted
|
||
|
|
c2 = b[-5:3:-2]
|
||
|
|
self.assertEqual(len(c1), 6)
|
||
|
|
self.assertEqual(len(c2), 6)
|
||
|
|
self.assertEqual(bytes(c1), b'h si d') # like b'd is h' but reverted
|
||
|
|
self.assertEqual(bytes(c2), b'h si d')
|
||
|
|
self.assertEqual(c2.size, (6,))
|
||
|
|
self.assertEqual(c2.stride, (-2,))
|
||
|
|
|
||
|
|
def test_slice_stride_reverse(self):
|
||
|
|
# slice.stop = -1
|
||
|
|
a = containers.ArrayView(b'hello')[::-1]
|
||
|
|
self.assertEqual(len(a), 5)
|
||
|
|
self.assertEqual(bytes(a), b'olleh')
|
||
|
|
|
||
|
|
def test_convert_memoryview(self):
|
||
|
|
a = b'World is hell!'
|
||
|
|
a_refcount = sys.getrefcount(a)
|
||
|
|
|
||
|
|
b = containers.ArrayView(a)
|
||
|
|
b_refcount = sys.getrefcount(b)
|
||
|
|
self.assertEqual(sys.getrefcount(a), a_refcount + 1)
|
||
|
|
|
||
|
|
# TODO: fix when pybind is replaced
|
||
|
|
|
||
|
|
c = memoryview(b)
|
||
|
|
self.assertEqual(c.obj, b) # TODO: should be a
|
||
|
|
self.assertEqual(sys.getrefcount(b), b_refcount + 1) # TODO: should not hcange
|
||
|
|
self.assertEqual(sys.getrefcount(a), a_refcount + 1) # TODO: should be +2
|
||
|
|
|
||
|
|
class StridedArrayView1D(unittest.TestCase):
|
||
|
|
def test_init(self):
|
||
|
|
a = containers.StridedArrayView1D()
|
||
|
|
b = containers.MutableStridedArrayView1D()
|
||
|
|
self.assertIs(a.obj, None)
|
||
|
|
self.assertIs(b.obj, None)
|
||
|
|
self.assertEqual(len(a), 0)
|
||
|
|
self.assertEqual(len(b), 0)
|
||
|
|
self.assertEqual(bytes(a), b'')
|
||
|
|
self.assertEqual(bytes(b), b'')
|
||
|
|
self.assertEqual(a.size, (0, ))
|
||
|
|
self.assertEqual(b.size, (0, ))
|
||
|
|
self.assertEqual(a.stride, (0, ))
|
||
|
|
self.assertEqual(b.stride, (0, ))
|
||
|
|
self.assertEqual(a.dimensions, 1)
|
||
|
|
self.assertEqual(b.dimensions, 1)
|
||
|
|
|
||
|
|
def test_init_buffer(self):
|
||
|
|
a = b'hello'
|
||
|
|
a_refcount = sys.getrefcount(a)
|
||
|
|
|
||
|
|
b = containers.StridedArrayView1D(a)
|
||
|
|
self.assertIs(b.obj, a)
|
||
|
|
self.assertEqual(len(b), 5)
|
||
|
|
self.assertEqual(bytes(b), b'hello')
|
||
|
|
self.assertEqual(b.size, (5, ))
|
||
|
|
self.assertEqual(b.stride, (1, ))
|
||
|
|
self.assertEqual(b[2], 'l')
|
||
|
|
self.assertEqual(sys.getrefcount(a), a_refcount + 1)
|
||
|
|
|
||
|
|
# Not mutable
|
||
|
|
with self.assertRaisesRegex(TypeError, "object does not support item assignment"):
|
||
|
|
b[4] = '!'
|
||
|
|
|
||
|
|
# b should keep a reference to a, so deleting the local reference
|
||
|
|
# shouldn't affect it
|
||
|
|
del a
|
||
|
|
self.assertTrue(sys.getrefcount(b.obj), a_refcount)
|
||
|
|
self.assertEqual(b[2], 'l')
|
||
|
|
|
||
|
|
# Now, if we delete b, a should not be referenced by anything anymore
|
||
|
|
a = b.obj
|
||
|
|
del b
|
||
|
|
self.assertTrue(sys.getrefcount(a), a_refcount)
|
||
|
|
|
||
|
|
@unittest.expectedFailure
|
||
|
|
def test_init_buffer_memoryview_obj(self):
|
||
|
|
a = b'hello'
|
||
|
|
v = memoryview(a)
|
||
|
|
b = containers.StridedArrayView1D(v)
|
||
|
|
self.assertIs(b.obj, a) # TODO: it's b because pybind is stupid
|
||
|
|
|
||
|
|
def test_init_buffer_mutable(self):
|
||
|
|
a = bytearray(b'hello')
|
||
|
|
b = containers.MutableStridedArrayView1D(a)
|
||
|
|
b[4] = '!'
|
||
|
|
self.assertEqual(b[4], '!')
|
||
|
|
self.assertEqual(bytes(b), b'hell!')
|
||
|
|
|
||
|
|
def test_init_buffer_unexpected_dimensions(self):
|
||
|
|
a = memoryview(b'123456').cast('b', shape=[2, 3])
|
||
|
|
self.assertEqual(bytes(a), b'123456')
|
||
|
|
with self.assertRaisesRegex(BufferError, "expected 1 dimensions but got 2"):
|
||
|
|
b = containers.StridedArrayView1D(a)
|
||
|
|
|
||
|
|
def test_init_buffer_stride(self):
|
||
|
|
a = memoryview(b'hello')[::2]
|
||
|
|
self.assertEqual(bytes(a), b'hlo')
|
||
|
|
b = containers.StridedArrayView1D(a)
|
||
|
|
self.assertEqual(len(b), 3)
|
||
|
|
self.assertEqual(bytes(b), b'hlo')
|
||
|
|
self.assertEqual(b.size, (3, ))
|
||
|
|
self.assertEqual(b.stride, (2, ))
|
||
|
|
self.assertEqual(b[2], 'o')
|
||
|
|
|
||
|
|
def test_init_buffer_mutable_from_immutable(self):
|
||
|
|
a = b'hello'
|
||
|
|
with self.assertRaisesRegex(BufferError, "Object is not writable."):
|
||
|
|
b = containers.MutableStridedArrayView1D(a)
|
||
|
|
|
||
|
|
def test_slice(self):
|
||
|
|
a = b'World is hell!'
|
||
|
|
a_refcount = sys.getrefcount(a)
|
||
|
|
|
||
|
|
b = containers.StridedArrayView1D(a)
|
||
|
|
b_refcount = sys.getrefcount(b)
|
||
|
|
self.assertEqual(sys.getrefcount(a), a_refcount + 1)
|
||
|
|
|
||
|
|
# When slicing, b's refcount should not change but a's refcount should
|
||
|
|
# increase
|
||
|
|
c = b[4:-4]
|
||
|
|
self.assertEqual(c.size, (6,))
|
||
|
|
self.assertEqual(c.stride, (1,))
|
||
|
|
self.assertIsInstance(c, containers.StridedArrayView1D)
|
||
|
|
self.assertEqual(bytes(c), b'd is h')
|
||
|
|
self.assertEqual(sys.getrefcount(b), b_refcount)
|
||
|
|
self.assertEqual(sys.getrefcount(a), a_refcount + 2)
|
||
|
|
|
||
|
|
# Deleting a slice should reduce a's refcount again, keep b's unchanged
|
||
|
|
del c
|
||
|
|
self.assertEqual(sys.getrefcount(b), b_refcount)
|
||
|
|
self.assertEqual(sys.getrefcount(a), a_refcount + 1)
|
||
|
|
|
||
|
|
def test_slice_invalid(self):
|
||
|
|
with self.assertRaisesRegex(TypeError, "indices must be integers"):
|
||
|
|
containers.StridedArrayView1D()[-5:3:"boo"]
|
||
|
|
|
||
|
|
def test_slice_stride(self):
|
||
|
|
a = b'World_ _i_s_ _hell!'
|
||
|
|
b = containers.StridedArrayView1D(a)
|
||
|
|
|
||
|
|
# Check consistency with slices on bytes
|
||
|
|
c1 = a[4:-4:2]
|
||
|
|
c2 = b[4:-4:2]
|
||
|
|
self.assertIsInstance(c2, containers.StridedArrayView1D)
|
||
|
|
self.assertEqual(len(c1), 6)
|
||
|
|
self.assertEqual(len(c2), 6)
|
||
|
|
self.assertEqual(bytes(c1), b'd is h')
|
||
|
|
self.assertEqual(bytes(c2), b'd is h')
|
||
|
|
self.assertEqual(c2.size, (6,))
|
||
|
|
self.assertEqual(c2.stride, (2,))
|
||
|
|
|
||
|
|
def test_slice_stride_negative(self):
|
||
|
|
a = b'World_ _i_s_ _hell!'
|
||
|
|
b = containers.StridedArrayView1D(a)
|
||
|
|
|
||
|
|
# Check consistency with slices on bytes
|
||
|
|
c1 = a[-5:3:-2] # like [4:-4:2] above, but reverted
|
||
|
|
c2 = b[-5:3:-2]
|
||
|
|
self.assertEqual(len(c1), 6)
|
||
|
|
self.assertEqual(len(c2), 6)
|
||
|
|
self.assertEqual(bytes(c1), b'h si d') # like b'd is h' but reverted
|
||
|
|
self.assertEqual(bytes(c2), b'h si d')
|
||
|
|
self.assertEqual(c2.size, (6,))
|
||
|
|
self.assertEqual(c2.stride, (-2,))
|
||
|
|
|
||
|
|
def test_convert_memoryview(self):
|
||
|
|
a = b'World is hell!'
|
||
|
|
a_refcount = sys.getrefcount(a)
|
||
|
|
|
||
|
|
b = containers.StridedArrayView1D(a)
|
||
|
|
b_refcount = sys.getrefcount(b)
|
||
|
|
self.assertEqual(sys.getrefcount(a), a_refcount + 1)
|
||
|
|
|
||
|
|
# TODO: fix when pybind is replaced
|
||
|
|
|
||
|
|
c = memoryview(b)
|
||
|
|
self.assertEqual(c.ndim, 1)
|
||
|
|
self.assertEqual(len(c), len(a))
|
||
|
|
self.assertEqual(bytes(c), a)
|
||
|
|
self.assertEqual(c.obj, b) # TODO: should be a
|
||
|
|
self.assertEqual(sys.getrefcount(b), b_refcount + 1) # TODO: should not change
|
||
|
|
self.assertEqual(sys.getrefcount(a), a_refcount + 1) # TODO: should be +2
|
||
|
|
|
||
|
|
c[-1] = ord('?') # TODO: wrong, should fail
|
||
|
|
self.assertEqual(a, b'World is hell?') # TODO: wrong
|
||
|
|
|
||
|
|
class StridedArrayView2D(unittest.TestCase):
|
||
|
|
def test_init(self):
|
||
|
|
a = containers.StridedArrayView2D()
|
||
|
|
b = containers.MutableStridedArrayView2D()
|
||
|
|
self.assertIs(a.obj, None)
|
||
|
|
self.assertIs(b.obj, None)
|
||
|
|
self.assertEqual(len(a), 0)
|
||
|
|
self.assertEqual(len(b), 0)
|
||
|
|
self.assertEqual(bytes(a), b'')
|
||
|
|
self.assertEqual(bytes(b), b'')
|
||
|
|
self.assertEqual(a.size, (0, 0))
|
||
|
|
self.assertEqual(b.size, (0, 0))
|
||
|
|
self.assertEqual(a.stride, (0, 0))
|
||
|
|
self.assertEqual(b.stride, (0, 0))
|
||
|
|
self.assertEqual(a.dimensions, 2)
|
||
|
|
self.assertEqual(b.dimensions, 2)
|
||
|
|
|
||
|
|
def test_init_buffer(self):
|
||
|
|
a = (b'01234567'
|
||
|
|
b'456789ab'
|
||
|
|
b'89abcdef')
|
||
|
|
a_refcount = sys.getrefcount(a)
|
||
|
|
|
||
|
|
b = containers.StridedArrayView2D(memoryview(a).cast('b', shape=[3, 8]))
|
||
|
|
# TODO: construct as containers.StridedArrayView2D(a, (3, 8), (8, 1))
|
||
|
|
#self.assertIs(b.obj, a) # TODO
|
||
|
|
self.assertEqual(len(b), 3)
|
||
|
|
self.assertEqual(bytes(b), b'01234567'
|
||
|
|
b'456789ab'
|
||
|
|
b'89abcdef')
|
||
|
|
self.assertEqual(b.size, (3, 8))
|
||
|
|
self.assertEqual(b.stride, (8, 1))
|
||
|
|
self.assertIsInstance(b[1], containers.StridedArrayView1D)
|
||
|
|
self.assertEqual(bytes(b[1]), b'456789ab')
|
||
|
|
self.assertEqual(b[1, 2], '6')
|
||
|
|
self.assertEqual(b[1][2], '6')
|
||
|
|
self.assertEqual(sys.getrefcount(a), a_refcount + 1)
|
||
|
|
|
||
|
|
# Not mutable
|
||
|
|
with self.assertRaisesRegex(TypeError, "object does not support item assignment"):
|
||
|
|
b[1, 2] = '!'
|
||
|
|
|
||
|
|
# b should keep a reference to a, so deleting the local reference
|
||
|
|
# shouldn't affect it
|
||
|
|
del a
|
||
|
|
self.assertTrue(sys.getrefcount(b.obj), a_refcount)
|
||
|
|
self.assertEqual(b[1][2], '6')
|
||
|
|
|
||
|
|
# Now, if we delete b, a should not be referenced by anything anymore
|
||
|
|
a = b.obj
|
||
|
|
del b
|
||
|
|
self.assertTrue(sys.getrefcount(a), a_refcount)
|
||
|
|
|
||
|
|
def test_init_buffer_mutable(self):
|
||
|
|
a = bytearray(b'01234567'
|
||
|
|
b'456789ab'
|
||
|
|
b'89abcdef')
|
||
|
|
b = containers.MutableStridedArrayView2D(memoryview(a).cast('b', shape=[3, 8]))
|
||
|
|
b[0, 7] = '!'
|
||
|
|
b[1, 7] = '!'
|
||
|
|
b[2, 7] = '!'
|
||
|
|
self.assertEqual(b[0][7], '!')
|
||
|
|
self.assertEqual(bytes(b), b'0123456!'
|
||
|
|
b'456789a!'
|
||
|
|
b'89abcde!')
|
||
|
|
|
||
|
|
def test_init_buffer_unexpected_dimensions(self):
|
||
|
|
a = b'123456'
|
||
|
|
with self.assertRaisesRegex(BufferError, "expected 2 dimensions but got 1"):
|
||
|
|
b = containers.StridedArrayView2D(a)
|
||
|
|
|
||
|
|
def test_init_buffer_stride(self):
|
||
|
|
a = memoryview(b'01234567'
|
||
|
|
b'456789ab'
|
||
|
|
b'89abcdef').cast('b', shape=[3, 8])[::2]
|
||
|
|
self.assertEqual(bytes(a), b'0123456789abcdef')
|
||
|
|
b = containers.StridedArrayView2D(a)
|
||
|
|
self.assertEqual(len(b), 2)
|
||
|
|
self.assertEqual(bytes(b), b'0123456789abcdef')
|
||
|
|
self.assertEqual(b.size, (2, 8))
|
||
|
|
self.assertEqual(b.stride, (16, 1))
|
||
|
|
self.assertEqual(bytes(b[1]), b'89abcdef')
|
||
|
|
self.assertEqual(b[1][3], 'b')
|
||
|
|
|
||
|
|
def test_init_buffer_mutable_from_immutable(self):
|
||
|
|
a = memoryview(b'01234567'
|
||
|
|
b'456789ab'
|
||
|
|
b'89abcdef').cast('b', shape=[3, 8])
|
||
|
|
with self.assertRaisesRegex(BufferError, "underlying buffer is not writable"):
|
||
|
|
b = containers.MutableStridedArrayView2D(a)
|
||
|
|
|
||
|
|
def test_slice(self):
|
||
|
|
a = (b'01234567'
|
||
|
|
b'456789ab'
|
||
|
|
b'89abcdef')
|
||
|
|
a_refcount = sys.getrefcount(a)
|
||
|
|
|
||
|
|
v = memoryview(a).cast('b', shape=[3, 8])
|
||
|
|
v_refcount = sys.getrefcount(v)
|
||
|
|
|
||
|
|
# TODO: Pybind refcounts against v, not a
|
||
|
|
|
||
|
|
b = containers.StridedArrayView2D(v)
|
||
|
|
b_refcount = sys.getrefcount(b)
|
||
|
|
self.assertEqual(sys.getrefcount(a), a_refcount + 1)
|
||
|
|
self.assertEqual(sys.getrefcount(v), v_refcount + 1) # TODO: should not change
|
||
|
|
|
||
|
|
# When slicing, b's refcount should not change but a's refcount should
|
||
|
|
# increase
|
||
|
|
c = b[0:-1]
|
||
|
|
self.assertIsInstance(c, containers.StridedArrayView2D)
|
||
|
|
self.assertEqual(c.size, (2, 8))
|
||
|
|
self.assertEqual(c.stride, (8, 1))
|
||
|
|
self.assertEqual(bytes(c), b'01234567456789ab')
|
||
|
|
self.assertEqual(sys.getrefcount(b), b_refcount)
|
||
|
|
self.assertEqual(sys.getrefcount(a), a_refcount + 1) # TODO: should be +2
|
||
|
|
self.assertEqual(sys.getrefcount(v), v_refcount + 2) # TODO: should not change
|
||
|
|
|
||
|
|
# Deleting a slice should reduce a's refcount again, keep b's unchanged
|
||
|
|
del c
|
||
|
|
self.assertEqual(sys.getrefcount(b), b_refcount)
|
||
|
|
self.assertEqual(sys.getrefcount(a), a_refcount + 1)
|
||
|
|
self.assertEqual(sys.getrefcount(v), v_refcount + 1) # TODO: should not change
|
||
|
|
|
||
|
|
def test_slice_multidimensional(self):
|
||
|
|
a = (b'01234567'
|
||
|
|
b'456789ab'
|
||
|
|
b'89abcdef')
|
||
|
|
a_refcount = sys.getrefcount(a)
|
||
|
|
|
||
|
|
v = memoryview(a).cast('b', shape=[3, 8])
|
||
|
|
v_refcount = sys.getrefcount(v)
|
||
|
|
|
||
|
|
# TODO: Pybind refcounts against v, not a
|
||
|
|
|
||
|
|
b = containers.StridedArrayView2D(v)
|
||
|
|
b_refcount = sys.getrefcount(b)
|
||
|
|
self.assertEqual(sys.getrefcount(a), a_refcount + 1)
|
||
|
|
self.assertEqual(sys.getrefcount(v), v_refcount + 1) # TODO: should not change
|
||
|
|
|
||
|
|
# When slicing, b's refcount should not change but a's refcount should
|
||
|
|
# increase
|
||
|
|
c = b[1:3,4:7]
|
||
|
|
self.assertIsInstance(c, containers.StridedArrayView2D)
|
||
|
|
self.assertEqual(c.size, (2, 3))
|
||
|
|
self.assertEqual(c.stride, (8, 1))
|
||
|
|
self.assertEqual(bytes(c[0]), b'89a')
|
||
|
|
self.assertEqual(bytes(c[1]), b'cde')
|
||
|
|
self.assertEqual(sys.getrefcount(b), b_refcount)
|
||
|
|
self.assertEqual(sys.getrefcount(a), a_refcount + 1) # TODO: should be +2
|
||
|
|
self.assertEqual(sys.getrefcount(v), v_refcount + 2) # TODO: should not change
|
||
|
|
|
||
|
|
# Deleting a slice should reduce a's refcount again, keep b's unchanged
|
||
|
|
del c
|
||
|
|
self.assertEqual(sys.getrefcount(b), b_refcount)
|
||
|
|
self.assertEqual(sys.getrefcount(a), a_refcount + 1)
|
||
|
|
self.assertEqual(sys.getrefcount(v), v_refcount + 1) # TODO: should not change
|
||
|
|
|
||
|
|
def test_slice_invalid(self):
|
||
|
|
with self.assertRaisesRegex(ValueError, "slice step cannot be zero"):
|
||
|
|
containers.StridedArrayView1D()[-5:3:0]
|
||
|
|
|
||
|
|
def test_slice_stride(self):
|
||
|
|
a = (b'01234567'
|
||
|
|
b'456789ab'
|
||
|
|
b'89abcdef')
|
||
|
|
v = memoryview(a).cast('b', shape=[3, 8])
|
||
|
|
b = containers.StridedArrayView2D(v)
|
||
|
|
|
||
|
|
# Check consistency with slices on memoryview
|
||
|
|
c1 = v[0:3:2]
|
||
|
|
c2 = b[0:3:2]
|
||
|
|
self.assertEqual(len(c1), 2)
|
||
|
|
self.assertEqual(len(c2), 2)
|
||
|
|
self.assertIsInstance(c2, containers.StridedArrayView2D)
|
||
|
|
self.assertEqual(bytes(c1), b'0123456789abcdef')
|
||
|
|
self.assertEqual(bytes(c2), b'0123456789abcdef')
|
||
|
|
self.assertEqual(c2.size, (2, 8))
|
||
|
|
self.assertEqual(c2.stride, (16, 1))
|
||
|
|
self.assertEqual(bytes(c2[1]), b'89abcdef')
|
||
|
|
|
||
|
|
def test_slice_stride_negative(self):
|
||
|
|
a = (b'01234567'
|
||
|
|
b'456789ab'
|
||
|
|
b'89abcdef')
|
||
|
|
v = memoryview(a).cast('b', shape=[3, 8])
|
||
|
|
b = containers.StridedArrayView2D(v)
|
||
|
|
|
||
|
|
# Check consistency with slices on memoryview
|
||
|
|
self.assertEqual(v.shape, (3, 8))
|
||
|
|
self.assertEqual(b.size, (3, 8))
|
||
|
|
self.assertEqual(v.strides, (8, 1))
|
||
|
|
self.assertEqual(b.stride, (8, 1))
|
||
|
|
c1 = v[-1:None:-2] # like [0:3:2] above, but reverted
|
||
|
|
c2 = b[-1:None:-2]
|
||
|
|
self.assertEqual(len(c1), 2)
|
||
|
|
self.assertEqual(len(c2), 2)
|
||
|
|
self.assertEqual(bytes(c1), b'89abcdef01234567') # like above but reverted
|
||
|
|
self.assertEqual(bytes(c2), b'89abcdef01234567')
|
||
|
|
self.assertEqual(c1.shape, (2, 8))
|
||
|
|
self.assertEqual(c2.size, (2, 8))
|
||
|
|
self.assertEqual(c1.strides, (-16, 1))
|
||
|
|
self.assertEqual(c2.stride, (-16, 1))
|
||
|
|
|
||
|
|
def test_slice_stride_negative_multidimensional(self):
|
||
|
|
a = (b'01234567'
|
||
|
|
b'456789ab'
|
||
|
|
b'89abcdef')
|
||
|
|
v = memoryview(a).cast('b', shape=[3, 8])
|
||
|
|
b = containers.StridedArrayView2D(v)
|
||
|
|
|
||
|
|
# Check consistency with slices on memoryview
|
||
|
|
self.assertEqual(v.shape, (3, 8))
|
||
|
|
self.assertEqual(b.size, (3, 8))
|
||
|
|
self.assertEqual(v.strides, (8, 1))
|
||
|
|
self.assertEqual(b.stride, (8, 1))
|
||
|
|
|
||
|
|
with self.assertRaises(NotImplementedError):
|
||
|
|
c1 = v[-1:None:-2, -2:2:-3] # HAH!
|
||
|
|
|
||
|
|
c2 = b[-1:None:-2, -2:2:-3]
|
||
|
|
self.assertEqual(len(c2), 2)
|
||
|
|
self.assertEqual(bytes(c2), b'eb63')
|
||
|
|
self.assertEqual(c2.size, (2, 2))
|
||
|
|
self.assertEqual(c2.stride, (-16, -3))
|
||
|
|
|
||
|
|
def test_ops(self):
|
||
|
|
a = (b'01234567'
|
||
|
|
b'456789ab'
|
||
|
|
b'89abcdef')
|
||
|
|
v = memoryview(a).cast('b', shape=[3, 8])
|
||
|
|
|
||
|
|
b = containers.StridedArrayView2D(v).transposed(0, 1).flipped(0)
|
||
|
|
self.assertEqual(b.size, (8, 3))
|
||
|
|
self.assertEqual(b.stride, (-1, 8))
|
||
|
|
self.assertEqual(bytes(b), b'7bf6ae59d48c37b26a159048')
|
||
|
|
|
||
|
|
c = containers.StridedArrayView2D(v).transposed(1, 0).flipped(1)
|
||
|
|
self.assertEqual(c.size, (8, 3))
|
||
|
|
self.assertEqual(c.stride, (1, -8))
|
||
|
|
self.assertEqual(bytes(c), b'840951a62b73c84d95ea6fb7')
|
||
|
|
|
||
|
|
d = containers.StridedArrayView2D(v).transposed(0, 1)[3:4].broadcasted(0, 5)
|
||
|
|
self.assertEqual(d.size, (5, 3))
|
||
|
|
self.assertEqual(d.stride, (0, 8))
|
||
|
|
self.assertEqual(bytes(d), b'37b37b37b37b37b')
|
||
|
|
|
||
|
|
d = containers.StridedArrayView2D(v)[:, 3:4].broadcasted(1, 2)
|
||
|
|
self.assertEqual(d.size, (3, 2))
|
||
|
|
self.assertEqual(d.stride, (8, 0))
|
||
|
|
self.assertEqual(bytes(d), b'3377bb')
|
||
|
|
|
||
|
|
def test_convert_memoryview(self):
|
||
|
|
a = (b'01234567'
|
||
|
|
b'456789ab'
|
||
|
|
b'89abcdef')
|
||
|
|
a_refcount = sys.getrefcount(a)
|
||
|
|
v = memoryview(a).cast('b', shape=[3, 8])
|
||
|
|
v_refcount = sys.getrefcount(v)
|
||
|
|
|
||
|
|
b = containers.StridedArrayView2D(v)
|
||
|
|
b_refcount = sys.getrefcount(b)
|
||
|
|
self.assertEqual(sys.getrefcount(a), a_refcount + 1) # TODO: should be + 2
|
||
|
|
self.assertEqual(sys.getrefcount(v), v_refcount + 1) # TODO: should not change
|
||
|
|
|
||
|
|
c = memoryview(b)
|
||
|
|
self.assertEqual(c.ndim, 2)
|
||
|
|
self.assertEqual(c.shape, (3, 8))
|
||
|
|
self.assertEqual(c.strides, (8, 1))
|
||
|
|
self.assertEqual(c.obj, v) # TODO: should be a
|
||
|
|
self.assertEqual(sys.getrefcount(b), b_refcount + 1) # TODO: should not change
|
||
|
|
self.assertEqual(sys.getrefcount(a), a_refcount + 1) # TODO: should be + 2
|
||
|
|
self.assertEqual(sys.getrefcount(v), v_refcount + 1) # TODO: should not change
|
||
|
|
|
||
|
|
c[2, 1] = ord('!') # TODO: wrong, should fail
|
||
|
|
self.assertEqual(chr(c[2, 1]), '!') # TODO: should be 9
|
||
|
|
|
||
|
|
class StridedArrayView3D(unittest.TestCase):
|
||
|
|
def test_init_buffer(self):
|
||
|
|
a = (b'01234567'
|
||
|
|
b'456789ab'
|
||
|
|
b'89abcdef'
|
||
|
|
|
||
|
|
b'cdef0123'
|
||
|
|
b'01234567'
|
||
|
|
b'456789ab')
|
||
|
|
b = containers.StridedArrayView3D(memoryview(a).cast('b', shape=[2, 3, 8]))
|
||
|
|
self.assertEqual(len(b), 2)
|
||
|
|
self.assertEqual(bytes(b), b'01234567456789ab89abcdefcdef012301234567456789ab')
|
||
|
|
self.assertEqual(b.size, (2, 3, 8))
|
||
|
|
self.assertEqual(b.stride, (24, 8, 1))
|
||
|
|
self.assertEqual(b[1, 2, 3], '7')
|
||
|
|
self.assertEqual(b[1][2][3], '7')
|
||
|
|
|
||
|
|
def test_init_buffer_mutable(self):
|
||
|
|
a = bytearray(b'01234567'
|
||
|
|
b'456789ab'
|
||
|
|
b'89abcdef'
|
||
|
|
|
||
|
|
b'cdef0123'
|
||
|
|
b'01234567'
|
||
|
|
b'456789ab')
|
||
|
|
b = containers.MutableStridedArrayView3D(memoryview(a).cast('b', shape=[2, 3, 8]))
|
||
|
|
b[0, 0, 7] = '!'
|
||
|
|
b[0, 1, 7] = '!'
|
||
|
|
b[0, 2, 7] = '!'
|
||
|
|
b[1, 0, 7] = '!'
|
||
|
|
b[1, 1, 7] = '!'
|
||
|
|
b[1, 2, 7] = '!'
|
||
|
|
self.assertEqual(b[1][1][7], '!')
|
||
|
|
self.assertEqual(bytes(b), b'0123456!'
|
||
|
|
b'456789a!'
|
||
|
|
b'89abcde!'
|
||
|
|
|
||
|
|
b'cdef012!'
|
||
|
|
b'0123456!'
|
||
|
|
b'456789a!')
|
||
|
|
|
||
|
|
def test_ops(self):
|
||
|
|
a = (b'01234567'
|
||
|
|
b'456789ab'
|
||
|
|
b'89abcdef'
|
||
|
|
|
||
|
|
b'cdef0123'
|
||
|
|
b'01234567'
|
||
|
|
b'456789ab')
|
||
|
|
v = memoryview(a).cast('b', shape=[2, 3, 8])
|
||
|
|
|
||
|
|
b = containers.StridedArrayView3D(v).transposed(0, 1).flipped(0)
|
||
|
|
self.assertEqual(b.size, (3, 2, 8))
|
||
|
|
self.assertEqual(b.stride, (-8, 24, 1))
|
||
|
|
self.assertEqual(bytes(b), b'89abcdef456789ab456789ab0123456701234567cdef0123')
|
||
|
|
|
||
|
|
c = containers.StridedArrayView3D(v).transposed(2, 0).flipped(1)
|
||
|
|
self.assertEqual(c.size, (8, 3, 2))
|
||
|
|
self.assertEqual(c.stride, (1, -8, 24))
|
||
|
|
self.assertEqual(bytes(c), b'84400c95511da6622eb7733fc88440d99551eaa662fbb773')
|
||
|
|
|
||
|
|
d = containers.StridedArrayView3D(v).transposed(1, 2)[0:1, 3:5, :].broadcasted(0, 5)
|
||
|
|
self.assertEqual(d.size, (5, 2, 3))
|
||
|
|
self.assertEqual(d.stride, (0, 1, 8))
|
||
|
|
self.assertEqual(bytes(d), b'37b48c37b48c37b48c37b48c37b48c')
|
||
|
|
|
||
|
|
e = containers.StridedArrayView3D(v)[:, 1:2, 3:4].flipped(2).broadcasted(1, 2)
|
||
|
|
self.assertEqual(e.size, (2, 2, 1))
|
||
|
|
self.assertEqual(e.stride, (24, 0, -1))
|
||
|
|
self.assertEqual(bytes(e), b'7733')
|
||
|
|
|
||
|
|
f = containers.StridedArrayView3D(v)[:, :, 0:1].broadcasted(2, 5)
|
||
|
|
self.assertEqual(f.size, (2, 3, 5))
|
||
|
|
self.assertEqual(f.stride, (24, 8, 0))
|
||
|
|
self.assertEqual(bytes(f), b'000004444488888ccccc0000044444')
|