summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/c-api/exceptions.rst8
-rw-r--r--Lib/test/test_capi/test_exceptions.py45
-rw-r--r--Modules/_testcapi/exceptions.c17
3 files changed, 70 insertions, 0 deletions
diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst
index 2139da0..f27e2bb 100644
--- a/Doc/c-api/exceptions.rst
+++ b/Doc/c-api/exceptions.rst
@@ -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
diff --git a/Lib/test/test_capi/test_exceptions.py b/Lib/test/test_capi/test_exceptions.py
index b96cc79..1bff65b 100644
--- a/Lib/test/test_capi/test_exceptions.py
+++ b/Lib/test/test_capi/test_exceptions.py
@@ -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):
diff --git a/Modules/_testcapi/exceptions.c b/Modules/_testcapi/exceptions.c
index e463e62..aac672a 100644
--- a/Modules/_testcapi/exceptions.c
+++ b/Modules/_testcapi/exceptions.c
@@ -303,6 +303,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
@@ -347,6 +363,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