import errno import os import re import sys import unittest from test import support from test.support import import_helper from test.support.os_helper import TESTFN, TESTFN_UNDECODABLE from test.support.script_helper import assert_python_failure from test.support.testcase import ExceptionIsLikeMixin from .test_misc import decode_stderr # Skip this test if the _testcapi module isn't available. _testcapi = import_helper.import_module('_testcapi') NULL = None class CustomError(Exception): pass class Test_Exceptions(unittest.TestCase): def test_exception(self): raised_exception = ValueError("5") new_exc = TypeError("TEST") try: raise raised_exception except ValueError as e: orig_sys_exception = sys.exception() orig_exception = _testcapi.set_exception(new_exc) new_sys_exception = sys.exception() new_exception = _testcapi.set_exception(orig_exception) reset_sys_exception = sys.exception() self.assertEqual(orig_exception, e) self.assertEqual(orig_exception, raised_exception) self.assertEqual(orig_sys_exception, orig_exception) self.assertEqual(reset_sys_exception, orig_exception) self.assertEqual(new_exception, new_exc) self.assertEqual(new_sys_exception, new_exception) else: self.fail("Exception not raised") def test_exc_info(self): raised_exception = ValueError("5") new_exc = TypeError("TEST") try: raise raised_exception except ValueError as e: tb = e.__traceback__ orig_sys_exc_info = sys.exc_info() orig_exc_info = _testcapi.set_exc_info(new_exc.__class__, new_exc, None) new_sys_exc_info = sys.exc_info() new_exc_info = _testcapi.set_exc_info(*orig_exc_info) reset_sys_exc_info = sys.exc_info() self.assertEqual(orig_exc_info[1], e) self.assertSequenceEqual(orig_exc_info, (raised_exception.__class__, raised_exception, tb)) self.assertSequenceEqual(orig_sys_exc_info, orig_exc_info) self.assertSequenceEqual(reset_sys_exc_info, orig_exc_info) self.assertSequenceEqual(new_exc_info, (new_exc.__class__, new_exc, None)) self.assertSequenceEqual(new_sys_exc_info, new_exc_info) else: self.assertTrue(False) class Test_FatalError(unittest.TestCase): def check_fatal_error(self, code, expected, not_expected=()): with support.SuppressCrashReport(): rc, out, err = assert_python_failure('-sSI', '-c', code) err = decode_stderr(err) self.assertIn('Fatal Python error: _testcapi_fatal_error_impl: MESSAGE\n', err) match = re.search(r'^Extension modules:(.*) \(total: ([0-9]+)\)$', err, re.MULTILINE) if not match: self.fail(f"Cannot find 'Extension modules:' in {err!r}") modules = set(match.group(1).strip().split(', ')) total = int(match.group(2)) for name in expected: self.assertIn(name, modules) for name in not_expected: self.assertNotIn(name, modules) self.assertEqual(len(modules), total) @support.requires_subprocess() def test_fatal_error(self): # By default, stdlib extension modules are ignored, # but not test modules. expected = ('_testcapi',) not_expected = ('sys',) code = 'import _testcapi, sys; _testcapi.fatal_error(b"MESSAGE")' self.check_fatal_error(code, expected, not_expected) # Mark _testcapi as stdlib module, but not sys expected = ('sys',) not_expected = ('_testcapi',) code = """if True: import _testcapi, sys sys.stdlib_module_names = frozenset({"_testcapi"}) _testcapi.fatal_error(b"MESSAGE") """ self.check_fatal_error(code, expected) class Test_ErrSetAndRestore(unittest.TestCase): def test_err_set_raised(self): with self.assertRaises(ValueError): _testcapi.err_set_raised(ValueError()) v = ValueError() try: _testcapi.err_set_raised(v) except ValueError as ex: self.assertIs(v, ex) def test_err_restore(self): with self.assertRaises(ValueError): _testcapi.err_restore(ValueError) with self.assertRaises(ValueError): _testcapi.err_restore(ValueError, 1) with self.assertRaises(ValueError): _testcapi.err_restore(ValueError, 1, None) with self.assertRaises(ValueError): _testcapi.err_restore(ValueError, ValueError()) try: _testcapi.err_restore(KeyError, "hi") except KeyError as k: self.assertEqual("hi", k.args[0]) try: 1/0 except Exception as e: tb = e.__traceback__ with self.assertRaises(ValueError): _testcapi.err_restore(ValueError, 1, tb) with self.assertRaises(TypeError): _testcapi.err_restore(ValueError, 1, 0) try: _testcapi.err_restore(ValueError, 1, tb) except ValueError as v: self.assertEqual(1, v.args[0]) self.assertIs(tb, v.__traceback__.tb_next) def test_set_object(self): # new exception as obj is not an exception with self.assertRaises(ValueError) as e: _testcapi.exc_set_object(ValueError, 42) self.assertEqual(e.exception.args, (42,)) # wraps the exception because unrelated types with self.assertRaises(ValueError) as e: _testcapi.exc_set_object(ValueError, TypeError(1,2,3)) wrapped = e.exception.args[0] self.assertIsInstance(wrapped, TypeError) self.assertEqual(wrapped.args, (1, 2, 3)) # is superclass, so does not wrap with self.assertRaises(PermissionError) as e: _testcapi.exc_set_object(OSError, PermissionError(24)) self.assertEqual(e.exception.args, (24,)) class Meta(type): def __subclasscheck__(cls, sub): 1/0 class Broken(Exception, metaclass=Meta): pass with self.assertRaises(ZeroDivisionError) as e: _testcapi.exc_set_object(Broken, Broken()) def test_set_object_and_fetch(self): class Broken(Exception): def __init__(self, *arg): raise ValueError("Broken __init__") exc = _testcapi.exc_set_object_fetch(Broken, 'abcd') self.assertIsInstance(exc, ValueError) self.assertEqual(exc.__notes__[0], "Normalization failed: type=Broken args='abcd'") class BadArg: def __repr__(self): raise TypeError('Broken arg type') exc = _testcapi.exc_set_object_fetch(Broken, BadArg()) self.assertIsInstance(exc, ValueError) self.assertEqual(exc.__notes__[0], 'Normalization failed: type=Broken args=') def test_set_string(self): """Test PyErr_SetString()""" setstring = _testcapi.err_setstring with self.assertRaises(ZeroDivisionError) as e: setstring(ZeroDivisionError, b'error') self.assertEqual(e.exception.args, ('error',)) with self.assertRaises(ZeroDivisionError) as e: setstring(ZeroDivisionError, 'помилка'.encode()) self.assertEqual(e.exception.args, ('помилка',)) with self.assertRaises(UnicodeDecodeError): setstring(ZeroDivisionError, b'\xff') self.assertRaises(SystemError, setstring, list, b'error') # CRASHES setstring(ZeroDivisionError, NULL) # CRASHES setstring(NULL, b'error') def test_format(self): """Test PyErr_Format()""" import_helper.import_module('ctypes') from ctypes import pythonapi, py_object, c_char_p, c_int name = "PyErr_Format" PyErr_Format = getattr(pythonapi, name) PyErr_Format.argtypes = (py_object, c_char_p,) PyErr_Format.restype = py_object with self.assertRaises(ZeroDivisionError) as e: PyErr_Format(ZeroDivisionError, b'%s %d', b'error', c_int(42)) self.assertEqual(e.exception.args, ('error 42',)) with self.assertRaises(ZeroDivisionError) as e: PyErr_Format(ZeroDivisionError, b'%s', 'помилка'.encode()) self.assertEqual(e.exception.args, ('помилка',)) with self.assertRaisesRegex(OverflowError, 'not in range'): PyErr_Format(ZeroDivisionError, b'%c', c_int(-1)) with self.assertRaisesRegex(ValueError, 'format string'): PyErr_Format(ZeroDivisionError, b'\xff') self.assertRaises(SystemError, PyErr_Format, list, b'error') # CRASHES PyErr_Format(ZeroDivisionError, NULL) # CRASHES PyErr_Format(py_object(), b'error') def test_setfromerrnowithfilename(self): """Test PyErr_SetFromErrnoWithFilename()""" setfromerrnowithfilename = _testcapi.err_setfromerrnowithfilename ENOENT = errno.ENOENT with self.assertRaises(FileNotFoundError) as e: setfromerrnowithfilename(ENOENT, OSError, b'file') self.assertEqual(e.exception.args, (ENOENT, 'No such file or directory')) self.assertEqual(e.exception.errno, ENOENT) self.assertEqual(e.exception.filename, 'file') with self.assertRaises(FileNotFoundError) as e: setfromerrnowithfilename(ENOENT, OSError, os.fsencode(TESTFN)) self.assertEqual(e.exception.filename, TESTFN) if TESTFN_UNDECODABLE: with self.assertRaises(FileNotFoundError) as e: setfromerrnowithfilename(ENOENT, OSError, TESTFN_UNDECODABLE) self.assertEqual(e.exception.filename, os.fsdecode(TESTFN_UNDECODABLE)) with self.assertRaises(FileNotFoundError) as e: setfromerrnowithfilename(ENOENT, OSError, NULL) self.assertIsNone(e.exception.filename) with self.assertRaises(OSError) as e: setfromerrnowithfilename(0, OSError, b'file') self.assertEqual(e.exception.args, (0, 'Error')) self.assertEqual(e.exception.errno, 0) self.assertEqual(e.exception.filename, 'file') with self.assertRaises(ZeroDivisionError) as e: setfromerrnowithfilename(ENOENT, ZeroDivisionError, b'file') self.assertEqual(e.exception.args, (ENOENT, 'No such file or directory', 'file')) # CRASHES setfromerrnowithfilename(ENOENT, NULL, b'error') def test_err_writeunraisable(self): # Test PyErr_WriteUnraisable() writeunraisable = _testcapi.err_writeunraisable firstline = self.test_err_writeunraisable.__code__.co_firstlineno with support.catch_unraisable_exception() as cm: writeunraisable(CustomError('oops!'), hex) self.assertEqual(cm.unraisable.exc_type, CustomError) self.assertEqual(str(cm.unraisable.exc_value), 'oops!') self.assertEqual(cm.unraisable.exc_traceback.tb_lineno, firstline + 6) self.assertIsNone(cm.unraisable.err_msg) self.assertEqual(cm.unraisable.object, hex) with support.catch_unraisable_exception() as cm: writeunraisable(CustomError('oops!'), NULL) self.assertEqual(cm.unraisable.exc_type, CustomError) self.assertEqual(str(cm.unraisable.exc_value), 'oops!') self.assertEqual(cm.unraisable.exc_traceback.tb_lineno, firstline + 15) self.assertIsNone(cm.unraisable.err_msg) self.assertIsNone(cm.unraisable.object) with (support.swap_attr(sys, 'unraisablehook', None), support.captured_stderr() as stderr): writeunraisable(CustomError('oops!'), hex) lines = stderr.getvalue().splitlines() self.assertEqual(lines[0], f'Exception ignored in: {hex!r}') self.assertEqual(lines[1], 'Traceback (most recent call last):') self.assertEqual(lines[-1], f'{__name__}.CustomError: oops!') with (support.swap_attr(sys, 'unraisablehook', None), support.captured_stderr() as stderr): writeunraisable(CustomError('oops!'), NULL) lines = stderr.getvalue().splitlines() self.assertEqual(lines[0], 'Traceback (most recent call last):') self.assertEqual(lines[-1], f'{__name__}.CustomError: oops!') # CRASHES writeunraisable(NULL, hex) # CRASHES writeunraisable(NULL, NULL) def test_err_formatunraisable(self): # Test PyErr_FormatUnraisable() formatunraisable = _testcapi.err_formatunraisable firstline = self.test_err_formatunraisable.__code__.co_firstlineno with support.catch_unraisable_exception() as cm: formatunraisable(CustomError('oops!'), b'Error in %R', []) self.assertEqual(cm.unraisable.exc_type, CustomError) self.assertEqual(str(cm.unraisable.exc_value), 'oops!') self.assertEqual(cm.unraisable.exc_traceback.tb_lineno, firstline + 6) self.assertEqual(cm.unraisable.err_msg, 'Error in []') self.assertIsNone(cm.unraisable.object) with support.catch_unraisable_exception() as cm: formatunraisable(CustomError('oops!'), b'undecodable \xff') self.assertEqual(cm.unraisable.exc_type, CustomError) self.assertEqual(str(cm.unraisable.exc_value), 'oops!') self.assertEqual(cm.unraisable.exc_traceback.tb_lineno, firstline + 15) self.assertIsNone(cm.unraisable.err_msg) self.assertIsNone(cm.unraisable.object) with support.catch_unraisable_exception() as cm: formatunraisable(CustomError('oops!'), NULL) self.assertEqual(cm.unraisable.exc_type, CustomError) self.assertEqual(str(cm.unraisable.exc_value), 'oops!') self.assertEqual(cm.unraisable.exc_traceback.tb_lineno, firstline + 24) self.assertIsNone(cm.unraisable.err_msg) self.assertIsNone(cm.unraisable.object) with (support.swap_attr(sys, 'unraisablehook', None), support.captured_stderr() as stderr): formatunraisable(CustomError('oops!'), b'Error in %R', []) lines = stderr.getvalue().splitlines() self.assertEqual(lines[0], f'Error in []:') self.assertEqual(lines[1], 'Traceback (most recent call last):') self.assertEqual(lines[-1], f'{__name__}.CustomError: oops!') with (support.swap_attr(sys, 'unraisablehook', None), support.captured_stderr() as stderr): formatunraisable(CustomError('oops!'), b'undecodable \xff') lines = stderr.getvalue().splitlines() self.assertEqual(lines[0], 'Traceback (most recent call last):') self.assertEqual(lines[-1], f'{__name__}.CustomError: oops!') with (support.swap_attr(sys, 'unraisablehook', None), support.captured_stderr() as stderr): formatunraisable(CustomError('oops!'), NULL) lines = stderr.getvalue().splitlines() self.assertEqual(lines[0], 'Traceback (most recent call last):') self.assertEqual(lines[-1], f'{__name__}.CustomError: oops!') # CRASHES formatunraisable(NULL, b'Error in %R', []) # CRASHES formatunraisable(NULL, NULL) class Test_PyUnstable_Exc_PrepReraiseStar(ExceptionIsLikeMixin, unittest.TestCase): def setUp(self): super().setUp() try: raise ExceptionGroup("eg", [TypeError('bad type'), ValueError(42)]) except ExceptionGroup as e: self.orig = e def test_invalid_args(self): with self.assertRaisesRegex(TypeError, "orig must be an exception"): _testcapi.unstable_exc_prep_reraise_star(42, [None]) with self.assertRaisesRegex(TypeError, "excs must be a list"): _testcapi.unstable_exc_prep_reraise_star(self.orig, 42) with self.assertRaisesRegex(TypeError, "not an exception"): _testcapi.unstable_exc_prep_reraise_star(self.orig, [TypeError(42), 42]) with self.assertRaisesRegex(ValueError, "orig must be a raised exception"): _testcapi.unstable_exc_prep_reraise_star(ValueError(42), [TypeError(42)]) with self.assertRaisesRegex(ValueError, "orig must be a raised exception"): _testcapi.unstable_exc_prep_reraise_star(ExceptionGroup("eg", [ValueError(42)]), [TypeError(42)]) def test_nothing_to_reraise(self): self.assertEqual( _testcapi.unstable_exc_prep_reraise_star(self.orig, [None]), None) try: raise ValueError(42) except ValueError as e: orig = e self.assertEqual( _testcapi.unstable_exc_prep_reraise_star(orig, [None]), None) def test_reraise_orig(self): orig = self.orig res = _testcapi.unstable_exc_prep_reraise_star(orig, [orig]) self.assertExceptionIsLike(res, orig) def test_raise_orig_parts(self): orig = self.orig match, rest = orig.split(TypeError) test_cases = [ ([match, rest], orig), ([rest, match], orig), ([match], match), ([rest], rest), ([], None), ] for input, expected in test_cases: with self.subTest(input=input): res = _testcapi.unstable_exc_prep_reraise_star(orig, input) self.assertExceptionIsLike(res, expected) def test_raise_with_new_exceptions(self): orig = self.orig match, rest = orig.split(TypeError) new1 = OSError('bad file') new2 = RuntimeError('bad runtime') test_cases = [ ([new1, match, rest], ExceptionGroup("", [new1, orig])), ([match, new1, rest], ExceptionGroup("", [new1, orig])), ([match, rest, new1], ExceptionGroup("", [new1, orig])), ([new1, new2, match, rest], ExceptionGroup("", [new1, new2, orig])), ([new1, match, new2, rest], ExceptionGroup("", [new1, new2, orig])), ([new2, rest, match, new1], ExceptionGroup("", [new2, new1, orig])), ([rest, new2, match, new1], ExceptionGroup("", [new2, new1, orig])), ([new1, new2, rest], ExceptionGroup("", [new1, new2, rest])), ([new1, match, new2], ExceptionGroup("", [new1, new2, match])), ([rest, new2, new1], ExceptionGroup("", [new2, new1, rest])), ([new1, new2], ExceptionGroup("", [new1, new2])), ([new2, new1], ExceptionGroup("", [new2, new1])), ] for (input, expected) in test_cases: with self.subTest(input=input): res = _testcapi.unstable_exc_prep_reraise_star(orig, input) self.assertExceptionIsLike(res, expected) if __name__ == "__main__": unittest.main()