From 1485431d60848f9a6683942817e63f2a4a22b217 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Thu, 4 Mar 2021 18:28:48 +0000 Subject: [PATCH] pretty formatting in the interpreter --- Include/cpython/traceback.h | 2 +- Include/traceback.h | 3 +- Lib/test/test_traceback.py | 51 ++++++++++----- Python/_warnings.c | 2 +- Python/pythonrun.c | 120 +++++++++++++++++++++--------------- Python/traceback.c | 39 ++++++++---- 6 files changed, 139 insertions(+), 78 deletions(-) diff --git a/Include/cpython/traceback.h b/Include/cpython/traceback.h index aac5b42c344d3f..08cd877081b9a1 100644 --- a/Include/cpython/traceback.h +++ b/Include/cpython/traceback.h @@ -10,5 +10,5 @@ typedef struct _traceback { int tb_lineno; } PyTracebackObject; -PyAPI_FUNC(int) _Py_DisplaySourceLine(PyObject *, PyObject *, int, int); +PyAPI_FUNC(int) _Py_DisplaySourceLine(PyObject *, PyObject *, int, int, char); PyAPI_FUNC(void) _PyTraceback_Add(const char *, const char *, int); diff --git a/Include/traceback.h b/Include/traceback.h index 7568fdee85012e..979a5a115ab895 100644 --- a/Include/traceback.h +++ b/Include/traceback.h @@ -9,7 +9,8 @@ extern "C" { PyAPI_FUNC(int) PyTraceBack_Here(PyFrameObject *); PyAPI_FUNC(int) PyTraceBack_Print(PyObject *, PyObject *); -int PyTraceBack_Print_Indented(PyObject *, PyObject *, int); +int PyTraceBack_Print_Indented(PyObject *, PyObject *, int, char); +int _Py_WriteFancyIndent(int, char, PyObject*); int _Py_WriteIndent(int, PyObject *); /* Reveal traceback type so we can typecheck traceback objects */ diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 189d7b94b029a7..20dc5315ea041f 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -556,7 +556,10 @@ def __eq__(self, other): "another exception occurred:\n\n") boundaries = re.compile( - '(%s|%s)' % (re.escape(cause_message), re.escape(context_message))) + '(%s|%s)' % (re.escape(cause_message),re.escape(context_message))) + +nested_exception_header_re = re.compile( + "[\+-]?\+[----]+[ \d\.]*[----]+") class BaseExceptionReportingTests: @@ -759,24 +762,40 @@ def inner_raise(): def outer_raise(): inner_raise() # Marker - # TODO: define different boundaries for ExceptionGroups? - blocks = boundaries.split(self.get_report(outer_raise)) - self.assertEqual(len(blocks), 3) - self.assertEqual(blocks[1], context_message) + blocks = nested_exception_header_re.split(self.get_report(outer_raise)) + self.assertEqual(len(blocks), 8) + # Expecting: + # ExceptionGroup("", [ZeroDivisionError(), ExceptionGroup("eg", TypeError(2))]) + # Where the ZeroDivisionError has context equal to: + # ExceptionGroup("eg", [ValueError(1)]) - # The first block is the "context" ExceptionGroup("eg", ValueError(1)) - self.assertIn('raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])', blocks[0]) + # Block 0: The outermost ExceptionGroup up to "with 2 sub-expressions" + self.assertIn('exception_or_callable()\n', blocks[0]) self.assertIn('inner_raise() # Marker', blocks[0]) - self.assertIn('ValueError: 1\n', blocks[0]) + self.assertIn('| with 2 sub-exceptions:\n', blocks[0]) self.assertNotIn('1/0', blocks[0]) - self.assertNotIn('TypeError: 2\n', blocks[0]) - # The second block is the ZeroDivError, along with the unrasied - # part of the original ExceptionGroup("eg", TypeError(2)) - self.assertIn('raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])', blocks[0]) - self.assertNotIn('inner_raise() # Marker', blocks[2]) - self.assertIn('1/0', blocks[2]) - self.assertIn('TypeError: 2\n', blocks[2]) - self.assertNotIn('ValueError: 1\n', blocks[2]) + self.assertNotIn('TypeError', blocks[0]) + + # Block 1: The ExceptionGroup which is the context of the ZeroDivisionError + self.assertIn(' | raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])\n', blocks[1]) + self.assertIn(' | ExceptionGroup: eg\n', blocks[1]) + self.assertIn(' | with one sub-exception:\n', blocks[1]) + + # Block 2: The ValueError + self.assertIn(' | ValueError: 1\n', blocks[2]) + + # Block 3: The ZeroDivisionError + self.assertIn(' | During handling of the above exception, another exception occurred:\n', blocks[3]) + self.assertIn(' | 1/0\n', blocks[3]) + self.assertIn(' | ZeroDivisionError: division by zero\n', blocks[3]) + + # Block 4: The nested ExceptionGroup + self.assertIn(' | raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])\n', blocks[4]) + self.assertIn(' | ExceptionGroup: eg\n', blocks[4]) + self.assertIn(' | with one sub-exception:\n', blocks[4]) + + # Block 5: The TypeError + self.assertIn(' | TypeError: 2\n', blocks[5]) class LimitTests(unittest.TestCase): diff --git a/Python/_warnings.c b/Python/_warnings.c index 021400f5580d6a..727452f8a8fe9f 100644 --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -543,7 +543,7 @@ show_warning(PyObject *filename, int lineno, PyObject *text, PyFile_WriteString("\n", f_stderr); } else { - _Py_DisplaySourceLine(f_stderr, filename, lineno, 2); + _Py_DisplaySourceLine(f_stderr, filename, lineno, 2, ' '); } error: diff --git a/Python/pythonrun.c b/Python/pythonrun.c index 7b09f55eff657b..9ac00525238ec5 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -769,21 +769,29 @@ PyErr_Print(void) PyErr_PrintEx(1); } -struct recursive_print_context +struct exception_print_context { - int depth; // The nesting level in exception groups - PyObject *parent_label; // Unicode label of the containing exception group + int exception_group_depth; // nesting level of current exception group + PyObject *parent_label; // Unicode label of containing exception group }; +static char margin_char(struct exception_print_context *ctx) { + return ctx->exception_group_depth ? '|' : '\0'; +} + +static char indent(struct exception_print_context *ctx) { + return 2 * ctx->exception_group_depth; +} + static void -print_exception(PyObject *f, PyObject *value, int indent) +print_exception(PyObject *f, PyObject *value, struct exception_print_context *ctx) { int err = 0; PyObject *type, *tb, *tmp; _Py_IDENTIFIER(print_file_and_line); if (!PyExceptionInstance_Check(value)) { - err += _Py_WriteIndent(indent, f); + err += _Py_WriteIndent(indent(ctx), f); err += PyFile_WriteString("TypeError: print_exception(): Exception expected for value, ", f); err += PyFile_WriteString(Py_TYPE(value)->tp_name, f); err += PyFile_WriteString(" found\n", f); @@ -797,7 +805,7 @@ print_exception(PyObject *f, PyObject *value, int indent) type = (PyObject *) Py_TYPE(value); tb = PyException_GetTraceback(value); if (tb && tb != Py_None) - err = PyTraceBack_Print_Indented(tb, f, indent); + err = PyTraceBack_Print_Indented(tb, f, indent(ctx), margin_char(ctx)); if (err == 0 && (err = _PyObject_LookupAttrId(value, &PyId_print_file_and_line, &tmp)) > 0) { @@ -818,7 +826,7 @@ print_exception(PyObject *f, PyObject *value, int indent) filename, lineno); Py_DECREF(filename); if (line != NULL) { - err += _Py_WriteIndent(indent, f); + err += _Py_WriteFancyIndent(indent(ctx), margin_char(ctx), f); PyFile_WriteObject(line, f, Py_PRINT_RAW); Py_DECREF(line); } @@ -848,7 +856,7 @@ print_exception(PyObject *f, PyObject *value, int indent) if (dot != NULL) className = dot+1; } - err += _Py_WriteIndent(indent, f); + err += _Py_WriteFancyIndent(indent(ctx), margin_char(ctx), f); moduleName = _PyObject_GetAttrId(type, &PyId___module__); if (moduleName == NULL || !PyUnicode_Check(moduleName)) { @@ -901,15 +909,15 @@ print_exception(PyObject *f, PyObject *value, int indent) } static const char cause_message[] = - "\nThe above exception was the direct cause " + "The above exception was the direct cause " "of the following exception:\n\n"; static const char context_message[] = - "\nDuring handling of the above exception, " + "During handling of the above exception, " "another exception occurred:\n\n"; static void -print_exception_recursive(PyObject *f, PyObject *value, PyObject *seen, struct recursive_print_context* ctx) +print_exception_recursive(PyObject *f, PyObject *value, PyObject *seen, struct exception_print_context* ctx) { int err = 0, res; PyObject *cause, *context; @@ -937,7 +945,8 @@ print_exception_recursive(PyObject *f, PyObject *value, PyObject *seen, struct r if (res == 0) { print_exception_recursive( f, cause, seen, ctx); - err |= _Py_WriteIndent(ctx->depth, f); + err |= PyFile_WriteString("\n", f); + err |= _Py_WriteFancyIndent(indent(ctx), margin_char(ctx), f); err |= PyFile_WriteString( cause_message, f); } @@ -956,8 +965,9 @@ print_exception_recursive(PyObject *f, PyObject *value, PyObject *seen, struct r if (res == 0) { print_exception_recursive( f, context, seen, ctx); - err |= PyFile_WriteString( - context_message, f); + err |= PyFile_WriteString("\n", f); + err |= _Py_WriteFancyIndent(indent(ctx), margin_char(ctx), f); + err |= PyFile_WriteString(context_message, f); } } Py_XDECREF(context); @@ -966,70 +976,84 @@ print_exception_recursive(PyObject *f, PyObject *value, PyObject *seen, struct r Py_XDECREF(value_id); } if (!PyObject_TypeCheck(value, (PyTypeObject *)PyExc_ExceptionGroup)) { - print_exception(f, value, ctx->depth); + print_exception(f, value, ctx); } else { /* ExceptionGroup */ /* TODO: use seen to prevent cycles */ /* TODO: add limit for number of exceptions printed */ - PyObject *line; - print_exception(f, value, ctx->depth); + + if (ctx->exception_group_depth == 0) { + ctx->exception_group_depth += 1; + } + print_exception(f, value, ctx); PyObject *excs = ((PyExceptionGroupObject *)value)->excs; if (excs && PySequence_Check(excs)) { Py_ssize_t i, num_excs = PySequence_Length(excs); PyObject *parent_label = ctx->parent_label; - line = PyUnicode_FromFormat( - "This exception has %d sub-exceptions:\n", num_excs); - err |= _Py_WriteIndent(ctx->depth, f); - err |= PyFile_WriteObject(line, f, Py_PRINT_RAW); - Py_XDECREF(line); - - for (i = 0; i < num_excs; i++) { - PyObject *label; - if (parent_label) { - label = PyUnicode_FromFormat("%U.%d", - parent_label, i+1); + if (num_excs > 0) { + PyObject *line; + if (num_excs > 1) { + line = PyUnicode_FromFormat( + " with %d sub-exceptions:\n", num_excs); } - else { - label = PyUnicode_FromFormat("%d", i+1); + else if (num_excs == 1) { + line = PyUnicode_FromFormat( + " with one sub-exception:\n"); } - line = PyUnicode_FromFormat( - "----------------- %U/%d -----------------\n", label, num_excs); - err |= _Py_WriteIndent(ctx->depth+1, f); + err |= _Py_WriteFancyIndent(indent(ctx), margin_char(ctx), f); err |= PyFile_WriteObject(line, f, Py_PRINT_RAW); Py_XDECREF(line); - - - ctx->depth += 1; - ctx->parent_label = label; - print_exception_recursive( - f, PySequence_GetItem(excs, i), seen, ctx); - ctx->depth -= 1; - ctx->parent_label = parent_label; - + for (i = 0; i < num_excs; i++) { + PyObject *label; + if (parent_label) { + label = PyUnicode_FromFormat("%U.%d", + parent_label, i + 1); + } + else { + label = PyUnicode_FromFormat("%d", i + 1); + } + err |= _Py_WriteIndent(indent(ctx), f); + line = PyUnicode_FromFormat( + "%s+---------------- %U ----------------\n", + (i == 0) ? "+-" : " ", label); + ctx->exception_group_depth += 1; + err |= PyFile_WriteObject(line, f, Py_PRINT_RAW); + Py_XDECREF(line); + + ctx->parent_label = label; + print_exception_recursive( + f, PySequence_GetItem(excs, i), seen, ctx); + ctx->parent_label = parent_label; + Py_XDECREF(label); + ctx->exception_group_depth -= 1; + } + err |= _Py_WriteIndent(indent(ctx), f); line = PyUnicode_FromFormat( - "----------------- end of %U -----------------\n", label); - err |= _Py_WriteIndent(ctx->depth+1, f); + "+------------------------------------\n"); err |= PyFile_WriteObject(line, f, Py_PRINT_RAW); Py_XDECREF(line); - Py_XDECREF(label); } } + if (ctx->exception_group_depth == 1) { + ctx->exception_group_depth -= 1; + } } - if (err != 0) + if (err != 0) { PyErr_Clear(); + } } void _PyErr_Display(PyObject *file, PyObject *exception, PyObject *value, PyObject *tb) { assert(file != NULL && file != Py_None); - struct recursive_print_context ctx; + struct exception_print_context ctx; PyObject *seen; // TODO: move this into the context - ctx.depth = 0; + ctx.exception_group_depth = 0; ctx.parent_label = 0; if (PyExceptionInstance_Check(value) diff --git a/Python/traceback.c b/Python/traceback.c index 1be7d492f65edb..876071182f825a 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -385,8 +385,24 @@ _Py_WriteIndent(int indent, PyObject *f) { return 0; } +/* Utility for write pretty indentation. Writes 2*indent chars, + * in the format "efefefe " where e is the edge character and b + * is the fill character + */ int -_Py_DisplaySourceLine(PyObject *f, PyObject *filename, int lineno, int indent) +_Py_WriteFancyIndent(int indent, char margin_char, PyObject *f) { + int err = 0; + char margin[] = {margin_char, ' ', '\0' }; + err |= _Py_WriteIndent(indent, f); + err |= PyFile_WriteString(margin, f); + if (err != 0) + return err; + return 0; +} + + +int +_Py_DisplaySourceLine(PyObject *f, PyObject *filename, int lineno, int indent, char margin_char) { int err = 0; int fd; @@ -497,7 +513,8 @@ _Py_DisplaySourceLine(PyObject *f, PyObject *filename, int lineno, int indent) } /* Write some spaces before the line */ - err = _Py_WriteIndent(indent, f); + err |= _Py_WriteFancyIndent(indent-4, margin_char, f); + err |= _Py_WriteIndent(4, f); /* finally display the line */ if (err == 0) @@ -509,7 +526,7 @@ _Py_DisplaySourceLine(PyObject *f, PyObject *filename, int lineno, int indent) } static int -tb_displayline(PyObject *f, PyObject *filename, int lineno, PyObject *name, int indent) +tb_displayline(PyObject *f, PyObject *filename, int lineno, PyObject *name, int indent, char margin_char) { int err; PyObject *line; @@ -520,13 +537,13 @@ tb_displayline(PyObject *f, PyObject *filename, int lineno, PyObject *name, int filename, lineno, name); if (line == NULL) return -1; - err = _Py_WriteIndent(indent, f); + err = _Py_WriteFancyIndent(indent, margin_char, f); err |= PyFile_WriteObject(line, f, Py_PRINT_RAW); Py_DECREF(line); if (err != 0) return err; /* ignore errors since we can't report them, can we? */ - if (_Py_DisplaySourceLine(f, filename, lineno, indent+4)) + if (_Py_DisplaySourceLine(f, filename, lineno, indent+4, margin_char)) PyErr_Clear(); return err; } @@ -551,7 +568,7 @@ tb_print_line_repeated(PyObject *f, long cnt) } static int -tb_printinternal(PyTracebackObject *tb, PyObject *f, long limit, int indent) +tb_printinternal(PyTracebackObject *tb, PyObject *f, long limit, int indent, char margin_char) { int err = 0; Py_ssize_t depth = 0; @@ -585,7 +602,7 @@ tb_printinternal(PyTracebackObject *tb, PyObject *f, long limit, int indent) cnt++; if (err == 0 && cnt <= TB_RECURSIVE_CUTOFF) { err = tb_displayline(f, code->co_filename, tb->tb_lineno, - code->co_name, indent); + code->co_name, indent, margin_char); if (err == 0) { err = PyErr_CheckSignals(); } @@ -602,7 +619,7 @@ tb_printinternal(PyTracebackObject *tb, PyObject *f, long limit, int indent) #define PyTraceBack_LIMIT 1000 int -PyTraceBack_Print_Indented(PyObject *v, PyObject *f, int indent) +PyTraceBack_Print_Indented(PyObject *v, PyObject *f, int indent, char margin_char) { int err; PyObject *limitv; @@ -625,17 +642,17 @@ PyTraceBack_Print_Indented(PyObject *v, PyObject *f, int indent) return 0; } } - err = _Py_WriteIndent(indent, f); + err = _Py_WriteFancyIndent(indent, margin_char, f); err |= PyFile_WriteString("Traceback (most recent call last):\n", f); if (!err) - err = tb_printinternal((PyTracebackObject *)v, f, limit, indent); + err = tb_printinternal((PyTracebackObject *)v, f, limit, indent, margin_char); return err; } int PyTraceBack_Print(PyObject *v, PyObject *f) { - return PyTraceBack_Print_Indented(v, f, 0); + return PyTraceBack_Print_Indented(v, f, 0, '\0'); } /* Reverse a string. For example, "abcd" becomes "dcba".