From 9e080145736852881774ad51bcb66775870f08b5 Mon Sep 17 00:00:00 2001 From: ApparentlyPlus Date: Sat, 5 Sep 2026 02:59:13 +0300 Subject: [PATCH] gh-94808: Add test coverage for the PyUnicodeError object and reason C API --- Lib/test/test_capi/test_exceptions.py | 103 ++++++++++++++++++++++++++ Modules/_testcapi/exceptions.c | 96 ++++++++++++++++++++++++ 2 files changed, 199 insertions(+) diff --git a/Lib/test/test_capi/test_exceptions.py b/Lib/test/test_capi/test_exceptions.py index 51ac41e33ac17ab..bd8cc8129703961 100644 --- a/Lib/test/test_capi/test_exceptions.py +++ b/Lib/test/test_capi/test_exceptions.py @@ -591,6 +591,109 @@ def _test_unicode_error_set_end(self, literal, exc_type, set_end): self.assertEqual(exc.end, new_end) self._check_no_crash(exc) + def test_unicode_encode_error_get_object(self): + get_object = _testcapi.unicode_encode_get_object + self._test_unicode_error_get_object('x', UnicodeEncodeError, get_object) + + def test_unicode_decode_error_get_object(self): + get_object = _testcapi.unicode_decode_get_object + self._test_unicode_error_get_object(b'x', UnicodeDecodeError, get_object) + + def test_unicode_translate_error_get_object(self): + get_object = _testcapi.unicode_translate_get_object + self._test_unicode_error_get_object('x', TestUnicodeTranslateError, get_object) + + def _test_unicode_error_get_object(self, literal, exc_type, get_object): + for obj_len in range(5): + obj = literal * obj_len + with self.subTest(obj, exc_type=exc_type): + exc = exc_type('utf-8', obj, 0, obj_len, 'reason') + self.assertEqual(get_object(exc), obj) + # the C API getter agrees with the attribute + self.assertEqual(get_object(exc), exc.object) + self._check_no_crash(exc) + + def test_unicode_encode_error_get_reason(self): + get_reason = _testcapi.unicode_encode_get_reason + self._test_unicode_error_get_reason('x', UnicodeEncodeError, get_reason) + + def test_unicode_decode_error_get_reason(self): + get_reason = _testcapi.unicode_decode_get_reason + self._test_unicode_error_get_reason(b'x', UnicodeDecodeError, get_reason) + + def test_unicode_translate_error_get_reason(self): + get_reason = _testcapi.unicode_translate_get_reason + self._test_unicode_error_get_reason('x', TestUnicodeTranslateError, get_reason) + + def _test_unicode_error_get_reason(self, literal, exc_type, get_reason): + obj_len = 5 + obj = literal * obj_len + for reason in ['reason', '', 'a' * 100, 'non-ascii \xe9']: + with self.subTest(exc_type=exc_type, reason=reason): + exc = exc_type('utf-8', obj, 0, obj_len, reason) + self.assertEqual(get_reason(exc), reason) + # the C API getter agrees with the attribute + self.assertEqual(get_reason(exc), exc.reason) + self._check_no_crash(exc) + + def test_unicode_encode_error_set_reason(self): + set_reason = _testcapi.unicode_encode_set_reason + self._test_unicode_error_set_reason('x', UnicodeEncodeError, set_reason) + + def test_unicode_decode_error_set_reason(self): + set_reason = _testcapi.unicode_decode_set_reason + self._test_unicode_error_set_reason(b'x', UnicodeDecodeError, set_reason) + + def test_unicode_translate_error_set_reason(self): + set_reason = _testcapi.unicode_translate_set_reason + self._test_unicode_error_set_reason('x', TestUnicodeTranslateError, set_reason) + + def _test_unicode_error_set_reason(self, literal, exc_type, set_reason): + obj_len = 5 + obj = literal * obj_len + for new_reason in ['new reason', '', 'a' * 100]: + with self.subTest('C-API', exc_type=exc_type, reason=new_reason): + exc = exc_type('utf-8', obj, 0, obj_len, 'reason') + set_reason(exc, new_reason) + self.assertEqual(exc.reason, new_reason) + self._check_no_crash(exc) + + with self.subTest('Py-API', exc_type=exc_type, reason=new_reason): + exc = exc_type('utf-8', obj, 0, obj_len, 'reason') + exc.reason = new_reason + self.assertEqual(exc.reason, new_reason) + self._check_no_crash(exc) + + def test_unicode_error_object_reason_bad_type(self): + # the C API getters and setters require a UnicodeError instance + bad_objects = [ValueError('x'), 'not an exception', 42, None] + getters = [ + ('UnicodeEncodeError', _testcapi.unicode_encode_get_object), + ('UnicodeDecodeError', _testcapi.unicode_decode_get_object), + ('UnicodeTranslateError', _testcapi.unicode_translate_get_object), + ('UnicodeEncodeError', _testcapi.unicode_encode_get_reason), + ('UnicodeDecodeError', _testcapi.unicode_decode_get_reason), + ('UnicodeTranslateError', _testcapi.unicode_translate_get_reason), + ] + for expect_type, getter in getters: + regex = f'expecting a {expect_type} object' + for bad in bad_objects: + with self.subTest(getter=getter, bad=bad): + with self.assertRaisesRegex(TypeError, regex): + getter(bad) + + setters = [ + ('UnicodeEncodeError', _testcapi.unicode_encode_set_reason), + ('UnicodeDecodeError', _testcapi.unicode_decode_set_reason), + ('UnicodeTranslateError', _testcapi.unicode_translate_set_reason), + ] + for expect_type, setter in setters: + regex = f'expecting a {expect_type} object' + for bad in bad_objects: + with self.subTest(setter=setter, bad=bad): + with self.assertRaisesRegex(TypeError, regex): + setter(bad, 'reason') + class Test_PyUnstable_Exc_PrepReraiseStar(ExceptionIsLikeMixin, unittest.TestCase): diff --git a/Modules/_testcapi/exceptions.c b/Modules/_testcapi/exceptions.c index c0254e044bc2d5f..3e2bc262e2bc563 100644 --- a/Modules/_testcapi/exceptions.c +++ b/Modules/_testcapi/exceptions.c @@ -517,6 +517,93 @@ unicode_translate_set_end(PyObject *Py_UNUSED(module), PyObject *args) Py_RETURN_NONE; } +/* Test PyUnicodeEncodeError_GetObject */ +static PyObject * +unicode_encode_get_object(PyObject *Py_UNUSED(module), PyObject *arg) +{ + return PyUnicodeEncodeError_GetObject(arg); +} + +/* Test PyUnicodeDecodeError_GetObject */ +static PyObject * +unicode_decode_get_object(PyObject *Py_UNUSED(module), PyObject *arg) +{ + return PyUnicodeDecodeError_GetObject(arg); +} + +/* Test PyUnicodeTranslateError_GetObject */ +static PyObject * +unicode_translate_get_object(PyObject *Py_UNUSED(module), PyObject *arg) +{ + return PyUnicodeTranslateError_GetObject(arg); +} + +/* Test PyUnicodeEncodeError_GetReason */ +static PyObject * +unicode_encode_get_reason(PyObject *Py_UNUSED(module), PyObject *arg) +{ + return PyUnicodeEncodeError_GetReason(arg); +} + +/* Test PyUnicodeDecodeError_GetReason */ +static PyObject * +unicode_decode_get_reason(PyObject *Py_UNUSED(module), PyObject *arg) +{ + return PyUnicodeDecodeError_GetReason(arg); +} + +/* Test PyUnicodeTranslateError_GetReason */ +static PyObject * +unicode_translate_get_reason(PyObject *Py_UNUSED(module), PyObject *arg) +{ + return PyUnicodeTranslateError_GetReason(arg); +} + +/* Test PyUnicodeEncodeError_SetReason */ +static PyObject * +unicode_encode_set_reason(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *exc; + const char *reason; + if (!PyArg_ParseTuple(args, "Os", &exc, &reason)) { + return NULL; + } + if (PyUnicodeEncodeError_SetReason(exc, reason) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + +/* Test PyUnicodeDecodeError_SetReason */ +static PyObject * +unicode_decode_set_reason(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *exc; + const char *reason; + if (!PyArg_ParseTuple(args, "Os", &exc, &reason)) { + return NULL; + } + if (PyUnicodeDecodeError_SetReason(exc, reason) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + +/* Test PyUnicodeTranslateError_SetReason */ +static PyObject * +unicode_translate_set_reason(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *exc; + const char *reason; + if (!PyArg_ParseTuple(args, "Os", &exc, &reason)) { + return NULL; + } + if (PyUnicodeTranslateError_SetReason(exc, reason) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + /* * Define the PyRecurdingInfinitelyError_Type */ @@ -572,6 +659,15 @@ static PyMethodDef test_methods[] = { {"unicode_encode_set_end", unicode_encode_set_end, METH_VARARGS}, {"unicode_decode_set_end", unicode_decode_set_end, METH_VARARGS}, {"unicode_translate_set_end", unicode_translate_set_end, METH_VARARGS}, + {"unicode_encode_get_object", unicode_encode_get_object, METH_O}, + {"unicode_decode_get_object", unicode_decode_get_object, METH_O}, + {"unicode_translate_get_object", unicode_translate_get_object, METH_O}, + {"unicode_encode_get_reason", unicode_encode_get_reason, METH_O}, + {"unicode_decode_get_reason", unicode_decode_get_reason, METH_O}, + {"unicode_translate_get_reason", unicode_translate_get_reason, METH_O}, + {"unicode_encode_set_reason", unicode_encode_set_reason, METH_VARARGS}, + {"unicode_decode_set_reason", unicode_decode_set_reason, METH_VARARGS}, + {"unicode_translate_set_reason", unicode_translate_set_reason, METH_VARARGS}, {NULL}, };