Merge pull request #7071 from youknowone/memoryview

Update test_memoryview from v3.14.3 and impl count/index
This commit is contained in:
Jeong, YunWon authored and GitHub committed 2026-02-11 00:13:48 +09:00
commit 355db1cfad
2 files changed
+256 -29

No files matched your search

+183 -22
View File
@@ -13,8 +13,15 @@ import array
import io
import copy
import pickle
import struct
from test.support import import_helper
from itertools import product
from test import support
from test.support import import_helper, threading_helper
class MyObject:
pass
class AbstractMemoryTests:
@@ -53,12 +60,53 @@ class AbstractMemoryTests:
for tp in self._types:
self.check_getitem_with_type(tp)
def test_index(self):
for tp in self._types:
b = tp(self._source)
m = self._view(b) # may be a sub-view
l = m.tolist()
k = 2 * len(self._source)
for chi in self._source:
if chi in l:
self.assertEqual(m.index(chi), l.index(chi))
else:
self.assertRaises(ValueError, m.index, chi)
for start, stop in product(range(-k, k), range(-k, k)):
index = -1
try:
index = l.index(chi, start, stop)
except ValueError:
pass
if index == -1:
self.assertRaises(ValueError, m.index, chi, start, stop)
else:
self.assertEqual(m.index(chi, start, stop), index)
def test_iter(self):
for tp in self._types:
b = tp(self._source)
m = self._view(b)
self.assertEqual(list(m), [m[i] for i in range(len(m))])
def test_count(self):
for tp in self._types:
b = tp(self._source)
m = self._view(b)
l = m.tolist()
for ch in list(m):
self.assertEqual(m.count(ch), l.count(ch))
b = tp((b'a' * 5) + (b'c' * 3))
m = self._view(b) # may be sliced
l = m.tolist()
with self.subTest('count', buffer=b):
self.assertEqual(m.count(ord('a')), l.count(ord('a')))
self.assertEqual(m.count(ord('b')), l.count(ord('b')))
self.assertEqual(m.count(ord('c')), l.count(ord('c')))
def test_setitem_readonly(self):
if not self.ro_type:
self.skipTest("no read-only type to test")
@@ -191,16 +239,12 @@ class AbstractMemoryTests:
self.assertEqual(m.suboffsets, ())
return m
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_attributes_readonly(self):
if not self.ro_type:
self.skipTest("no read-only type to test")
m = self.check_attributes_with_type(self.ro_type)
self.assertEqual(m.readonly, True)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_attributes_writable(self):
if not self.rw_type:
self.skipTest("no writable type to test")
@@ -231,8 +275,6 @@ class AbstractMemoryTests:
self.m = memoryview(base)
class MySource(tp):
pass
class MyObject:
pass
# Create a reference cycle through a memoryview object.
# This exercises mbuf_clear().
@@ -345,6 +387,21 @@ class AbstractMemoryTests:
m = self._view(b)
self.assertRaises(ValueError, hash, m)
@unittest.expectedFailure # TODO: RUSTPYTHON; re-entrant buffer release not detected
def test_hash_use_after_free(self):
# Prevent crash in memoryview(v).__hash__ with re-entrant v.__hash__.
# Regression test for https://github.com/python/cpython/issues/142664.
class E(array.array):
def __hash__(self):
mv.release()
self.clear()
return 123
v = E('B', b'A' * 4096)
mv = memoryview(v).toreadonly() # must be read-only for hash()
self.assertRaises(BufferError, hash, mv)
self.assertRaises(BufferError, mv.__hash__)
def test_weakref(self):
# Check memoryviews are weakrefable
for tp in self._types:
@@ -400,6 +457,21 @@ class AbstractMemoryTests:
self.assertEqual(c.format, "H")
self.assertEqual(d.format, "H")
@unittest.expectedFailure # TODO: RUSTPYTHON; re-entrant buffer release not detected
def test_hex_use_after_free(self):
# Prevent UAF in memoryview.hex(sep) with re-entrant sep.__len__.
# Regression test for https://github.com/python/cpython/issues/143195.
ba = bytearray(b'A' * 1024)
mv = memoryview(ba)
class S(bytes):
def __len__(self):
mv.release()
ba.clear()
return 1
self.assertRaises(BufferError, mv.hex, S(b':'))
# Variations on source objects for the buffer: bytes-like objects, then arrays
# with itemsize > 1.
@@ -439,6 +511,18 @@ class BaseMemoryviewTests:
def _check_contents(self, tp, obj, contents):
self.assertEqual(obj, tp(contents))
def test_count(self):
super().test_count()
for tp in self._types:
b = tp((b'a' * 5) + (b'c' * 3))
m = self._view(b) # should not be sliced
self.assertEqual(len(b), len(m))
with self.subTest('count', buffer=b):
self.assertEqual(m.count(ord('a')), 5)
self.assertEqual(m.count(ord('b')), 0)
self.assertEqual(m.count(ord('c')), 3)
class BaseMemorySliceTests:
source_bytes = b"XabcdefY"
@@ -472,11 +556,6 @@ class BaseMemorySliceSliceTests:
class BytesMemoryviewTest(unittest.TestCase,
BaseMemoryviewTests, BaseBytesMemoryTests):
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_gc(self):
super().test_gc()
def test_constructor(self):
for tp in self._types:
ob = tp(self._source)
@@ -487,6 +566,10 @@ class BytesMemoryviewTest(unittest.TestCase,
self.assertRaises(TypeError, memoryview, argument=ob)
self.assertRaises(TypeError, memoryview, ob, argument=True)
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: False is not true : <test.test_memoryview.MyObject object at 0x84ecb1920>
def test_gc(self):
return super().test_gc()
class ArrayMemoryviewTest(unittest.TestCase,
BaseMemoryviewTests, BaseArrayMemoryTests):
@@ -501,24 +584,24 @@ class ArrayMemoryviewTest(unittest.TestCase,
class BytesMemorySliceTest(unittest.TestCase,
BaseMemorySliceTests, BaseBytesMemoryTests):
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_gc(self):
super().test_gc()
pass
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: False is not true : <test.test_memoryview.MyObject object at 0x84ecb13e0>
def test_gc(self):
return super().test_gc()
class ArrayMemorySliceTest(unittest.TestCase,
BaseMemorySliceTests, BaseArrayMemoryTests):
pass
class BytesMemorySliceSliceTest(unittest.TestCase,
BaseMemorySliceSliceTests, BaseBytesMemoryTests):
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_gc(self):
super().test_gc()
pass
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: False is not true : <test.test_memoryview.MyObject object at 0x84ddca1c0>
def test_gc(self):
return super().test_gc()
class ArrayMemorySliceSliceTest(unittest.TestCase,
BaseMemorySliceSliceTests, BaseArrayMemoryTests):
pass
@@ -544,6 +627,14 @@ class OtherTest(unittest.TestCase):
m[2:] = memoryview(p6).cast(format)[2:]
self.assertEqual(d.value, 0.6)
def test_half_float(self):
half_data = struct.pack('eee', 0.0, -1.5, 1.5)
float_data = struct.pack('fff', 0.0, -1.5, 1.5)
half_view = memoryview(half_data).cast('e')
float_view = memoryview(float_data).cast('f')
self.assertEqual(half_view.nbytes * 2, float_view.nbytes)
self.assertListEqual(half_view.tolist(), float_view.tolist())
def test_memoryview_hex(self):
# Issue #9951: memoryview.hex() segfaults with non-contiguous buffers.
x = b'0' * 200000
@@ -551,6 +642,26 @@ class OtherTest(unittest.TestCase):
m2 = m1[::-1]
self.assertEqual(m2.hex(), '30' * 200000)
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: Unexpected keyword argument sep
def test_memoryview_hex_separator(self):
x = bytes(range(97, 102))
m1 = memoryview(x)
m2 = m1[::-1]
self.assertEqual(m2.hex(':'), '65:64:63:62:61')
self.assertEqual(m2.hex(':', 2), '65:6463:6261')
self.assertEqual(m2.hex(':', -2), '6564:6362:61')
self.assertEqual(m2.hex(sep=':', bytes_per_sep=2), '65:6463:6261')
self.assertEqual(m2.hex(sep=':', bytes_per_sep=-2), '6564:6362:61')
for bytes_per_sep in 5, -5, 2**31-1, -(2**31-1):
with self.subTest(bytes_per_sep=bytes_per_sep):
self.assertEqual(m2.hex(':', bytes_per_sep), '6564636261')
for bytes_per_sep in 2**31, -2**31, 2**1000, -2**1000:
with self.subTest(bytes_per_sep=bytes_per_sep):
try:
self.assertEqual(m2.hex(':', bytes_per_sep), '6564636261')
except OverflowError:
pass
def test_copy(self):
m = memoryview(b'abc')
with self.assertRaises(TypeError):
@@ -562,8 +673,7 @@ class OtherTest(unittest.TestCase):
with self.assertRaises(TypeError):
pickle.dumps(m, proto)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: ValueError not raised
def test_use_released_memory(self):
# gh-92888: Previously it was possible to use a memoryview even after
# backing buffer is freed in certain cases. This tests that those
@@ -666,5 +776,56 @@ class OtherTest(unittest.TestCase):
m[0] = MyBool()
self.assertEqual(ba[:8], b'\0'*8)
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'memoryview' object has no attribute '__buffer__'
def test_buffer_reference_loop(self):
m = memoryview(b'abc').__buffer__(0)
o = MyObject()
o.m = m
o.o = o
wr = weakref.ref(o)
del m, o
gc.collect()
self.assertIsNone(wr())
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'pickle' has no attribute 'PickleBuffer'
def test_picklebuffer_reference_loop(self):
pb = pickle.PickleBuffer(memoryview(b'abc'))
o = MyObject()
o.pb = pb
o.o = o
wr = weakref.ref(o)
del pb, o
gc.collect()
self.assertIsNone(wr())
@threading_helper.requires_working_threading()
@support.requires_resource("cpu")
class RacingTest(unittest.TestCase):
def test_racing_getbuf_and_releasebuf(self):
"""Repeatly access the memoryview for racing."""
try:
from multiprocessing.managers import SharedMemoryManager
except ImportError:
self.skipTest("Test requires multiprocessing")
from threading import Thread, Event
start = Event()
with SharedMemoryManager() as smm:
obj = smm.ShareableList(range(100))
def test():
# Issue gh-127085, the `ShareableList.count` is just a
# convenient way to mess the `exports` counter of `memoryview`,
# this issue has no direct relation with `ShareableList`.
start.wait(support.SHORT_TIMEOUT)
for i in range(10):
obj.count(1)
threads = [Thread(target=test) for _ in range(10)]
with threading_helper.start_threads(threads):
start.set()
del obj
if __name__ == "__main__":
unittest.main()
+73 -7
View File
@@ -617,13 +617,22 @@ impl PyMemoryView {
#[pygetset]
fn suboffsets(&self, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
self.try_not_released(vm)?;
Ok(vm.ctx.new_tuple(
self.desc
.dim_desc
.iter()
.map(|(_, _, suboffset)| suboffset.to_pyobject(vm))
.collect(),
))
let has_suboffsets = self
.desc
.dim_desc
.iter()
.any(|(_, _, suboffset)| *suboffset != 0);
if has_suboffsets {
Ok(vm.ctx.new_tuple(
self.desc
.dim_desc
.iter()
.map(|(_, _, suboffset)| suboffset.to_pyobject(vm))
.collect(),
))
} else {
Ok(vm.ctx.empty_tuple.clone())
}
}
#[pygetset]
@@ -740,6 +749,63 @@ impl PyMemoryView {
self.contiguous_or_collect(|x| bytes_to_hex(x, sep, bytes_per_sep, vm))
}
#[pymethod]
fn count(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> {
self.try_not_released(vm)?;
if self.desc.ndim() != 1 {
return Err(
vm.new_not_implemented_error("multi-dimensional sub-views are not implemented")
);
}
let len = self.desc.dim_desc[0].0;
let mut count = 0;
for i in 0..len {
let item = self.getitem_by_idx(i as isize, vm)?;
if vm.bool_eq(&item, &value)? {
count += 1;
}
}
Ok(count)
}
#[pymethod]
fn index(
&self,
value: PyObjectRef,
start: OptionalArg<isize>,
stop: OptionalArg<isize>,
vm: &VirtualMachine,
) -> PyResult<usize> {
self.try_not_released(vm)?;
if self.desc.ndim() != 1 {
return Err(
vm.new_not_implemented_error("multi-dimensional sub-views are not implemented")
);
}
let len = self.desc.dim_desc[0].0;
let start = start.unwrap_or(0);
let stop = stop.unwrap_or(len as isize);
let start = if start < 0 {
(start + len as isize).max(0) as usize
} else {
(start as usize).min(len)
};
let stop = if stop < 0 {
(stop + len as isize).max(0) as usize
} else {
(stop as usize).min(len)
};
for i in start..stop {
let item = self.getitem_by_idx(i as isize, vm)?;
if vm.bool_eq(&item, &value)? {
return Ok(i);
}
}
Err(vm.new_value_error("memoryview.index(x): x not in memoryview"))
}
fn cast_to_1d(&self, format: PyStrRef, vm: &VirtualMachine) -> PyResult<Self> {
let format_spec = Self::parse_format(format.as_str(), vm)?;
let itemsize = format_spec.size();