diff --git a/Lib/test/test_capi/test_exceptions.py b/Lib/test/test_capi/test_exceptions.py index 1bff65b6559f0b..5903b782d9ca8c 100644 --- a/Lib/test/test_capi/test_exceptions.py +++ b/Lib/test/test_capi/test_exceptions.py @@ -3,11 +3,12 @@ import re import sys import unittest +import textwrap 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.os_helper import TESTFN, TESTFN_UNDECODABLE, unlink +from test.support.script_helper import assert_python_failure, assert_python_ok from test.support.testcase import ExceptionIsLikeMixin from .test_misc import decode_stderr @@ -68,6 +69,52 @@ def test_exc_info(self): else: self.assertTrue(False) + def test_warn_with_stacklevel(self): + self.addCleanup(unlink, TESTFN) + with open(TESTFN, 'w', encoding='utf-8') as f: + f.write(textwrap.dedent('''\ + import _testcapi + + def foo(): + _testcapi.function_set_warning() + + foo() # line 6 + + + foo() # line 9 + ''')) + proc = assert_python_ok(TESTFN) + warnings = proc.err.splitlines() + expected = [ + b':6: RuntimeWarning: Testing PyErr_WarnEx', + b' foo() # line 6', + b':9: RuntimeWarning: Testing PyErr_WarnEx', + b' foo() # line 9', + ] + self.assertEqual(len(warnings), len(expected)) + for index, entry in enumerate(warnings): + self.assertIn(expected[index], entry) + + def test_warn_during_finalization(self): + self.addCleanup(unlink, TESTFN) + with open(TESTFN, 'w', encoding='utf8') as f: + f.write(textwrap.dedent('''\ + import _testcapi + + class Foo: + def foo(self): + _testcapi.function_set_warning() + def __del__(self): + self.foo() + + ref = Foo() + ''')) + proc = assert_python_ok(TESTFN) + warnings = proc.err.splitlines() + # Due to the finalization of the interpreter, the source will be ommited + # because the ``warnings`` module cannot be imported at this time + self.assertEqual(len(warnings), 1) + self.assertIn(b':7: RuntimeWarning: Testing PyErr_WarnEx', warnings[0]) class Test_FatalError(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-05-16-23-02-03.gh-issue-119049.qpd_S-.rst b/Misc/NEWS.d/next/Core and Builtins/2024-05-16-23-02-03.gh-issue-119049.qpd_S-.rst new file mode 100644 index 00000000000000..1d7aad8d1e5be6 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-05-16-23-02-03.gh-issue-119049.qpd_S-.rst @@ -0,0 +1,2 @@ +Fix displaying the source line for warnings created by the C API if the +:mod:`warnings` module had not yet been imported. diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index edd137f30ca9ba..7068fe5ff48cd2 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3230,6 +3230,15 @@ test_atexit(PyObject *self, PyObject *Py_UNUSED(args)) static PyObject *test_buildvalue_issue38913(PyObject *, PyObject *); +static PyObject * +function_set_warning(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + if (PyErr_WarnEx(PyExc_RuntimeWarning, "Testing PyErr_WarnEx", 2)) { + return NULL; + } + Py_RETURN_NONE; +} + static PyMethodDef TestMethods[] = { {"set_errno", set_errno, METH_VARARGS}, {"test_config", test_config, METH_NOARGS}, @@ -3369,6 +3378,7 @@ static PyMethodDef TestMethods[] = { {"function_get_kw_defaults", function_get_kw_defaults, METH_O, NULL}, {"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL}, {"test_atexit", test_atexit, METH_NOARGS}, + {"function_set_warning", function_set_warning, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Python/_warnings.c b/Python/_warnings.c index 1f91edbf5cb5dc..e04607d44442a9 100644 --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -571,10 +571,9 @@ call_show_warning(PyThreadState *tstate, PyObject *category, PyObject *show_fn, *msg, *res, *warnmsg_cls = NULL; PyInterpreterState *interp = tstate->interp; - /* If the source parameter is set, try to get the Python implementation. - The Python implementation is able to log the traceback where the source + /* The Python implementation is able to log the traceback where the source was allocated, whereas the C implementation doesn't. */ - show_fn = GET_WARNINGS_ATTR(interp, _showwarnmsg, source != NULL); + show_fn = GET_WARNINGS_ATTR(interp, _showwarnmsg, 1); if (show_fn == NULL) { if (PyErr_Occurred()) return -1;