Skip to content

Commit 2b38a9a

Browse files
gh-93649: Split tracemalloc tests from _testcapimodule.c (#99551)
1 parent 0264f63 commit 2b38a9a

File tree

4 files changed

+250
-229
lines changed

4 files changed

+250
-229
lines changed

Lib/test/test_capi/test_mem.py

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import re
2+
import textwrap
3+
import unittest
4+
5+
6+
from test import support
7+
from test.support import import_helper, requires_subprocess
8+
from test.support.script_helper import assert_python_failure, assert_python_ok
9+
10+
11+
# Skip this test if the _testcapi module isn't available.
12+
_testcapi = import_helper.import_module('_testcapi')
13+
14+
@requires_subprocess()
15+
class PyMemDebugTests(unittest.TestCase):
16+
PYTHONMALLOC = 'debug'
17+
# '0x04c06e0' or '04C06E0'
18+
PTR_REGEX = r'(?:0x)?[0-9a-fA-F]+'
19+
20+
def check(self, code):
21+
with support.SuppressCrashReport():
22+
out = assert_python_failure(
23+
'-c', code,
24+
PYTHONMALLOC=self.PYTHONMALLOC,
25+
# FreeBSD: instruct jemalloc to not fill freed() memory
26+
# with junk byte 0x5a, see JEMALLOC(3)
27+
MALLOC_CONF="junk:false",
28+
)
29+
stderr = out.err
30+
return stderr.decode('ascii', 'replace')
31+
32+
def test_buffer_overflow(self):
33+
out = self.check('import _testcapi; _testcapi.pymem_buffer_overflow()')
34+
regex = (r"Debug memory block at address p={ptr}: API 'm'\n"
35+
r" 16 bytes originally requested\n"
36+
r" The [0-9] pad bytes at p-[0-9] are FORBIDDENBYTE, as expected.\n"
37+
r" The [0-9] pad bytes at tail={ptr} are not all FORBIDDENBYTE \(0x[0-9a-f]{{2}}\):\n"
38+
r" at tail\+0: 0x78 \*\*\* OUCH\n"
39+
r" at tail\+1: 0xfd\n"
40+
r" at tail\+2: 0xfd\n"
41+
r" .*\n"
42+
r"( The block was made by call #[0-9]+ to debug malloc/realloc.\n)?"
43+
r" Data at p: cd cd cd .*\n"
44+
r"\n"
45+
r"Enable tracemalloc to get the memory block allocation traceback\n"
46+
r"\n"
47+
r"Fatal Python error: _PyMem_DebugRawFree: bad trailing pad byte")
48+
regex = regex.format(ptr=self.PTR_REGEX)
49+
regex = re.compile(regex, flags=re.DOTALL)
50+
self.assertRegex(out, regex)
51+
52+
def test_api_misuse(self):
53+
out = self.check('import _testcapi; _testcapi.pymem_api_misuse()')
54+
regex = (r"Debug memory block at address p={ptr}: API 'm'\n"
55+
r" 16 bytes originally requested\n"
56+
r" The [0-9] pad bytes at p-[0-9] are FORBIDDENBYTE, as expected.\n"
57+
r" The [0-9] pad bytes at tail={ptr} are FORBIDDENBYTE, as expected.\n"
58+
r"( The block was made by call #[0-9]+ to debug malloc/realloc.\n)?"
59+
r" Data at p: cd cd cd .*\n"
60+
r"\n"
61+
r"Enable tracemalloc to get the memory block allocation traceback\n"
62+
r"\n"
63+
r"Fatal Python error: _PyMem_DebugRawFree: bad ID: Allocated using API 'm', verified using API 'r'\n")
64+
regex = regex.format(ptr=self.PTR_REGEX)
65+
self.assertRegex(out, regex)
66+
67+
def check_malloc_without_gil(self, code):
68+
out = self.check(code)
69+
expected = ('Fatal Python error: _PyMem_DebugMalloc: '
70+
'Python memory allocator called without holding the GIL')
71+
self.assertIn(expected, out)
72+
73+
def test_pymem_malloc_without_gil(self):
74+
# Debug hooks must raise an error if PyMem_Malloc() is called
75+
# without holding the GIL
76+
code = 'import _testcapi; _testcapi.pymem_malloc_without_gil()'
77+
self.check_malloc_without_gil(code)
78+
79+
def test_pyobject_malloc_without_gil(self):
80+
# Debug hooks must raise an error if PyObject_Malloc() is called
81+
# without holding the GIL
82+
code = 'import _testcapi; _testcapi.pyobject_malloc_without_gil()'
83+
self.check_malloc_without_gil(code)
84+
85+
def check_pyobject_is_freed(self, func_name):
86+
code = textwrap.dedent(f'''
87+
import gc, os, sys, _testcapi
88+
# Disable the GC to avoid crash on GC collection
89+
gc.disable()
90+
try:
91+
_testcapi.{func_name}()
92+
# Exit immediately to avoid a crash while deallocating
93+
# the invalid object
94+
os._exit(0)
95+
except _testcapi.error:
96+
os._exit(1)
97+
''')
98+
assert_python_ok(
99+
'-c', code,
100+
PYTHONMALLOC=self.PYTHONMALLOC,
101+
MALLOC_CONF="junk:false",
102+
)
103+
104+
def test_pyobject_null_is_freed(self):
105+
self.check_pyobject_is_freed('check_pyobject_null_is_freed')
106+
107+
def test_pyobject_uninitialized_is_freed(self):
108+
self.check_pyobject_is_freed('check_pyobject_uninitialized_is_freed')
109+
110+
def test_pyobject_forbidden_bytes_is_freed(self):
111+
self.check_pyobject_is_freed('check_pyobject_forbidden_bytes_is_freed')
112+
113+
def test_pyobject_freed_is_freed(self):
114+
self.check_pyobject_is_freed('check_pyobject_freed_is_freed')
115+
116+
def test_set_nomemory(self):
117+
code = """if 1:
118+
import _testcapi
119+
120+
class C(): pass
121+
122+
# The first loop tests both functions and that remove_mem_hooks()
123+
# can be called twice in a row. The second loop checks a call to
124+
# set_nomemory() after a call to remove_mem_hooks(). The third
125+
# loop checks the start and stop arguments of set_nomemory().
126+
for outer_cnt in range(1, 4):
127+
start = 10 * outer_cnt
128+
for j in range(100):
129+
if j == 0:
130+
if outer_cnt != 3:
131+
_testcapi.set_nomemory(start)
132+
else:
133+
_testcapi.set_nomemory(start, start + 1)
134+
try:
135+
C()
136+
except MemoryError as e:
137+
if outer_cnt != 3:
138+
_testcapi.remove_mem_hooks()
139+
print('MemoryError', outer_cnt, j)
140+
_testcapi.remove_mem_hooks()
141+
break
142+
"""
143+
rc, out, err = assert_python_ok('-c', code)
144+
lines = out.splitlines()
145+
for i, line in enumerate(lines, 1):
146+
self.assertIn(b'MemoryError', out)
147+
*_, count = line.split(b' ')
148+
count = int(count)
149+
self.assertLessEqual(count, i*5)
150+
self.assertGreaterEqual(count, i*5-2)
151+
152+
153+
class PyMemMallocDebugTests(PyMemDebugTests):
154+
PYTHONMALLOC = 'malloc_debug'
155+
156+
157+
@unittest.skipUnless(support.with_pymalloc(), 'need pymalloc')
158+
class PyMemPymallocDebugTests(PyMemDebugTests):
159+
PYTHONMALLOC = 'pymalloc_debug'
160+
161+
162+
@unittest.skipUnless(support.Py_DEBUG, 'need Py_DEBUG')
163+
class PyMemDefaultTests(PyMemDebugTests):
164+
# test default allocator of Python compiled in debug mode
165+
PYTHONMALLOC = ''
166+
167+
168+
if __name__ == "__main__":
169+
unittest.main()

Lib/test/test_capi/test_misc.py

-154
Original file line numberDiff line numberDiff line change
@@ -323,42 +323,6 @@ def test_getitem_with_error(self):
323323
def test_buildvalue_N(self):
324324
_testcapi.test_buildvalue_N()
325325

326-
def test_set_nomemory(self):
327-
code = """if 1:
328-
import _testcapi
329-
330-
class C(): pass
331-
332-
# The first loop tests both functions and that remove_mem_hooks()
333-
# can be called twice in a row. The second loop checks a call to
334-
# set_nomemory() after a call to remove_mem_hooks(). The third
335-
# loop checks the start and stop arguments of set_nomemory().
336-
for outer_cnt in range(1, 4):
337-
start = 10 * outer_cnt
338-
for j in range(100):
339-
if j == 0:
340-
if outer_cnt != 3:
341-
_testcapi.set_nomemory(start)
342-
else:
343-
_testcapi.set_nomemory(start, start + 1)
344-
try:
345-
C()
346-
except MemoryError as e:
347-
if outer_cnt != 3:
348-
_testcapi.remove_mem_hooks()
349-
print('MemoryError', outer_cnt, j)
350-
_testcapi.remove_mem_hooks()
351-
break
352-
"""
353-
rc, out, err = assert_python_ok('-c', code)
354-
lines = out.splitlines()
355-
for i, line in enumerate(lines, 1):
356-
self.assertIn(b'MemoryError', out)
357-
*_, count = line.split(b' ')
358-
count = int(count)
359-
self.assertLessEqual(count, i*5)
360-
self.assertGreaterEqual(count, i*5-2)
361-
362326
def test_mapping_keys_values_items(self):
363327
class Mapping1(dict):
364328
def keys(self):
@@ -1470,124 +1434,6 @@ class Test_testinternalcapi(unittest.TestCase):
14701434
if name.startswith('test_'))
14711435

14721436

1473-
@support.requires_subprocess()
1474-
class PyMemDebugTests(unittest.TestCase):
1475-
PYTHONMALLOC = 'debug'
1476-
# '0x04c06e0' or '04C06E0'
1477-
PTR_REGEX = r'(?:0x)?[0-9a-fA-F]+'
1478-
1479-
def check(self, code):
1480-
with support.SuppressCrashReport():
1481-
out = assert_python_failure(
1482-
'-c', code,
1483-
PYTHONMALLOC=self.PYTHONMALLOC,
1484-
# FreeBSD: instruct jemalloc to not fill freed() memory
1485-
# with junk byte 0x5a, see JEMALLOC(3)
1486-
MALLOC_CONF="junk:false",
1487-
)
1488-
stderr = out.err
1489-
return stderr.decode('ascii', 'replace')
1490-
1491-
def test_buffer_overflow(self):
1492-
out = self.check('import _testcapi; _testcapi.pymem_buffer_overflow()')
1493-
regex = (r"Debug memory block at address p={ptr}: API 'm'\n"
1494-
r" 16 bytes originally requested\n"
1495-
r" The [0-9] pad bytes at p-[0-9] are FORBIDDENBYTE, as expected.\n"
1496-
r" The [0-9] pad bytes at tail={ptr} are not all FORBIDDENBYTE \(0x[0-9a-f]{{2}}\):\n"
1497-
r" at tail\+0: 0x78 \*\*\* OUCH\n"
1498-
r" at tail\+1: 0xfd\n"
1499-
r" at tail\+2: 0xfd\n"
1500-
r" .*\n"
1501-
r"( The block was made by call #[0-9]+ to debug malloc/realloc.\n)?"
1502-
r" Data at p: cd cd cd .*\n"
1503-
r"\n"
1504-
r"Enable tracemalloc to get the memory block allocation traceback\n"
1505-
r"\n"
1506-
r"Fatal Python error: _PyMem_DebugRawFree: bad trailing pad byte")
1507-
regex = regex.format(ptr=self.PTR_REGEX)
1508-
regex = re.compile(regex, flags=re.DOTALL)
1509-
self.assertRegex(out, regex)
1510-
1511-
def test_api_misuse(self):
1512-
out = self.check('import _testcapi; _testcapi.pymem_api_misuse()')
1513-
regex = (r"Debug memory block at address p={ptr}: API 'm'\n"
1514-
r" 16 bytes originally requested\n"
1515-
r" The [0-9] pad bytes at p-[0-9] are FORBIDDENBYTE, as expected.\n"
1516-
r" The [0-9] pad bytes at tail={ptr} are FORBIDDENBYTE, as expected.\n"
1517-
r"( The block was made by call #[0-9]+ to debug malloc/realloc.\n)?"
1518-
r" Data at p: cd cd cd .*\n"
1519-
r"\n"
1520-
r"Enable tracemalloc to get the memory block allocation traceback\n"
1521-
r"\n"
1522-
r"Fatal Python error: _PyMem_DebugRawFree: bad ID: Allocated using API 'm', verified using API 'r'\n")
1523-
regex = regex.format(ptr=self.PTR_REGEX)
1524-
self.assertRegex(out, regex)
1525-
1526-
def check_malloc_without_gil(self, code):
1527-
out = self.check(code)
1528-
expected = ('Fatal Python error: _PyMem_DebugMalloc: '
1529-
'Python memory allocator called without holding the GIL')
1530-
self.assertIn(expected, out)
1531-
1532-
def test_pymem_malloc_without_gil(self):
1533-
# Debug hooks must raise an error if PyMem_Malloc() is called
1534-
# without holding the GIL
1535-
code = 'import _testcapi; _testcapi.pymem_malloc_without_gil()'
1536-
self.check_malloc_without_gil(code)
1537-
1538-
def test_pyobject_malloc_without_gil(self):
1539-
# Debug hooks must raise an error if PyObject_Malloc() is called
1540-
# without holding the GIL
1541-
code = 'import _testcapi; _testcapi.pyobject_malloc_without_gil()'
1542-
self.check_malloc_without_gil(code)
1543-
1544-
def check_pyobject_is_freed(self, func_name):
1545-
code = textwrap.dedent(f'''
1546-
import gc, os, sys, _testcapi
1547-
# Disable the GC to avoid crash on GC collection
1548-
gc.disable()
1549-
try:
1550-
_testcapi.{func_name}()
1551-
# Exit immediately to avoid a crash while deallocating
1552-
# the invalid object
1553-
os._exit(0)
1554-
except _testcapi.error:
1555-
os._exit(1)
1556-
''')
1557-
assert_python_ok(
1558-
'-c', code,
1559-
PYTHONMALLOC=self.PYTHONMALLOC,
1560-
MALLOC_CONF="junk:false",
1561-
)
1562-
1563-
def test_pyobject_null_is_freed(self):
1564-
self.check_pyobject_is_freed('check_pyobject_null_is_freed')
1565-
1566-
def test_pyobject_uninitialized_is_freed(self):
1567-
self.check_pyobject_is_freed('check_pyobject_uninitialized_is_freed')
1568-
1569-
def test_pyobject_forbidden_bytes_is_freed(self):
1570-
self.check_pyobject_is_freed('check_pyobject_forbidden_bytes_is_freed')
1571-
1572-
def test_pyobject_freed_is_freed(self):
1573-
self.check_pyobject_is_freed('check_pyobject_freed_is_freed')
1574-
1575-
1576-
class PyMemMallocDebugTests(PyMemDebugTests):
1577-
PYTHONMALLOC = 'malloc_debug'
1578-
1579-
1580-
@unittest.skipUnless(support.with_pymalloc(), 'need pymalloc')
1581-
class PyMemPymallocDebugTests(PyMemDebugTests):
1582-
PYTHONMALLOC = 'pymalloc_debug'
1583-
1584-
1585-
@unittest.skipUnless(support.Py_DEBUG, 'need Py_DEBUG')
1586-
class PyMemDefaultTests(PyMemDebugTests):
1587-
# test default allocator of Python compiled in debug mode
1588-
PYTHONMALLOC = ''
1589-
1590-
15911437
@unittest.skipIf(_testmultiphase is None, "test requires _testmultiphase module")
15921438
class Test_ModuleStateAccess(unittest.TestCase):
15931439
"""Test access to module start (PEP 573)"""

0 commit comments

Comments
 (0)