From 9e31b3952f6101ef71ec029481b972169ab0e0f1 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Sun, 5 Sep 2021 16:54:13 +0100 Subject: [PATCH] bpo-41031: Match C and Python code formatting of unprintable exceptions and exceptions in the __main__ module. (GH-28139) --- Lib/test/test_sys.py | 26 ++++++++++----- Lib/test/test_traceback.py | 32 +++++++++++++++++-- Lib/traceback.py | 2 +- .../2021-09-03-12-35-17.bpo-41031.yPSJEs.rst | 1 + Python/errors.c | 4 ++- Python/pythonrun.c | 4 ++- 6 files changed, 55 insertions(+), 14 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2021-09-03-12-35-17.bpo-41031.yPSJEs.rst diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 12305ca95d0..e98803b48f6 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1071,19 +1071,29 @@ class UnraisableHookTest(unittest.TestCase): self.assertTrue(report.endswith("\n")) def test_original_unraisablehook_exception_qualname(self): + # See bpo-41031, bpo-45083. + # Check that the exception is printed with its qualified name + # rather than just classname, and the module names appears + # unless it is one of the hard-coded exclusions. class A: class B: class X(Exception): pass - with test.support.captured_stderr() as stderr, \ - test.support.swap_attr(sys, 'unraisablehook', - sys.__unraisablehook__): - expected = self.write_unraisable_exc( - A.B.X(), "msg", "obj"); - report = stderr.getvalue() - testName = 'test_original_unraisablehook_exception_qualname' - self.assertIn(f"{testName}..A.B.X", report) + for moduleName in 'builtins', '__main__', 'some_module': + with self.subTest(moduleName=moduleName): + A.B.X.__module__ = moduleName + with test.support.captured_stderr() as stderr, \ + test.support.swap_attr(sys, 'unraisablehook', + sys.__unraisablehook__): + expected = self.write_unraisable_exc( + A.B.X(), "msg", "obj"); + report = stderr.getvalue() + self.assertIn(A.B.X.__qualname__, report) + if moduleName in ['builtins', '__main__']: + self.assertNotIn(moduleName + '.', report) + else: + self.assertIn(moduleName + '.', report) def test_original_unraisablehook_wrong_type(self): exc = ValueError(42) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 949adefd76f..363165d06ef 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -172,7 +172,7 @@ class TracebackCases(unittest.TestCase): 1/0 err = traceback.format_exception_only(X, X()) self.assertEqual(len(err), 1) - str_value = '' % X.__name__ + str_value = '' if X.__module__ in ('__main__', 'builtins'): str_name = X.__qualname__ else: @@ -1171,19 +1171,45 @@ class BaseExceptionReportingTests: exp = "\n".join(expected) self.assertEqual(exp, err) - def test_format_exception_only_qualname(self): + def test_exception_qualname(self): class A: class B: class X(Exception): def __str__(self): return "I am X" - pass + err = self.get_report(A.B.X()) str_value = 'I am X' str_name = '.'.join([A.B.X.__module__, A.B.X.__qualname__]) exp = "%s: %s\n" % (str_name, str_value) self.assertEqual(exp, err) + def test_exception_modulename(self): + class X(Exception): + def __str__(self): + return "I am X" + + for modulename in '__main__', 'builtins', 'some_module': + X.__module__ = modulename + with self.subTest(modulename=modulename): + err = self.get_report(X()) + str_value = 'I am X' + if modulename in ['builtins', '__main__']: + str_name = X.__qualname__ + else: + str_name = '.'.join([X.__module__, X.__qualname__]) + exp = "%s: %s\n" % (str_name, str_value) + self.assertEqual(exp, err) + + def test_exception_bad__str__(self): + class X(Exception): + def __str__(self): + 1/0 + err = self.get_report(X()) + str_value = '' + str_name = '.'.join([X.__module__, X.__qualname__]) + self.assertEqual(err, f"{str_name}: {str_value}\n") + class PyExcReportingTests(BaseExceptionReportingTests, unittest.TestCase): # diff --git a/Lib/traceback.py b/Lib/traceback.py index d51c2010005..1b537dc5a91 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -169,7 +169,7 @@ def _some_str(value): try: return str(value) except: - return '' % type(value).__name__ + return '' # -- diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-09-03-12-35-17.bpo-41031.yPSJEs.rst b/Misc/NEWS.d/next/Core and Builtins/2021-09-03-12-35-17.bpo-41031.yPSJEs.rst new file mode 100644 index 00000000000..5dcfaa0046c --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2021-09-03-12-35-17.bpo-41031.yPSJEs.rst @@ -0,0 +1 @@ +Match C and Python code formatting of unprintable exceptions and exceptions in the :mod:`__main__` module. \ No newline at end of file diff --git a/Python/errors.c b/Python/errors.c index 15ca21b6840..b2030f728a7 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -25,6 +25,7 @@ extern char *strerror(int); extern "C" { #endif +_Py_IDENTIFIER(__main__); _Py_IDENTIFIER(__module__); _Py_IDENTIFIER(builtins); _Py_IDENTIFIER(stderr); @@ -1297,7 +1298,8 @@ write_unraisable_exc_file(PyThreadState *tstate, PyObject *exc_type, } } else { - if (!_PyUnicode_EqualToASCIIId(modulename, &PyId_builtins)) { + if (!_PyUnicode_EqualToASCIIId(modulename, &PyId_builtins) && + !_PyUnicode_EqualToASCIIId(modulename, &PyId___main__)) { if (PyFile_WriteObject(modulename, file, Py_PRINT_RAW) < 0) { Py_DECREF(modulename); return -1; diff --git a/Python/pythonrun.c b/Python/pythonrun.c index 3d07f43b525..0e0262c0e8c 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -35,6 +35,7 @@ #endif +_Py_IDENTIFIER(__main__); _Py_IDENTIFIER(builtins); _Py_IDENTIFIER(excepthook); _Py_IDENTIFIER(flush); @@ -974,7 +975,8 @@ print_exception(PyObject *f, PyObject *value) err = PyFile_WriteString("", f); } else { - if (!_PyUnicode_EqualToASCIIId(modulename, &PyId_builtins)) + if (!_PyUnicode_EqualToASCIIId(modulename, &PyId_builtins) && + !_PyUnicode_EqualToASCIIId(modulename, &PyId___main__)) { err = PyFile_WriteObject(modulename, f, Py_PRINT_RAW); err += PyFile_WriteString(".", f);