Skip to content

Commit 176db8d

Browse files
committed
gh-158585: Backport test_capi.test_bytes from main
Backport also _testcapi.call_with_nomemory().
1 parent df75ad4 commit 176db8d

2 files changed

Lines changed: 74 additions & 38 deletions

File tree

‎Lib/test/test_capi/test_bytes.py‎

Lines changed: 39 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,12 @@ def test_fromstringandsize(self):
5959
self.assertEqual(fromstringandsize(b'abc'), b'abc')
6060
self.assertEqual(fromstringandsize(b'abc', 2), b'ab')
6161
self.assertEqual(fromstringandsize(b'abc\0def'), b'abc\0def')
62-
self.assertEqual(fromstringandsize(b'a'), b'a')
63-
self.assertEqual(fromstringandsize(b'a', 1), b'a')
64-
self.assertEqual(fromstringandsize(b'', 0), b'')
65-
self.assertEqual(fromstringandsize(NULL, 0), b'')
62+
63+
# Return singleton for 0 or 1 bytes
64+
self.assertIs(fromstringandsize(b'', 0), b'')
65+
self.assertIs(fromstringandsize(NULL, 0), b'')
66+
self.assertIs(fromstringandsize(b'a', 1), b'a')
67+
6668
self.assertEqual(len(fromstringandsize(NULL, 3)), 3)
6769
self.assertRaises((MemoryError, OverflowError),
6870
fromstringandsize, NULL, PY_SSIZE_T_MAX)
@@ -77,7 +79,10 @@ def test_fromstring(self):
7779
fromstring = _testlimitedcapi.bytes_fromstring
7880

7981
self.assertEqual(fromstring(b'abc\0def'), b'abc')
80-
self.assertEqual(fromstring(b''), b'')
82+
83+
# Return singleton for 0 or 1 bytes
84+
self.assertIs(fromstring(b''), b'')
85+
self.assertIs(fromstring(b'a'), b'a')
8186

8287
# CRASHES fromstring(NULL)
8388

@@ -434,7 +439,7 @@ def test_resize(self):
434439
writer = self.create_writer()
435440
writer.resize(len(b'hello'))
436441
writer.write(0, b'hello')
437-
self.assertEqual(writer.finish(), b'hello')
442+
self.bytes_equal(writer.finish(), b'hello')
438443

439444
writer = self.create_writer()
440445
writer.resize(0) # noop
@@ -445,7 +450,7 @@ def test_resize(self):
445450
writer.resize(len(b'number=123'), )
446451
writer.write(len(b'number='), b'123')
447452
writer.resize(len(b'number=123')) # noop
448-
self.assertEqual(writer.finish(), b'number=123')
453+
self.bytes_equal(writer.finish(), b'number=123')
449454

450455
# Switch from small buffer to large buffer
451456
writer = self.create_writer()
@@ -454,7 +459,7 @@ def test_resize(self):
454459
writer.write(0, b's' * small)
455460
writer.resize(large)
456461
writer.write(small, b'L' * (large - small))
457-
self.assertEqual(writer.finish(),
462+
self.bytes_equal(writer.finish(),
458463
b's' * small + b'L' * (large - small))
459464

460465
# Make sure that it's possible to write after a resize to zero
@@ -463,7 +468,7 @@ def test_resize(self):
463468
writer.resize(self.LARGE_BUFFER)
464469
writer.resize(0)
465470
writer.write_bytes(b'abc', 3)
466-
self.assertEqual(writer.finish(), b'abc')
471+
self.bytes_equal(writer.finish(), b'abc')
467472

468473
# invalid size
469474
for size in (self.SMALL_BUFFER, self.LARGE_BUFFER):
@@ -474,7 +479,7 @@ def test_resize(self):
474479
writer.resize(-1)
475480
with self.assertRaises((MemoryError, OverflowError)):
476481
writer.resize(_testcapi.PY_SSIZE_T_MAX)
477-
self.assertEqual(writer.finish(), b'x' * size)
482+
self.bytes_equal(writer.finish(), b'x' * size)
478483

479484
@support.nomemtest
480485
def test_resize_error(self):
@@ -488,7 +493,7 @@ def test_resize_error(self):
488493
writer.resize(size)
489494
suffix = b'still working'
490495
writer.write_bytes(suffix, -1)
491-
self.assertEqual(writer.finish(), init + suffix)
496+
self.bytes_equal(writer.finish(), init + suffix)
492497

493498
# Note: PyBytesWriter_Resize() leaves the buffer unchanged (no resize)
494499
# if the new size is smaller than the allocated size
@@ -498,7 +503,7 @@ def test_grow(self):
498503
writer = self.create_writer(0)
499504
writer.grow(len(b'number=123'))
500505
writer.write(0, b'number=123')
501-
self.assertEqual(writer.finish(), b'number=123')
506+
self.bytes_equal(writer.finish(), b'number=123')
502507

503508
writer = self.create_writer()
504509
writer.grow(0) # noop
@@ -509,25 +514,7 @@ def test_grow(self):
509514
writer.grow(len(b'123'), )
510515
writer.write(len(b'number='), b'123')
511516
writer.grow(0) # noop
512-
self.assertEqual(writer.finish(), b'number=123')
513-
514-
for size in (self.SMALL_BUFFER, self.LARGE_BUFFER):
515-
with self.subTest(size=size):
516-
# Truncate the last byte
517-
data = b'x' * size
518-
writer = self.create_writer(size)
519-
writer.write(0, data)
520-
self.assertEqual(writer.get_data(), data)
521-
writer.grow(-1)
522-
self.assertEqual(writer.get_data(), data[:-1])
523-
self.assertEqual(writer.finish(), data[:-1])
524-
525-
# Make the buffer empty
526-
writer = self.create_writer(size)
527-
writer.write(0, data)
528-
writer.grow(-size)
529-
self.assertEqual(writer.get_data(), b'')
530-
self.assertEqual(writer.finish(), b'')
517+
self.bytes_equal(writer.finish(), b'number=123')
531518

532519
# Switch from small buffer to large buffer
533520
writer = self.create_writer()
@@ -536,7 +523,7 @@ def test_grow(self):
536523
writer.write(0, b's' * small)
537524
writer.grow(large - small)
538525
writer.write(small, b'L' * (large - small))
539-
self.assertEqual(writer.finish(),
526+
self.bytes_equal(writer.finish(),
540527
b's' * small + b'L' * (large - small))
541528

542529
# invalid size
@@ -548,7 +535,7 @@ def test_grow(self):
548535
writer.grow(-size - 1)
549536
with self.assertRaises(MemoryError):
550537
writer.grow(_testcapi.PY_SSIZE_T_MAX)
551-
self.assertEqual(writer.finish(), b'x' * size)
538+
self.bytes_equal(writer.finish(), b'x' * size)
552539

553540
@support.nomemtest
554541
def test_grow_error(self):
@@ -574,23 +561,37 @@ def test_format_i(self):
574561

575562
writer = self.create_writer()
576563
writer.format_i(b'x=%i, ', 123)
577-
writer.format_i(b'y=%i', 456)
564+
writer.format_i(b'y=%d', 456)
578565
self.bytes_equal(writer.finish(), b'x=123, y=456')
579566

567+
# Test invalid format: "%t" is invalid, stop the parser and copy
568+
# remaining bytes
569+
writer = self.create_writer()
570+
writer.write_bytes(b'prefix ', -1)
571+
writer.format_i(b'y=%._ t %i', 0)
572+
writer.write_bytes(b'.', 1)
573+
self.bytes_equal(writer.finish(), b'prefix y=%._ t %i.')
574+
580575
def test_format_s(self):
581576
# Test PyBytesWriter_Format()
582577
writer = self.create_writer()
583578
writer.format_s(b's=%s', b'Hello World')
584579
self.bytes_equal(writer.finish(), b's=Hello World')
585580

581+
# Test precision
582+
writer = self.create_writer()
583+
writer.format_s(b'%.6s', b'Monty Python')
584+
writer.format_s(b'%.20s', b'3.0')
585+
self.bytes_equal(writer.finish(), b'Monty 3.0')
586+
586587
@support.nomemtest
587588
def test_format_s_memory_error(self):
588589
writer = self.create_writer()
589-
s = b'x' * 500
590+
s = b'x' * self.LARGE_BUFFER
590591
with self.assertRaises(MemoryError):
591-
with support.inject_memory_error_cm():
592-
writer.format_s(b's=%s', s)
592+
_testcapi.call_with_nomemory(0, 0, writer.format_s, b's=%s', s)
593593
writer.write_bytes(b'after', -1)
594+
self.bytes_equal(writer.finish(), b'after')
594595

595596
@threading_helper.requires_working_threading()
596597
def test_thread(self):
@@ -620,7 +621,7 @@ def thread_func(writer, LARGE_BUFFER):
620621

621622
self.assertEqual(size, 10)
622623
self.assertEqual(data, b'x' * 10)
623-
self.assertEqual(writer.finish(), b'x' * 10)
624+
self.bytes_equal(writer.finish(), b'x' * 10)
624625

625626

626627
class BytesWriterTest(BaseWriterTest, unittest.TestCase):

‎Modules/_testcapi/mem.c‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,39 @@ remove_mem_hooks(PyObject *self, PyObject *Py_UNUSED(ignored))
188188
Py_RETURN_NONE;
189189
}
190190

191+
static PyObject *
192+
call_with_nomemory(PyObject *self, PyObject *args)
193+
{
194+
/* Call func(*args) with memory allocation failing as in set_nomemory().
195+
* No bytecode is executed between arming the failure and the call. */
196+
Py_ssize_t nargs = PyTuple_GET_SIZE(args);
197+
if (nargs < 3) {
198+
PyErr_SetString(PyExc_TypeError,
199+
"call_with_nomemory() requires at least 3 arguments");
200+
return NULL;
201+
}
202+
int start = PyLong_AsInt(PyTuple_GET_ITEM(args, 0));
203+
if (start == -1 && PyErr_Occurred()) {
204+
return NULL;
205+
}
206+
int stop = PyLong_AsInt(PyTuple_GET_ITEM(args, 1));
207+
if (stop == -1 && PyErr_Occurred()) {
208+
return NULL;
209+
}
210+
PyObject *func = PyTuple_GET_ITEM(args, 2);
211+
/* PyObject_Call() with a prebuilt tuple does not allocate,
212+
* unlike PyObject_Vectorcall() for a callee without vectorcall. */
213+
PyObject *callargs = PyTuple_GetSlice(args, 3, nargs);
214+
if (callargs == NULL) {
215+
return NULL;
216+
}
217+
fm_set_nomemory(start, stop);
218+
PyObject *res = PyObject_Call(func, callargs, NULL);
219+
fm_remove_hooks();
220+
Py_DECREF(callargs);
221+
return res;
222+
}
223+
191224
static PyObject *
192225
test_setallocators(PyMemAllocatorDomain domain)
193226
{
@@ -874,6 +907,8 @@ static PyMethodDef test_methods[] = {
874907
PyDoc_STR("Remove memory hooks.")},
875908
{"set_nomemory", set_nomemory, METH_VARARGS,
876909
PyDoc_STR("set_nomemory(start:int, stop:int = 0)")},
910+
{"call_with_nomemory", call_with_nomemory, METH_VARARGS,
911+
PyDoc_STR("call_with_nomemory(start:int, stop:int, func, /, *args)")},
877912
{"test_pymem_alloc0", test_pymem_alloc0, METH_NOARGS},
878913
{"test_pymem_setallocators", test_pymem_setallocators, METH_NOARGS},
879914
{"test_pymem_setrawallocators", test_pymem_setrawallocators, METH_NOARGS},

0 commit comments

Comments
 (0)