Skip to content

Commit c1508fb

Browse files
authored
[3.15] gh-157242: Add support.inject_memory_error() function (#159553)
* gh-157242: Add support.inject_memory_error() function (#157431) Add inject_memory_error() and memory_error_cm() functions to test.support to inject memory errors: make the memory allocator fail. (cherry picked from commit 9a1733d) * gh-157242: Use support.inject_memory_error_cm() in tests (#157811) Rename support.memory_error_cm() to support.inject_memory_error_cm(). (cherry picked from commit a92e0f9) * Use support.inject_memory_error_cm()
1 parent 251278d commit c1508fb

14 files changed

Lines changed: 78 additions & 93 deletions

File tree

‎Lib/test/support/__init__.py‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3455,6 +3455,22 @@ def built_with_c_assertions():
34553455
return bool(_testlimitedcapi._py_getbuiltwithassert())
34563456

34573457

3458+
def inject_memory_error(start=0, stop=0):
3459+
"""
3460+
Memory allocation fails after 'start' allocation requests, and until 'stop'
3461+
allocation requests except when 'stop' is negative or equal to 0 (default)
3462+
in which case allocation failures never stop.
3463+
3464+
Raise SkipTest if the _testcapi extension module is missing
3465+
"""
3466+
try:
3467+
import _testcapi
3468+
except ImportError:
3469+
raise unittest.SkipTest("_testcapi required")
3470+
3471+
_testcapi.set_nomemory(start, stop)
3472+
3473+
34583474
@contextlib.contextmanager
34593475
def inject_memory_error_cm(start=0, stop=0):
34603476
"""

‎Lib/test/test_atexit.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,14 +197,14 @@ def test_atexit_with_low_memory(self):
197197
# callback doesn't cause an infinite loop during finalization.
198198
code = textwrap.dedent("""
199199
import atexit
200-
import _testcapi
200+
from test.support import inject_memory_error
201201
202202
def callback():
203203
print("hello")
204204
205205
atexit.register(callback)
206206
# Simulate low memory condition
207-
_testcapi.set_nomemory(0)
207+
inject_memory_error()
208208
""")
209209

210210
with os_helper.temp_dir() as temp_dir:

‎Lib/test/test_bytes.py‎

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,10 @@ def __index__(self):
5151

5252

5353
@contextlib.contextmanager
54-
def inject_memory_error(testcase, start):
55-
# Raise SkipTest if _testcapi extension module is missing
56-
_testcapi = import_helper.import_module('_testcapi')
57-
54+
def inject_memory_error(testcase, start=0):
5855
with testcase.assertRaises(MemoryError):
59-
try:
60-
_testcapi.set_nomemory(start)
56+
with support.inject_memory_error_cm(start):
6157
yield
62-
finally:
63-
_testcapi.remove_mem_hooks()
6458

6559

6660
class BaseBytesTest:
@@ -1607,7 +1601,7 @@ def test_resize_error(self):
16071601
del ba[:offset]
16081602
else:
16091603
expected = ba.copy()
1610-
with inject_memory_error(self, 0):
1604+
with inject_memory_error(self):
16111605
ba.resize(1024)
16121606
self.assertEqual(ba, expected)
16131607

@@ -1618,7 +1612,7 @@ def test_resize_error(self):
16181612
del ba[:offset]
16191613
else:
16201614
expected = ba.copy()
1621-
with inject_memory_error(self, 0):
1615+
with inject_memory_error(self):
16221616
ba.resize(1)
16231617
self.assertEqual(ba, expected)
16241618

@@ -1691,21 +1685,20 @@ def test_take_bytes_error(self):
16911685
# gh-157242: If bytearray.take_bytes() fails (MemoryError),
16921686
# the bytearray must be left unchanged.
16931687

1694-
for logical_offset, to_take, mem_errors in (
1688+
for logical_offset, to_take, start_list in (
16951689
(True, 5, (0, 1)),
16961690
(False, 5, (0, 1)),
16971691
(True, None, (0,)),
16981692
):
1699-
for mem_error in mem_errors:
1700-
with self.subTest(logical_offset=logical_offset,
1701-
to_take=to_take, mem_error=mem_error):
1693+
for start in start_list:
1694+
with self.subTest(logical_offset=logical_offset, start=start):
17021695
ba = bytearray(b'0123456789')
17031696
if logical_offset:
17041697
expected = ba[3:]
17051698
del ba[:3]
17061699
else:
17071700
expected = ba.copy()
1708-
with inject_memory_error(self, mem_error):
1701+
with inject_memory_error(self, start):
17091702
ba.take_bytes(to_take)
17101703
self.assertEqual(ba, expected)
17111704

‎Lib/test/test_capi/test_bytes.py‎

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,23 @@ def test_resize(self):
476476
writer.resize(_testcapi.PY_SSIZE_T_MAX)
477477
self.assertEqual(writer.finish(), b'x' * size)
478478

479+
@support.nomemtest
480+
def test_resize_error(self):
481+
# Test PyBytesWriter_Resize() error
482+
init = b'x' * self.LARGE_BUFFER
483+
writer = self.create_writer(len(init))
484+
writer.write(0, init)
485+
size = len(init) + 100
486+
with self.assertRaises(MemoryError):
487+
with support.inject_memory_error_cm():
488+
writer.resize(size)
489+
suffix = b'still working'
490+
writer.write_bytes(suffix, -1)
491+
self.assertEqual(writer.finish(), init + suffix)
492+
493+
# Note: PyBytesWriter_Resize() leaves the buffer unchanged (no resize)
494+
# if the new size is smaller than the allocated size
495+
479496
def test_grow(self):
480497
# Test PyBytesWriter_Grow()
481498
writer = self.create_writer(0)
@@ -534,24 +551,20 @@ def test_grow(self):
534551
self.assertEqual(writer.finish(), b'x' * size)
535552

536553
@support.nomemtest
537-
def test_resize_error(self):
538-
# Test PyBytesWriter_Resize() error
554+
def test_grow_error(self):
555+
# Test PyBytesWriter_Grow() error
539556
init = b'x' * self.LARGE_BUFFER
540557
writer = self.create_writer(len(init))
541558
writer.write(0, init)
542-
size = len(init) + 100
543-
try:
544-
with self.assertRaises(MemoryError):
545-
_testcapi.set_nomemory(0)
546-
writer.resize(size)
547-
finally:
548-
_testcapi.remove_mem_hooks()
559+
with self.assertRaises(MemoryError):
560+
with support.inject_memory_error_cm():
561+
writer.grow(100)
549562
suffix = b'still working'
550563
writer.write_bytes(suffix, -1)
551564
self.bytes_equal(writer.finish(), init + suffix)
552565

553-
# Note: PyBytesWriter_Resize() leaves the buffer unchanged (no resize)
554-
# if the new size is smaller than the allocated size
566+
# Note: PyBytesWriter_Grow() leaves the buffer unchanged (no resize)
567+
# if grow is negative.
555568

556569
def test_format_i(self):
557570
# Test PyBytesWriter_Format()
@@ -575,11 +588,8 @@ def test_format_s_memory_error(self):
575588
writer = self.create_writer()
576589
s = b'x' * 500
577590
with self.assertRaises(MemoryError):
578-
try:
579-
_testcapi.set_nomemory(0, 0)
591+
with support.inject_memory_error_cm():
580592
writer.format_s(b's=%s', s)
581-
finally:
582-
_testcapi.remove_mem_hooks()
583593
writer.write_bytes(b'after', -1)
584594

585595
@threading_helper.requires_working_threading()

‎Lib/test/test_class.py‎

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,8 +1016,6 @@ class C:
10161016
@support.nomemtest
10171017
@isolation.runInSubprocess()
10181018
def test_detach_materialized_dict_no_memory(self):
1019-
import _testcapi
1020-
10211019
class A:
10221020
def __init__(self):
10231021
self.a = 1
@@ -1032,11 +1030,8 @@ def __init__(self):
10321030
d = a.__dict__
10331031
try:
10341032
with support.catch_unraisable_exception() as ex:
1035-
_testcapi.set_nomemory(n, n + 1)
1036-
try:
1033+
with support.inject_memory_error_cm(n, n + 1):
10371034
del a
1038-
finally:
1039-
_testcapi.remove_mem_hooks()
10401035
exc_type = ex.unraisable and ex.unraisable.exc_type
10411036
except MemoryError:
10421037
# The failing allocation was not in the deallocation code.

‎Lib/test/test_dict.py‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -461,15 +461,11 @@ def test_copy_noncompact(self):
461461

462462
@support.nomemtest
463463
def test_copy_empty_no_memory(self):
464-
import _testcapi
465464
d = {}
466465
d_copy = d.copy
467466
with self.assertRaises(MemoryError):
468-
try:
469-
_testcapi.set_nomemory(0)
467+
with support.inject_memory_error_cm():
470468
d_copy()
471-
finally:
472-
_testcapi.remove_mem_hooks()
473469

474470
def test_get(self):
475471
d = {}

‎Lib/test/test_exceptions.py‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1663,14 +1663,14 @@ def test_recursion_normalizing_with_no_memory(self):
16631663
# the size of the list of preallocated MemoryError instances, the
16641664
# Fatal Python error message mentions MemoryError.
16651665
code = """if 1:
1666-
import _testcapi
1666+
from test import support
16671667
class C(): pass
16681668
def recurse(cnt):
16691669
cnt -= 1
16701670
if cnt:
16711671
recurse(cnt)
16721672
else:
1673-
_testcapi.set_nomemory(0)
1673+
support.inject_memory_error()
16741674
C()
16751675
recurse(16)
16761676
"""
@@ -1846,9 +1846,10 @@ def test_unhandled(self):
18461846
@support.nomemtest
18471847
def test_memory_error_in_PyErr_PrintEx(self):
18481848
code = """if 1:
1849-
import _testcapi
1849+
from test import support
1850+
stop = %d
18501851
class C(): pass
1851-
_testcapi.set_nomemory(0, %d)
1852+
support.inject_memory_error(0, stop)
18521853
C()
18531854
"""
18541855

@@ -2013,8 +2014,8 @@ def test_exec_set_nomemory_hang(self):
20132014
warmup_code = "a = list(range(0, 1))\n" * 60
20142015
user_input = warmup_code + dedent("""
20152016
try:
2016-
import _testcapi
2017-
_testcapi.set_nomemory(0)
2017+
from test import support
2018+
support.inject_memory_error()
20182019
b = list(range(1000, 2000))
20192020
except Exception as e:
20202021
import traceback

‎Lib/test/test_interpreters/test_stress.py‎

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,9 @@ def run():
7878

7979
@support.nomemtest
8080
def test_create_interpreter_no_memory(self):
81-
import _testcapi
82-
83-
assertion = self.assertRaises(InterpreterError)
84-
try:
85-
_testcapi.set_nomemory(0, 1)
86-
with assertion:
81+
with self.assertRaises(InterpreterError):
82+
with support.inject_memory_error_cm(0, 1):
8783
_interpreters.create()
88-
finally:
89-
_testcapi.remove_mem_hooks()
9084

9185

9286
if __name__ == '__main__':

‎Lib/test/test_io/test_memoryio.py‎

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import unittest
77
from test import support
8-
from test.support import import_helper
98

109
import gc
1110
import io
@@ -654,18 +653,14 @@ def __buffer__(self, flags):
654653

655654
def test_memory_error(self):
656655
# gh-157242: io.BytesIO() must not close the file on MemoryError
657-
_testcapi = import_helper.import_module('_testcapi')
658656

659657
# write()
660658
stream = self.ioclass()
661659
stream.write(self.buftype('abc'))
660+
data = self.buftype('def')
662661
with self.assertRaises(MemoryError):
663-
try:
664-
data = self.buftype('def')
665-
_testcapi.set_nomemory(0)
662+
with support.inject_memory_error_cm():
666663
stream.write(data)
667-
finally:
668-
_testcapi.remove_mem_hooks()
669664
stream.write(self.buftype('123'))
670665
self.assertEqual(stream.getvalue(), self.buftype('abc123'))
671666

@@ -674,11 +669,8 @@ def test_memory_error(self):
674669
stream = self.ioclass()
675670
stream.write(data)
676671
with self.assertRaises(MemoryError):
677-
try:
678-
_testcapi.set_nomemory(0)
672+
with support.inject_memory_error_cm():
679673
stream.truncate(5)
680-
finally:
681-
_testcapi.remove_mem_hooks()
682674
self.assertEqual(stream.getvalue(), data)
683675

684676

‎Lib/test/test_list.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,11 +329,12 @@ def test_tier2_invalidates_iterator(self):
329329
def test_no_memory(self):
330330
# gh-118331: Make sure we don't crash if list allocation fails
331331
code = textwrap.dedent("""
332-
import _testcapi, sys
332+
from test import support
333+
import sys
333334
# Prime the freelist
334335
l = [None]
335336
del l
336-
_testcapi.set_nomemory(0)
337+
support.inject_memory_error()
337338
l = [None]
338339
""")
339340
rc, _, _ = assert_python_failure("-c", code)

0 commit comments

Comments
 (0)