]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.12] gh-108082: C API: Add tests for PyErr_WriteUnraisable() (GH-111455) (GH-111507)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Mon, 30 Oct 2023 17:36:00 +0000 (18:36 +0100)
committerGitHub <noreply@github.com>
Mon, 30 Oct 2023 17:36:00 +0000 (17:36 +0000)
Also document the behavior when called with NULL.
(cherry picked from commit bca330542912532baa33af20a107fcf956cf007a)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Doc/c-api/exceptions.rst
Lib/test/test_capi/test_exceptions.py
Modules/_testcapi/exceptions.c

index 2139da051e0193199f2ca4d72db29f25ff8286a9..f27e2bbfef05c59e5939a97a46e623dd40557bcc 100644 (file)
@@ -88,9 +88,17 @@ Printing and clearing
    The function is called with a single argument *obj* that identifies the context
    in which the unraisable exception occurred. If possible,
    the repr of *obj* will be printed in the warning message.
+   If *obj* is ``NULL``, only the traceback is printed.
 
    An exception must be set when calling this function.
 
+   .. versionchanged:: 3.4
+      Print a traceback. Print only traceback if *obj* is ``NULL``.
+
+   .. versionchanged:: 3.8
+      Use :func:`sys.unraisablehook`.
+
+
 .. c:function:: void PyErr_DisplayException(PyObject *exc)
 
    Print the standard traceback display of ``exc`` to ``sys.stderr``, including
index b96cc7922a0ee711bd382ca2008997ea6751c4ae..1bff65b6559f0b0698f2dfaf9b4a29e884da29f9 100644 (file)
@@ -17,6 +17,10 @@ _testcapi = import_helper.import_module('_testcapi')
 
 NULL = None
 
+class CustomError(Exception):
+    pass
+
+
 class Test_Exceptions(unittest.TestCase):
 
     def test_exception(self):
@@ -270,6 +274,47 @@ class Test_ErrSetAndRestore(unittest.TestCase):
                          (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)
+
 
 class Test_PyUnstable_Exc_PrepReraiseStar(ExceptionIsLikeMixin, unittest.TestCase):
 
index c511c6d071b6f1e2e4bb64e26a35f42f48fcdd66..4e02f5bec1a942c43fc9ce4003e30cadb286cd9e 100644 (file)
@@ -331,6 +331,22 @@ _testcapi_traceback_print_impl(PyObject *module, PyObject *traceback,
     Py_RETURN_NONE;
 }
 
+static PyObject *
+err_writeunraisable(PyObject *Py_UNUSED(module), PyObject *args)
+{
+    PyObject *exc, *obj;
+    if (!PyArg_ParseTuple(args, "OO", &exc, &obj)) {
+        return NULL;
+    }
+    NULLABLE(exc);
+    NULLABLE(obj);
+    if (exc) {
+        PyErr_SetRaisedException(Py_NewRef(exc));
+    }
+    PyErr_WriteUnraisable(obj);
+    Py_RETURN_NONE;
+}
+
 /*[clinic input]
 _testcapi.unstable_exc_prep_reraise_star
     orig: object
@@ -375,6 +391,7 @@ static PyTypeObject PyRecursingInfinitelyError_Type = {
 
 static PyMethodDef test_methods[] = {
     {"err_restore",             err_restore,                     METH_VARARGS},
+    {"err_writeunraisable",     err_writeunraisable,             METH_VARARGS},
     _TESTCAPI_ERR_SET_RAISED_METHODDEF
     _TESTCAPI_EXCEPTION_PRINT_METHODDEF
     _TESTCAPI_FATAL_ERROR_METHODDEF