Skip to content
Prev Previous commit
Next Next commit
traceback.c implementation
  • Loading branch information
belm0 committed Jun 24, 2022
commit 94be1862575d50422057e49d6818c8335aeca1f8
127 changes: 65 additions & 62 deletions Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ def do_test(firstlines, message, charset, lineno):
self.assertTrue(stdout[2].endswith(err_line),
"Invalid traceback line: {0!r} instead of {1!r}".format(
stdout[2], err_line))
actual_err_msg = stdout[3 if has_no_debug_ranges() else 4]
actual_err_msg = stdout[3]
self.assertTrue(actual_err_msg == err_msg,
"Invalid error message: {0!r} instead of {1!r}".format(
actual_err_msg, err_msg))
Expand Down Expand Up @@ -704,23 +704,23 @@ class A: pass
)
self.assertEqual(result_lines, expected_error.splitlines())

# @cpython_only
# @requires_debug_ranges()
# class CPythonTracebackErrorCaretTests(TracebackErrorLocationCaretTests):
# """
# Same set of tests as above but with Python's internal traceback printing.
# """
# def get_exception(self, callable):
# from _testcapi import exception_print
# try:
# callable()
# self.fail("No exception thrown.")
# except Exception as e:
# with captured_output("stderr") as tbstderr:
# exception_print(e)
# return tbstderr.getvalue().splitlines()[:-1]
#
# callable_line = get_exception.__code__.co_firstlineno + 3
@cpython_only
@requires_debug_ranges()
class CPythonTracebackErrorCaretTests(TracebackErrorLocationCaretTests):
"""
Same set of tests as above but with Python's internal traceback printing.
"""
def get_exception(self, callable):
from _testcapi import exception_print
try:
callable()
self.fail("No exception thrown.")
except Exception as e:
with captured_output("stderr") as tbstderr:
exception_print(e)
return tbstderr.getvalue().splitlines()[:-1]

callable_line = get_exception.__code__.co_firstlineno + 3


class TracebackFormatTests(unittest.TestCase):
Expand Down Expand Up @@ -1015,14 +1015,14 @@ def h(count=10):
def test_recursive_traceback_python(self):
self._check_recursive_traceback_display(traceback.print_exc)

# @cpython_only
# @requires_debug_ranges()
# def test_recursive_traceback_cpython_internal(self):
# from _testcapi import exception_print
# def render_exc():
# exc_type, exc_value, exc_tb = sys.exc_info()
# exception_print(exc_value)
# self._check_recursive_traceback_display(render_exc)
@cpython_only
@requires_debug_ranges()
def test_recursive_traceback_cpython_internal(self):
from _testcapi import exception_print
def render_exc():
exc_type, exc_value, exc_tb = sys.exc_info()
exception_print(exc_value)
self._check_recursive_traceback_display(render_exc)

def test_format_stack(self):
def fmt():
Expand Down Expand Up @@ -1058,16 +1058,10 @@ def __eq__(self, other):
exception_print(exc_val)

tb = stderr_f.getvalue().strip().splitlines()
if has_no_debug_ranges():
self.assertEqual(11, len(tb))
self.assertEqual(context_message.strip(), tb[5])
self.assertIn('UnhashableException: ex2', tb[3])
self.assertIn('UnhashableException: ex1', tb[10])
else:
self.assertEqual(13, len(tb))
self.assertEqual(context_message.strip(), tb[6])
self.assertIn('UnhashableException: ex2', tb[4])
self.assertIn('UnhashableException: ex1', tb[12])
self.assertEqual(11, len(tb))
self.assertEqual(context_message.strip(), tb[5])
self.assertIn('UnhashableException: ex2', tb[3])
self.assertIn('UnhashableException: ex1', tb[10])

def deep_eg(self):
e = TypeError(1)
Expand Down Expand Up @@ -1451,15 +1445,17 @@ def __str__(self):
# #### Exception Groups ####

def test_exception_group_basic(self):
self.maxDiff = None
def exc():
raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])
if True: raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])

expected = (
f' + Exception Group Traceback (most recent call last):\n'
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
f' | exception_or_callable()\n'
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 1}, in exc\n'
f' | raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])\n'
f' | if True: raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])\n'
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | ExceptionGroup: eg (2 sub-exceptions)\n'
f' +-+---------------- 1 ----------------\n'
f' | ValueError: 1\n'
Expand All @@ -1474,13 +1470,14 @@ def test_exception_group_cause(self):
def exc():
EG = ExceptionGroup
try:
raise EG("eg1", [ValueError(1), TypeError(2)])
if True: raise EG("eg1", [ValueError(1), TypeError(2)])
except Exception as e:
raise EG("eg2", [ValueError(3), TypeError(4)]) from e

expected = (f' + Exception Group Traceback (most recent call last):\n'
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 3}, in exc\n'
f' | raise EG("eg1", [ValueError(1), TypeError(2)])\n'
f' | if True: raise EG("eg1", [ValueError(1), TypeError(2)])\n'
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | ExceptionGroup: eg1 (2 sub-exceptions)\n'
f' +-+---------------- 1 ----------------\n'
f' | ValueError: 1\n'
Expand Down Expand Up @@ -1510,7 +1507,7 @@ def exc():
EG = ExceptionGroup
try:
try:
raise EG("eg1", [ValueError(1), TypeError(2)])
if True: raise EG("eg1", [ValueError(1), TypeError(2)])
except:
raise EG("eg2", [ValueError(3), TypeError(4)])
except:
Expand All @@ -1519,7 +1516,8 @@ def exc():
expected = (
f' + Exception Group Traceback (most recent call last):\n'
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 4}, in exc\n'
f' | raise EG("eg1", [ValueError(1), TypeError(2)])\n'
f' | if True: raise EG("eg1", [ValueError(1), TypeError(2)])\n'
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | ExceptionGroup: eg1 (2 sub-exceptions)\n'
f' +-+---------------- 1 ----------------\n'
f' | ValueError: 1\n'
Expand Down Expand Up @@ -1558,7 +1556,7 @@ def exc():
TE = TypeError
try:
try:
raise EG("nested", [TE(2), TE(3)])
if True: raise EG("nested", [TE(2), TE(3)])
except Exception as e:
exc = e
raise EG("eg", [VE(1), exc, VE(4)])
Expand All @@ -1574,7 +1572,8 @@ def exc():
f' +---------------- 2 ----------------\n'
f' | Exception Group Traceback (most recent call last):\n'
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 6}, in exc\n'
f' | raise EG("nested", [TE(2), TE(3)])\n'
f' | if True: raise EG("nested", [TE(2), TE(3)])\n'
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | ExceptionGroup: nested (2 sub-exceptions)\n'
f' +-+---------------- 1 ----------------\n'
f' | TypeError: 2\n'
Expand Down Expand Up @@ -1732,7 +1731,7 @@ def exc():
excs = []
for msg in ['bad value', 'terrible value']:
try:
raise ValueError(msg)
if True: raise ValueError(msg)
except ValueError as e:
e.add_note(f'the {msg}')
excs.append(e)
Expand Down Expand Up @@ -1761,13 +1760,15 @@ def exc():
f' +-+---------------- 1 ----------------\n'
f' | Traceback (most recent call last):\n'
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
f' | raise ValueError(msg)\n'
f' | if True: raise ValueError(msg)\n'
f' | ^^^^^^^^^^^^^^^^^^^^^\n'
f' | ValueError: bad value\n'
f' | the bad value\n'
f' +---------------- 2 ----------------\n'
f' | Traceback (most recent call last):\n'
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
f' | raise ValueError(msg)\n'
f' | if True: raise ValueError(msg)\n'
f' | ^^^^^^^^^^^^^^^^^^^^^\n'
f' | ValueError: terrible value\n'
f' | the terrible value\n'
f' +------------------------------------\n')
Expand All @@ -1781,7 +1782,7 @@ def exc():
excs = []
for msg in ['bad value', 'terrible value']:
try:
raise ValueError(msg)
if True: raise ValueError(msg)
except ValueError as e:
e.add_note(f'the {msg}')
e.add_note(f'Goodbye {msg}')
Expand Down Expand Up @@ -1813,14 +1814,16 @@ def exc():
f' +-+---------------- 1 ----------------\n'
f' | Traceback (most recent call last):\n'
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
f' | raise ValueError(msg)\n'
f' | if True: raise ValueError(msg)\n'
f' | ^^^^^^^^^^^^^^^^^^^^^\n'
f' | ValueError: bad value\n'
f' | the bad value\n'
f' | Goodbye bad value\n'
f' +---------------- 2 ----------------\n'
f' | Traceback (most recent call last):\n'
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
f' | raise ValueError(msg)\n'
f' | if True: raise ValueError(msg)\n'
f' | ^^^^^^^^^^^^^^^^^^^^^\n'
f' | ValueError: terrible value\n'
f' | the terrible value\n'
f' | Goodbye terrible value\n'
Expand Down Expand Up @@ -1871,18 +1874,18 @@ def get_report(self, e):
return s


# class CExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
# #
# # This checks built-in reporting by the interpreter.
# #
#
# @cpython_only
# def get_report(self, e):
# from _testcapi import exception_print
# e = self.get_exception(e)
# with captured_output("stderr") as s:
# exception_print(e)
# return s.getvalue()
class CExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
#
# This checks built-in reporting by the interpreter.
#

@cpython_only
def get_report(self, e):
from _testcapi import exception_print
e = self.get_exception(e)
with captured_output("stderr") as s:
exception_print(e)
return s.getvalue()


class LimitTests(unittest.TestCase):
Expand Down
11 changes: 9 additions & 2 deletions Python/traceback.c
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,6 @@ _Py_DisplaySourceLine(PyObject *f, PyObject *filename, int lineno, int indent,
* Traceback (most recent call last):
* File "/home/isidentical/cpython/cpython/t.py", line 10, in <module>
* add_values(1, 2, 'x', 3, 4)
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^
* File "/home/isidentical/cpython/cpython/t.py", line 2, in add_values
* return a + b + c + d + e
* ~~~~~~^~~
Expand Down Expand Up @@ -792,6 +791,7 @@ tb_displayline(PyTracebackObject* tb, PyObject *f, PyObject *filename, int linen

int code_offset = tb->tb_lasti;
PyCodeObject* code = frame->f_frame->f_code;
const Py_ssize_t source_line_len = PyUnicode_GET_LENGTH(source_line);

int start_line;
int end_line;
Expand Down Expand Up @@ -859,7 +859,7 @@ tb_displayline(PyTracebackObject* tb, PyObject *f, PyObject *filename, int linen
goto done;
}

Py_ssize_t i = PyUnicode_GET_LENGTH(source_line);
Py_ssize_t i = source_line_len;
while (--i >= 0) {
if (!IS_WHITESPACE(source_line_str[i])) {
break;
Expand All @@ -869,6 +869,13 @@ tb_displayline(PyTracebackObject* tb, PyObject *f, PyObject *filename, int linen
end_offset = i + 1;
}

// elide indicators if primary char spans the frame line
Py_ssize_t stripped_line_len = source_line_len - truncation - _TRACEBACK_SOURCE_LINE_INDENT;
if (end_offset - start_offset == stripped_line_len &&
left_end_offset == -1 && right_start_offset == -1) {
goto done;
}

if (_Py_WriteIndentedMargin(margin_indent, margin, f) < 0) {
err = -1;
goto done;
Expand Down