Update test_faulthandler from v3.14.2

This commit is contained in:
CPython Developers authored and Jeong, YunWon committed 2026-02-03 13:41:15 +09:00
1 parent 6b62b4cca9
commit 6bf394d249
1 file changed
+95 -77
+95 -77
View File
@@ -22,6 +22,16 @@ if not support.has_subprocess_support:
TIMEOUT = 0.5
STACK_HEADER_STR = r'Stack (most recent call first):'
# Regular expressions
STACK_HEADER = re.escape(STACK_HEADER_STR)
THREAD_NAME = r'( \[.*\])?'
THREAD_ID = fr'Thread 0x[0-9a-f]+{THREAD_NAME}'
THREAD_HEADER = fr'{THREAD_ID} \(most recent call first\):'
CURRENT_THREAD_ID = fr'Current thread 0x[0-9a-f]+{THREAD_NAME}'
CURRENT_THREAD_HEADER = fr'{CURRENT_THREAD_ID} \(most recent call first\):'
def expected_traceback(lineno1, lineno2, header, min_count=1):
regex = header
@@ -45,6 +55,13 @@ def temporary_filename():
finally:
os_helper.unlink(filename)
ADDRESS_EXPR = "0x[0-9a-f]+"
C_STACK_REGEX = [
r"Current thread's C stack trace \(most recent call first\):",
fr'( Binary file ".+"(, at .*(\+|-){ADDRESS_EXPR})? \[{ADDRESS_EXPR}\])|(<.+>)'
]
class FaultHandlerTests(unittest.TestCase):
def get_output(self, code, filename=None, fd=None):
@@ -93,6 +110,7 @@ class FaultHandlerTests(unittest.TestCase):
fd=None, know_current_thread=True,
py_fatal_error=False,
garbage_collecting=False,
c_stack=True,
function='<module>'):
"""
Check that the fault handler for fatal errors is enabled and check the
@@ -100,21 +118,32 @@ class FaultHandlerTests(unittest.TestCase):
Raise an error if the output doesn't match the expected format.
"""
if all_threads:
all_threads_disabled = (
all_threads
and (not sys._is_gil_enabled())
)
if all_threads and not all_threads_disabled:
if know_current_thread:
header = 'Current thread 0x[0-9a-f]+'
header = CURRENT_THREAD_HEADER
else:
header = 'Thread 0x[0-9a-f]+'
header = THREAD_HEADER
else:
header = 'Stack'
header = STACK_HEADER
regex = [f'^{fatal_error}']
if py_fatal_error:
regex.append("Python runtime state: initialized")
regex.append('')
regex.append(fr'{header} \(most recent call first\):')
if garbage_collecting:
regex.append(' Garbage-collecting')
regex.append(fr' File "<string>", line {lineno} in {function}')
if all_threads_disabled and not py_fatal_error:
regex.append("<Cannot show all threads while the GIL is disabled>")
regex.append(fr'{header}')
if support.Py_GIL_DISABLED and py_fatal_error and not know_current_thread:
regex.append(" <tstate is freed>")
else:
if garbage_collecting and not all_threads_disabled:
regex.append(' Garbage-collecting')
regex.append(fr' File "<string>", line {lineno} in {function}')
if c_stack:
regex.extend(C_STACK_REGEX)
regex = '\n'.join(regex)
if other_regex:
@@ -137,8 +166,6 @@ class FaultHandlerTests(unittest.TestCase):
fatal_error = 'Windows fatal exception: %s' % name_regex
self.check_error(code, line_number, fatal_error, **kw)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.skipIf(sys.platform.startswith('aix'),
"the first page of memory is a mapped read-only on AIX")
def test_read_null(self):
@@ -162,8 +189,6 @@ class FaultHandlerTests(unittest.TestCase):
3,
'access violation')
# TODO: RUSTPYTHON, AssertionError: Regex didn't match
@unittest.expectedFailure
@skip_segfault_on_android
def test_sigsegv(self):
self.check_fatal_error("""
@@ -174,8 +199,7 @@ class FaultHandlerTests(unittest.TestCase):
3,
'Segmentation fault')
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Regex didn't match: '(?m)^Fatal Python error: Segmentation fault\n\n<Cannot show all threads while the GIL is disabled>\nStack\\ \\(most\\ recent\\ call\\ first\\):\n File "<string>", line 9 in __del__\nCurrent thread\'s C stack trace \\(most recent call first\\):\n( Binary file ".+"(, at .*(\\+|-)0x[0-9a-f]+)? \\[0x[0-9a-f]+\\])|(<.+>)' not found in 'exit'
@skip_segfault_on_android
def test_gc(self):
# bpo-44466: Detect if the GC is running
@@ -212,8 +236,7 @@ class FaultHandlerTests(unittest.TestCase):
function='__del__',
garbage_collecting=True)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 0 == 0
def test_fatal_error_c_thread(self):
self.check_fatal_error("""
import faulthandler
@@ -226,8 +249,7 @@ class FaultHandlerTests(unittest.TestCase):
func='faulthandler_fatal_error_thread',
py_fatal_error=True)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@support.skip_if_sanitizer("TSAN itercepts SIGABRT", thread=True)
def test_sigabrt(self):
self.check_fatal_error("""
import faulthandler
@@ -237,10 +259,9 @@ class FaultHandlerTests(unittest.TestCase):
3,
'Aborted')
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.skipIf(sys.platform == 'win32',
"SIGFPE cannot be caught on Windows")
@support.skip_if_sanitizer("TSAN itercepts SIGFPE", thread=True)
def test_sigfpe(self):
self.check_fatal_error("""
import faulthandler
@@ -252,6 +273,7 @@ class FaultHandlerTests(unittest.TestCase):
@unittest.skipIf(_testcapi is None, 'need _testcapi')
@unittest.skipUnless(hasattr(signal, 'SIGBUS'), 'need signal.SIGBUS')
@support.skip_if_sanitizer("TSAN itercepts SIGBUS", thread=True)
@skip_segfault_on_android
def test_sigbus(self):
self.check_fatal_error("""
@@ -266,6 +288,7 @@ class FaultHandlerTests(unittest.TestCase):
@unittest.skipIf(_testcapi is None, 'need _testcapi')
@unittest.skipUnless(hasattr(signal, 'SIGILL'), 'need signal.SIGILL')
@support.skip_if_sanitizer("TSAN itercepts SIGILL", thread=True)
@skip_segfault_on_android
def test_sigill(self):
self.check_fatal_error("""
@@ -291,13 +314,9 @@ class FaultHandlerTests(unittest.TestCase):
func='_testcapi_fatal_error_impl',
py_fatal_error=True)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_fatal_error(self):
self.check_fatal_error_func(False)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_fatal_error_without_gil(self):
self.check_fatal_error_func(True)
@@ -316,8 +335,6 @@ class FaultHandlerTests(unittest.TestCase):
'(?:Segmentation fault|Bus error)',
other_regex='unable to raise a stack overflow')
# TODO: RUSTPYTHON
@unittest.expectedFailure
@skip_segfault_on_android
def test_gil_released(self):
self.check_fatal_error("""
@@ -328,8 +345,6 @@ class FaultHandlerTests(unittest.TestCase):
3,
'Segmentation fault')
# TODO: RUSTPYTHON
@unittest.expectedFailure
@skip_segfault_on_android
def test_enable_file(self):
with temporary_filename() as filename:
@@ -343,8 +358,6 @@ class FaultHandlerTests(unittest.TestCase):
'Segmentation fault',
filename=filename)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.skipIf(sys.platform == "win32",
"subprocess doesn't support pass_fds on Windows")
@skip_segfault_on_android
@@ -361,8 +374,6 @@ class FaultHandlerTests(unittest.TestCase):
'Segmentation fault',
fd=fd)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@skip_segfault_on_android
def test_enable_single_thread(self):
self.check_fatal_error("""
@@ -389,8 +400,7 @@ class FaultHandlerTests(unittest.TestCase):
"%r is present in %r" % (not_expected, stderr))
self.assertNotEqual(exitcode, 0)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Cannot find 'Extension modules:' in 'Fatal Python error: Segmentation fault\n\nCurrent thread 0x0000000000004284 (most recent call first):\n File "<string>", line 6 in <module>'
@skip_segfault_on_android
def test_dump_ext_modules(self):
code = """
@@ -511,7 +521,7 @@ class FaultHandlerTests(unittest.TestCase):
else:
lineno = 14
expected = [
'Stack (most recent call first):',
f'{STACK_HEADER_STR}',
' File "<string>", line %s in funcB' % lineno,
' File "<string>", line 17 in funcA',
' File "<string>", line 19 in <module>'
@@ -523,14 +533,11 @@ class FaultHandlerTests(unittest.TestCase):
def test_dump_traceback(self):
self.check_dump_traceback()
# TODO: RUSTPYTHON - binary file write needs different handling
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; - binary file write needs different handling
def test_dump_traceback_file(self):
with temporary_filename() as filename:
self.check_dump_traceback(filename=filename)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.skipIf(sys.platform == "win32",
"subprocess doesn't support pass_fds on Windows")
def test_dump_traceback_fd(self):
@@ -553,7 +560,7 @@ class FaultHandlerTests(unittest.TestCase):
func_name=func_name,
)
expected = [
'Stack (most recent call first):',
f'{STACK_HEADER_STR}',
' File "<string>", line 4 in %s' % truncated,
' File "<string>", line 6 in <module>'
]
@@ -607,28 +614,26 @@ class FaultHandlerTests(unittest.TestCase):
lineno = 10
# When the traceback is dumped, the waiter thread may be in the
# `self.running.set()` call or in `self.stop.wait()`.
regex = r"""
^Thread 0x[0-9a-f]+ \(most recent call first\):
regex = fr"""
^{THREAD_HEADER}
(?: File ".*threading.py", line [0-9]+ in [_a-z]+
){{1,3}} File "<string>", line (?:22|23) in run
File ".*threading.py", line [0-9]+ in _bootstrap_inner
File ".*threading.py", line [0-9]+ in _bootstrap
Current thread 0x[0-9a-f]+ \(most recent call first\):
{CURRENT_THREAD_HEADER}
File "<string>", line {lineno} in dump
File "<string>", line 28 in <module>$
"""
regex = dedent(regex.format(lineno=lineno)).strip()
regex = dedent(regex).strip()
self.assertRegex(output, regex)
self.assertEqual(exitcode, 0)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Regex didn't match: '^Thread 0x[0-9a-f]+( \\[.*\\])? \\(most recent call first\\):\n(?: File ".*threading.py", line [0-9]+ in [_a-z]+\n){1,3} File "<string>", line (?:22|23) in run\n File ".*threading.py", line [0-9]+ in _bootstrap_inner\n File ".*threading.py", line [0-9]+ in _bootstrap\n\nCurrent thread 0x[0-9a-f]+( \\[.*\\])? \\(most recent call first\\):\n File "<string>", line 10 in dump\n File "<string>", line 28 in <module>$' not found in 'Stack (most recent call first):\n File "<string>", line 10 in dump\n File "<string>", line 28 in <module>'
def test_dump_traceback_threads(self):
self.check_dump_traceback_threads(None)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; - TypeError: a bytes-like object is required, not 'str'
def test_dump_traceback_threads_file(self):
with temporary_filename() as filename:
self.check_dump_traceback_threads(filename)
@@ -688,44 +693,38 @@ class FaultHandlerTests(unittest.TestCase):
count = loops
if repeat:
count *= 2
header = r'Timeout \(%s\)!\nThread 0x[0-9a-f]+ \(most recent call first\):\n' % timeout_str
header = (fr'Timeout \({timeout_str}\)!\n'
fr'{THREAD_HEADER}\n')
regex = expected_traceback(17, 26, header, min_count=count)
self.assertRegex(trace, regex)
else:
self.assertEqual(trace, '')
self.assertEqual(exitcode, 0)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Regex didn't match: '^Timeout \\(0:00:00.500000\\)!\\nThread 0x[0-9a-f]+( \\[.*\\])? \\(most recent call first\\):\\n File "<string>", line 17 in func\n File "<string>", line 26 in <module>$' not found in 'Traceback (most recent call last):\n File "<string>", line 26, in <module>\n File "<string>", line 14, in func\nAttributeError: \'NoneType\' object has no attribute \'fileno\''
def test_dump_traceback_later(self):
self.check_dump_traceback_later()
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Regex didn't match: '^Timeout \\(0:00:00.500000\\)!\\nThread 0x[0-9a-f]+( \\[.*\\])? \\(most recent call first\\):\\n File "<string>", line 17 in func\n File "<string>", line 26 in <module>\nTimeout \\(0:00:00.500000\\)!\\nThread 0x[0-9a-f]+( \\[.*\\])? \\(most recent call first\\):\\n File "<string>", line 17 in func\n File "<string>", line 26 in <module>' not found in 'Traceback (most recent call last):\n File "<string>", line 26, in <module>\n File "<string>", line 14, in func\nAttributeError: \'NoneType\' object has no attribute \'fileno\''
def test_dump_traceback_later_repeat(self):
self.check_dump_traceback_later(repeat=True)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; - AttributeError: 'NoneType' object has no attribute 'fileno'
def test_dump_traceback_later_cancel(self):
self.check_dump_traceback_later(cancel=True)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Regex didn't match: '^Timeout \\(0:00:00.500000\\)!\\nThread 0x[0-9a-f]+( \\[.*\\])? \\(most recent call first\\):\\n File "<string>", line 17 in func\n File "<string>", line 26 in <module>$' not found in 'Timeout (00:00:00.500000)!\n<timeout: cannot dump traceback from watchdog thread>'
def test_dump_traceback_later_file(self):
with temporary_filename() as filename:
self.check_dump_traceback_later(filename=filename)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.skipIf(sys.platform == "win32",
"subprocess doesn't support pass_fds on Windows")
def test_dump_traceback_later_fd(self):
with tempfile.TemporaryFile('wb+') as fp:
self.check_dump_traceback_later(fd=fp.fileno())
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Regex didn't match: '^Timeout \\(0:00:00.500000\\)!\\nThread 0x[0-9a-f]+( \\[.*\\])? \\(most recent call first\\):\\n File "<string>", line 17 in func\n File "<string>", line 26 in <module>\nTimeout \\(0:00:00.500000\\)!\\nThread 0x[0-9a-f]+( \\[.*\\])? \\(most recent call first\\):\\n File "<string>", line 17 in func\n File "<string>", line 26 in <module>' not found in 'Traceback (most recent call last):\n File "<string>", line 26, in <module>\n File "<string>", line 14, in func\nAttributeError: \'NoneType\' object has no attribute \'fileno\''
@support.requires_resource('walltime')
def test_dump_traceback_later_twice(self):
self.check_dump_traceback_later(loops=2)
@@ -801,9 +800,9 @@ class FaultHandlerTests(unittest.TestCase):
trace = '\n'.join(trace)
if not unregister:
if all_threads:
regex = r'Current thread 0x[0-9a-f]+ \(most recent call first\):\n'
regex = fr'{CURRENT_THREAD_HEADER}\n'
else:
regex = r'Stack \(most recent call first\):\n'
regex = fr'{STACK_HEADER}\n'
regex = expected_traceback(14, 32, regex)
self.assertRegex(trace, regex)
else:
@@ -813,37 +812,26 @@ class FaultHandlerTests(unittest.TestCase):
else:
self.assertEqual(exitcode, 0)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_register(self):
self.check_register()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_unregister(self):
self.check_register(unregister=True)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_register_file(self):
with temporary_filename() as filename:
self.check_register(filename=filename)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.skipIf(sys.platform == "win32",
"subprocess doesn't support pass_fds on Windows")
def test_register_fd(self):
with tempfile.TemporaryFile('wb+') as fp:
self.check_register(fd=fp.fileno())
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_register_threads(self):
self.check_register(all_threads=True)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@support.skip_if_sanitizer("gh-129825: hangs under TSAN", thread=True)
def test_register_chain(self):
self.check_register(chain=True)
@@ -871,8 +859,6 @@ class FaultHandlerTests(unittest.TestCase):
with self.check_stderr_none():
faulthandler.register(signal.SIGUSR1)
# TODO: RUSTPYTHON, AttributeError: module 'msvcrt' has no attribute 'GetErrorMode'
@unittest.expectedFailure
@unittest.skipUnless(MS_WINDOWS, 'specific to Windows')
def test_raise_exception(self):
for exc, name in (
@@ -985,5 +971,37 @@ class FaultHandlerTests(unittest.TestCase):
_, exitcode = self.get_output(code)
self.assertEqual(exitcode, 0)
def check_c_stack(self, output):
starting_line = output.pop(0)
self.assertRegex(starting_line, C_STACK_REGEX[0])
self.assertGreater(len(output), 0)
for line in output:
with self.subTest(line=line):
if line != '': # Ignore trailing or leading newlines
self.assertRegex(line, C_STACK_REGEX[1])
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 1 != 0
def test_dump_c_stack(self):
code = dedent("""
import faulthandler
faulthandler.dump_c_stack()
""")
output, exitcode = self.get_output(code)
self.assertEqual(exitcode, 0)
self.check_c_stack(output)
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'faulthandler' has no attribute 'dump_c_stack'
def test_dump_c_stack_file(self):
import tempfile
with tempfile.TemporaryFile("w+") as tmp:
faulthandler.dump_c_stack(file=tmp)
tmp.flush() # Just in case
tmp.seek(0)
self.check_c_stack(tmp.read().split("\n"))
if __name__ == "__main__":
unittest.main()