| import errno | |
| import os | |
| import re | |
| import sys | |
| import unittest | |
| import textwrap | |
| from test import support | |
| from test.support import import_helper, force_not_colorized | |
| from test.support.os_helper import TESTFN, TESTFN_UNDECODABLE | |
| from test.support.script_helper import assert_python_failure, assert_python_ok | |
| from test.support.testcase import ExceptionIsLikeMixin | |
| from .test_misc import decode_stderr | |
| # Skip this test if the _testcapi or _testinternalcapi module isn't available. | |
| _testcapi = import_helper.import_module('_testcapi') | |
| _testinternalcapi = import_helper.import_module('_testinternalcapi') | |
| 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) | |
| def test_warn_with_stacklevel(self): | |
| code = textwrap.dedent('''\ | |
| import _testcapi | |
| def foo(): | |
| _testcapi.function_set_warning() | |
| foo() # line 6 | |
| foo() # line 9 | |
| ''') | |
| proc = assert_python_ok("-c", code) | |
| warnings = proc.err.splitlines() | |
| self.assertEqual(warnings, [ | |
| b'<string>:6: RuntimeWarning: Testing PyErr_WarnEx', | |
| b'<string>:9: RuntimeWarning: Testing PyErr_WarnEx', | |
| ]) | |
| def test_warn_during_finalization(self): | |
| code = textwrap.dedent('''\ | |
| import _testcapi | |
| class Foo: | |
| def foo(self): | |
| _testcapi.function_set_warning() | |
| def __del__(self): | |
| self.foo() | |
| ref = Foo() | |
| ''') | |
| proc = assert_python_ok("-c", code) | |
| warnings = proc.err.splitlines() | |
| # Due to the finalization of the interpreter, the source will be omitted | |
| # because the ``warnings`` module cannot be imported at this time | |
| self.assertEqual(warnings, [ | |
| b'<string>:7: RuntimeWarning: Testing PyErr_WarnEx', | |
| ]) | |
| def test__pyerr_setkeyerror(self): | |
| # Test _PyErr_SetKeyError() | |
| _pyerr_setkeyerror = _testinternalcapi._pyerr_setkeyerror | |
| for arg in ( | |
| "key", | |
| # check that a tuple argument is not unpacked | |
| (1, 2, 3), | |
| # PyErr_SetObject(exc_type, exc_value) uses exc_value if it's | |
| # already an exception, but _PyErr_SetKeyError() always creates a | |
| # new KeyError. | |
| KeyError('arg'), | |
| ): | |
| with self.subTest(arg=arg): | |
| with self.assertRaises(KeyError) as cm: | |
| # Test calling _PyErr_SetKeyError() with an exception set | |
| # to check that the function overrides the current | |
| # exception. | |
| _pyerr_setkeyerror(arg) | |
| self.assertEqual(cm.exception.args, (arg,)) | |
| 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=<unknown>') | |
| 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) | |
| @force_not_colorized | |
| def test_err_writeunraisable_lines(self): | |
| writeunraisable = _testcapi.err_writeunraisable | |
| 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) | |
| @force_not_colorized | |
| def test_err_formatunraisable_lines(self): | |
| formatunraisable = _testcapi.err_formatunraisable | |
| 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 TestUnicodeTranslateError(UnicodeTranslateError): | |
| # UnicodeTranslateError takes 4 arguments instead of 5, | |
| # so we just make a UnicodeTranslateError class that is | |
| # compatible with the UnicodeError.__init__. | |
| def __init__(self, encoding, *args, **kwargs): | |
| super().__init__(*args, **kwargs) | |
| class TestUnicodeError(unittest.TestCase): | |
| def _check_no_crash(self, exc): | |
| # ensure that the __str__() method does not crash | |
| _ = str(exc) | |
| def test_unicode_encode_error_get_start(self): | |
| get_start = _testcapi.unicode_encode_get_start | |
| self._test_unicode_error_get_start('x', UnicodeEncodeError, get_start) | |
| def test_unicode_decode_error_get_start(self): | |
| get_start = _testcapi.unicode_decode_get_start | |
| self._test_unicode_error_get_start(b'x', UnicodeDecodeError, get_start) | |
| def test_unicode_translate_error_get_start(self): | |
| get_start = _testcapi.unicode_translate_get_start | |
| self._test_unicode_error_get_start('x', TestUnicodeTranslateError, get_start) | |
| def _test_unicode_error_get_start(self, literal, exc_type, get_start): | |
| for obj_len, start, c_start in [ | |
| # normal cases | |
| (5, 0, 0), | |
| (5, 1, 1), | |
| (5, 2, 2), | |
| # out of range start is clamped to max(0, obj_len - 1) | |
| (0, 0, 0), | |
| (0, 1, 0), | |
| (0, 10, 0), | |
| (5, 5, 4), | |
| (5, 10, 4), | |
| # negative values are allowed but clipped in the getter | |
| (0, -1, 0), | |
| (1, -1, 0), | |
| (2, -1, 0), | |
| (2, -2, 0), | |
| ]: | |
| obj = literal * obj_len | |
| with self.subTest(obj, exc_type=exc_type, start=start): | |
| exc = exc_type('utf-8', obj, start, obj_len, 'reason') | |
| self.assertEqual(get_start(exc), c_start) | |
| self._check_no_crash(exc) | |
| def test_unicode_encode_error_set_start(self): | |
| set_start = _testcapi.unicode_encode_set_start | |
| self._test_unicode_error_set_start('x', UnicodeEncodeError, set_start) | |
| def test_unicode_decode_error_set_start(self): | |
| set_start = _testcapi.unicode_decode_set_start | |
| self._test_unicode_error_set_start(b'x', UnicodeDecodeError, set_start) | |
| def test_unicode_translate_error_set_start(self): | |
| set_start = _testcapi.unicode_translate_set_start | |
| self._test_unicode_error_set_start('x', TestUnicodeTranslateError, set_start) | |
| def _test_unicode_error_set_start(self, literal, exc_type, set_start): | |
| obj_len = 5 | |
| obj = literal * obj_len | |
| for new_start in range(-2 * obj_len, 2 * obj_len): | |
| with self.subTest('C-API', obj=obj, exc_type=exc_type, new_start=new_start): | |
| exc = exc_type('utf-8', obj, 0, obj_len, 'reason') | |
| # arbitrary value is allowed in the C API setter | |
| set_start(exc, new_start) | |
| self.assertEqual(exc.start, new_start) | |
| self._check_no_crash(exc) | |
| with self.subTest('Py-API', obj=obj, exc_type=exc_type, new_start=new_start): | |
| exc = exc_type('utf-8', obj, 0, obj_len, 'reason') | |
| # arbitrary value is allowed in the attribute setter | |
| exc.start = new_start | |
| self.assertEqual(exc.start, new_start) | |
| self._check_no_crash(exc) | |
| def test_unicode_encode_error_get_end(self): | |
| get_end = _testcapi.unicode_encode_get_end | |
| self._test_unicode_error_get_end('x', UnicodeEncodeError, get_end) | |
| def test_unicode_decode_error_get_end(self): | |
| get_end = _testcapi.unicode_decode_get_end | |
| self._test_unicode_error_get_end(b'x', UnicodeDecodeError, get_end) | |
| def test_unicode_translate_error_get_end(self): | |
| get_end = _testcapi.unicode_translate_get_end | |
| self._test_unicode_error_get_end('x', TestUnicodeTranslateError, get_end) | |
| def _test_unicode_error_get_end(self, literal, exc_type, get_end): | |
| for obj_len, end, c_end in [ | |
| # normal cases | |
| (5, 0, 1), | |
| (5, 1, 1), | |
| (5, 2, 2), | |
| # out-of-range clipped in [MIN(1, OBJLEN), MAX(MIN(1, OBJLEN), OBJLEN)] | |
| (0, 0, 0), | |
| (0, 1, 0), | |
| (0, 10, 0), | |
| (1, 1, 1), | |
| (1, 2, 1), | |
| (5, 5, 5), | |
| (5, 5, 5), | |
| (5, 10, 5), | |
| # negative values are allowed but clipped in the getter | |
| (0, -1, 0), | |
| (1, -1, 1), | |
| (2, -1, 1), | |
| (2, -2, 1), | |
| ]: | |
| obj = literal * obj_len | |
| with self.subTest(obj, exc_type=exc_type, end=end): | |
| exc = exc_type('utf-8', obj, 0, end, 'reason') | |
| self.assertEqual(get_end(exc), c_end) | |
| self._check_no_crash(exc) | |
| def test_unicode_encode_error_set_end(self): | |
| set_end = _testcapi.unicode_encode_set_end | |
| self._test_unicode_error_set_end('x', UnicodeEncodeError, set_end) | |
| def test_unicode_decode_error_set_end(self): | |
| set_end = _testcapi.unicode_decode_set_end | |
| self._test_unicode_error_set_end(b'x', UnicodeDecodeError, set_end) | |
| def test_unicode_translate_error_set_end(self): | |
| set_end = _testcapi.unicode_translate_set_end | |
| self._test_unicode_error_set_end('x', TestUnicodeTranslateError, set_end) | |
| def _test_unicode_error_set_end(self, literal, exc_type, set_end): | |
| obj_len = 5 | |
| obj = literal * obj_len | |
| for new_end in range(-2 * obj_len, 2 * obj_len): | |
| with self.subTest('C-API', obj=obj, exc_type=exc_type, new_end=new_end): | |
| exc = exc_type('utf-8', obj, 0, obj_len, 'reason') | |
| # arbitrary value is allowed in the C API setter | |
| set_end(exc, new_end) | |
| self.assertEqual(exc.end, new_end) | |
| self._check_no_crash(exc) | |
| with self.subTest('Py-API', obj=obj, exc_type=exc_type, new_end=new_end): | |
| exc = exc_type('utf-8', obj, 0, obj_len, 'reason') | |
| # arbitrary value is allowed in the attribute setter | |
| exc.end = new_end | |
| self.assertEqual(exc.end, new_end) | |
| self._check_no_crash(exc) | |
| 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() |