mirror of
https://github.com/python/cpython.git
synced 2025-11-10 18:42:04 +00:00
gh-93883: elide traceback indicators when possible (#93994)
* gh-93883: elide traceback indicators when possible
Elide traceback column indicators when the entire line of the
frame is implicated. This reduces traceback length and draws
even more attention to the remaining (very relevant) indicators.
Example:
```
Traceback (most recent call last):
File "query.py", line 99, in <module>
bar()
File "query.py", line 66, in bar
foo()
File "query.py", line 37, in foo
magic_arithmetic('foo')
File "query.py", line 18, in magic_arithmetic
return add_counts(x) / 25
^^^^^^^^^^^^^
File "query.py", line 24, in add_counts
return 25 + query_user(user1) + query_user(user2)
^^^^^^^^^^^^^^^^^
File "query.py", line 32, in query_user
return 1 + query_count(db, response['a']['b']['c']['user'], retry=True)
~~~~~~~~~~~~~~~~~~^^^^^
TypeError: 'NoneType' object is not subscriptable
```
Rather than going out of our way to provide indicator coverage
in every traceback test suite, the indicator test suite should
be responible for sufficient coverage (e.g. by adding a basic
exception group test to ensure that margin strings are covered).
This commit is contained in:
parent
c9118afd04
commit
da717519ec
9 changed files with 111 additions and 135 deletions
|
|
@ -464,32 +464,27 @@ The output for the example would look similar to this:
|
||||||
*** print_tb:
|
*** print_tb:
|
||||||
File "<doctest...>", line 10, in <module>
|
File "<doctest...>", line 10, in <module>
|
||||||
lumberjack()
|
lumberjack()
|
||||||
^^^^^^^^^^^^
|
|
||||||
*** print_exception:
|
*** print_exception:
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
File "<doctest...>", line 10, in <module>
|
File "<doctest...>", line 10, in <module>
|
||||||
lumberjack()
|
lumberjack()
|
||||||
^^^^^^^^^^^^
|
|
||||||
File "<doctest...>", line 4, in lumberjack
|
File "<doctest...>", line 4, in lumberjack
|
||||||
bright_side_of_death()
|
bright_side_of_death()
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
IndexError: tuple index out of range
|
IndexError: tuple index out of range
|
||||||
*** print_exc:
|
*** print_exc:
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
File "<doctest...>", line 10, in <module>
|
File "<doctest...>", line 10, in <module>
|
||||||
lumberjack()
|
lumberjack()
|
||||||
^^^^^^^^^^^^
|
|
||||||
File "<doctest...>", line 4, in lumberjack
|
File "<doctest...>", line 4, in lumberjack
|
||||||
bright_side_of_death()
|
bright_side_of_death()
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
IndexError: tuple index out of range
|
IndexError: tuple index out of range
|
||||||
*** format_exc, first and last line:
|
*** format_exc, first and last line:
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
IndexError: tuple index out of range
|
IndexError: tuple index out of range
|
||||||
*** format_exception:
|
*** format_exception:
|
||||||
['Traceback (most recent call last):\n',
|
['Traceback (most recent call last):\n',
|
||||||
' File "<doctest default[0]>", line 10, in <module>\n lumberjack()\n ^^^^^^^^^^^^\n',
|
' File "<doctest default[0]>", line 10, in <module>\n lumberjack()\n',
|
||||||
' File "<doctest default[0]>", line 4, in lumberjack\n bright_side_of_death()\n ^^^^^^^^^^^^^^^^^^^^^^\n',
|
' File "<doctest default[0]>", line 4, in lumberjack\n bright_side_of_death()\n',
|
||||||
' File "<doctest default[0]>", line 7, in bright_side_of_death\n return tuple()[0]\n ~~~~~~~^^^\n',
|
' File "<doctest default[0]>", line 7, in bright_side_of_death\n return tuple()[0]\n ~~~~~~~^^^\n',
|
||||||
'IndexError: tuple index out of range\n']
|
'IndexError: tuple index out of range\n']
|
||||||
*** extract_tb:
|
*** extract_tb:
|
||||||
|
|
@ -497,8 +492,8 @@ The output for the example would look similar to this:
|
||||||
<FrameSummary file <doctest...>, line 4 in lumberjack>,
|
<FrameSummary file <doctest...>, line 4 in lumberjack>,
|
||||||
<FrameSummary file <doctest...>, line 7 in bright_side_of_death>]
|
<FrameSummary file <doctest...>, line 7 in bright_side_of_death>]
|
||||||
*** format_tb:
|
*** format_tb:
|
||||||
[' File "<doctest default[0]>", line 10, in <module>\n lumberjack()\n ^^^^^^^^^^^^\n',
|
[' File "<doctest default[0]>", line 10, in <module>\n lumberjack()\n',
|
||||||
' File "<doctest default[0]>", line 4, in lumberjack\n bright_side_of_death()\n ^^^^^^^^^^^^^^^^^^^^^^\n',
|
' File "<doctest default[0]>", line 4, in lumberjack\n bright_side_of_death()\n',
|
||||||
' File "<doctest default[0]>", line 7, in bright_side_of_death\n return tuple()[0]\n ~~~~~~~^^^\n']
|
' File "<doctest default[0]>", line 7, in bright_side_of_death\n return tuple()[0]\n ~~~~~~~^^^\n']
|
||||||
*** tb_lineno: 10
|
*** tb_lineno: 10
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,6 @@ when dealing with deeply nested dictionary objects and multiple function calls,
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
File "query.py", line 37, in <module>
|
File "query.py", line 37, in <module>
|
||||||
magic_arithmetic('foo')
|
magic_arithmetic('foo')
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "query.py", line 18, in magic_arithmetic
|
File "query.py", line 18, in magic_arithmetic
|
||||||
return add_counts(x) / 25
|
return add_counts(x) / 25
|
||||||
^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
from idlelib import run
|
from idlelib import run
|
||||||
import io
|
import io
|
||||||
import sys
|
import sys
|
||||||
from test.support import captured_output, captured_stderr, has_no_debug_ranges
|
from test.support import captured_output, captured_stderr
|
||||||
import unittest
|
import unittest
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
import idlelib
|
import idlelib
|
||||||
|
|
@ -33,14 +33,9 @@ def __eq__(self, other):
|
||||||
run.print_exception()
|
run.print_exception()
|
||||||
|
|
||||||
tb = output.getvalue().strip().splitlines()
|
tb = output.getvalue().strip().splitlines()
|
||||||
if has_no_debug_ranges():
|
self.assertEqual(11, len(tb))
|
||||||
self.assertEqual(11, len(tb))
|
self.assertIn('UnhashableException: ex2', tb[3])
|
||||||
self.assertIn('UnhashableException: ex2', tb[3])
|
self.assertIn('UnhashableException: ex1', tb[10])
|
||||||
self.assertIn('UnhashableException: ex1', tb[10])
|
|
||||||
else:
|
|
||||||
self.assertEqual(13, len(tb))
|
|
||||||
self.assertIn('UnhashableException: ex2', tb[4])
|
|
||||||
self.assertIn('UnhashableException: ex1', tb[12])
|
|
||||||
|
|
||||||
data = (('1/0', ZeroDivisionError, "division by zero\n"),
|
data = (('1/0', ZeroDivisionError, "division by zero\n"),
|
||||||
('abc', NameError, "name 'abc' is not defined. "
|
('abc', NameError, "name 'abc' is not defined. "
|
||||||
|
|
|
||||||
|
|
@ -549,10 +549,10 @@ def test_pep_409_verbiage(self):
|
||||||
script_name = _make_test_script(script_dir, 'script', script)
|
script_name = _make_test_script(script_dir, 'script', script)
|
||||||
exitcode, stdout, stderr = assert_python_failure(script_name)
|
exitcode, stdout, stderr = assert_python_failure(script_name)
|
||||||
text = stderr.decode('ascii').split('\n')
|
text = stderr.decode('ascii').split('\n')
|
||||||
self.assertEqual(len(text), 6)
|
self.assertEqual(len(text), 5)
|
||||||
self.assertTrue(text[0].startswith('Traceback'))
|
self.assertTrue(text[0].startswith('Traceback'))
|
||||||
self.assertTrue(text[1].startswith(' File '))
|
self.assertTrue(text[1].startswith(' File '))
|
||||||
self.assertTrue(text[4].startswith('NameError'))
|
self.assertTrue(text[3].startswith('NameError'))
|
||||||
|
|
||||||
def test_non_ascii(self):
|
def test_non_ascii(self):
|
||||||
# Mac OS X denies the creation of a file with an invalid UTF-8 name.
|
# Mac OS X denies the creation of a file with an invalid UTF-8 name.
|
||||||
|
|
|
||||||
|
|
@ -2854,7 +2854,7 @@ def test_testmod(): r"""
|
||||||
# Skip the test: the filesystem encoding is unable to encode the filename
|
# Skip the test: the filesystem encoding is unable to encode the filename
|
||||||
supports_unicode = False
|
supports_unicode = False
|
||||||
|
|
||||||
if supports_unicode and not support.has_no_debug_ranges():
|
if supports_unicode:
|
||||||
def test_unicode(): """
|
def test_unicode(): """
|
||||||
Check doctest with a non-ascii filename:
|
Check doctest with a non-ascii filename:
|
||||||
|
|
||||||
|
|
@ -2876,10 +2876,8 @@ def test_unicode(): """
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
File ...
|
File ...
|
||||||
exec(compile(example.source, filename, "single",
|
exec(compile(example.source, filename, "single",
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "<doctest foo-bär@baz[0]>", line 1, in <module>
|
File "<doctest foo-bär@baz[0]>", line 1, in <module>
|
||||||
raise Exception('clé')
|
raise Exception('clé')
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
Exception: clé
|
Exception: clé
|
||||||
TestResults(failed=1, attempted=1)
|
TestResults(failed=1, attempted=1)
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,7 @@ def do_test(firstlines, message, charset, lineno):
|
||||||
self.assertTrue(stdout[2].endswith(err_line),
|
self.assertTrue(stdout[2].endswith(err_line),
|
||||||
"Invalid traceback line: {0!r} instead of {1!r}".format(
|
"Invalid traceback line: {0!r} instead of {1!r}".format(
|
||||||
stdout[2], err_line))
|
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,
|
self.assertTrue(actual_err_msg == err_msg,
|
||||||
"Invalid error message: {0!r} instead of {1!r}".format(
|
"Invalid error message: {0!r} instead of {1!r}".format(
|
||||||
actual_err_msg, err_msg))
|
actual_err_msg, err_msg))
|
||||||
|
|
@ -386,18 +386,19 @@ def get_exception(self, callable):
|
||||||
callable_line = get_exception.__code__.co_firstlineno + 2
|
callable_line = get_exception.__code__.co_firstlineno + 2
|
||||||
|
|
||||||
def test_basic_caret(self):
|
def test_basic_caret(self):
|
||||||
|
# NOTE: In caret tests, "if True:" is used as a way to force indicator
|
||||||
|
# display, since the raising expression spans only part of the line.
|
||||||
def f():
|
def f():
|
||||||
raise ValueError("basic caret tests")
|
if True: raise ValueError("basic caret tests")
|
||||||
|
|
||||||
lineno_f = f.__code__.co_firstlineno
|
lineno_f = f.__code__.co_firstlineno
|
||||||
expected_f = (
|
expected_f = (
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
' ^^^^^^^^^^\n'
|
|
||||||
f' File "{__file__}", line {lineno_f+1}, in f\n'
|
f' File "{__file__}", line {lineno_f+1}, in f\n'
|
||||||
' raise ValueError("basic caret tests")\n'
|
' if True: raise ValueError("basic caret tests")\n'
|
||||||
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
||||||
)
|
)
|
||||||
result_lines = self.get_exception(f)
|
result_lines = self.get_exception(f)
|
||||||
self.assertEqual(result_lines, expected_f.splitlines())
|
self.assertEqual(result_lines, expected_f.splitlines())
|
||||||
|
|
@ -406,17 +407,16 @@ def test_line_with_unicode(self):
|
||||||
# Make sure that even if a line contains multi-byte unicode characters
|
# Make sure that even if a line contains multi-byte unicode characters
|
||||||
# the correct carets are printed.
|
# the correct carets are printed.
|
||||||
def f_with_unicode():
|
def f_with_unicode():
|
||||||
raise ValueError("Ĥellö Wörld")
|
if True: raise ValueError("Ĥellö Wörld")
|
||||||
|
|
||||||
lineno_f = f_with_unicode.__code__.co_firstlineno
|
lineno_f = f_with_unicode.__code__.co_firstlineno
|
||||||
expected_f = (
|
expected_f = (
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
' ^^^^^^^^^^\n'
|
|
||||||
f' File "{__file__}", line {lineno_f+1}, in f_with_unicode\n'
|
f' File "{__file__}", line {lineno_f+1}, in f_with_unicode\n'
|
||||||
' raise ValueError("Ĥellö Wörld")\n'
|
' if True: raise ValueError("Ĥellö Wörld")\n'
|
||||||
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
||||||
)
|
)
|
||||||
result_lines = self.get_exception(f_with_unicode)
|
result_lines = self.get_exception(f_with_unicode)
|
||||||
self.assertEqual(result_lines, expected_f.splitlines())
|
self.assertEqual(result_lines, expected_f.splitlines())
|
||||||
|
|
@ -431,7 +431,6 @@ def foo(a: THIS_DOES_NOT_EXIST ) -> int:
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
' ^^^^^^^^^^\n'
|
|
||||||
f' File "{__file__}", line {lineno_f+1}, in f_with_type\n'
|
f' File "{__file__}", line {lineno_f+1}, in f_with_type\n'
|
||||||
' def foo(a: THIS_DOES_NOT_EXIST ) -> int:\n'
|
' def foo(a: THIS_DOES_NOT_EXIST ) -> int:\n'
|
||||||
' ^^^^^^^^^^^^^^^^^^^\n'
|
' ^^^^^^^^^^^^^^^^^^^\n'
|
||||||
|
|
@ -443,7 +442,7 @@ def test_caret_multiline_expression(self):
|
||||||
# Make sure no carets are printed for expressions spanning multiple
|
# Make sure no carets are printed for expressions spanning multiple
|
||||||
# lines.
|
# lines.
|
||||||
def f_with_multiline():
|
def f_with_multiline():
|
||||||
raise ValueError(
|
if True: raise ValueError(
|
||||||
"error over multiple lines"
|
"error over multiple lines"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -452,10 +451,9 @@ def f_with_multiline():
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
' ^^^^^^^^^^\n'
|
|
||||||
f' File "{__file__}", line {lineno_f+1}, in f_with_multiline\n'
|
f' File "{__file__}", line {lineno_f+1}, in f_with_multiline\n'
|
||||||
' raise ValueError(\n'
|
' if True: raise ValueError(\n'
|
||||||
' ^^^^^^^^^^^^^^^^^'
|
' ^^^^^^^^^^^^^^^^^'
|
||||||
)
|
)
|
||||||
result_lines = self.get_exception(f_with_multiline)
|
result_lines = self.get_exception(f_with_multiline)
|
||||||
self.assertEqual(result_lines, expected_f.splitlines())
|
self.assertEqual(result_lines, expected_f.splitlines())
|
||||||
|
|
@ -484,7 +482,6 @@ def f_with_multiline():
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
' ^^^^^^^^^^\n'
|
|
||||||
f' File "{__file__}", line {lineno_f+2}, in f_with_multiline\n'
|
f' File "{__file__}", line {lineno_f+2}, in f_with_multiline\n'
|
||||||
' return compile(code, "?", "exec")\n'
|
' return compile(code, "?", "exec")\n'
|
||||||
' ^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
' ^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
||||||
|
|
@ -501,9 +498,8 @@ def test_caret_multiline_expression_bin_op(self):
|
||||||
# lines.
|
# lines.
|
||||||
def f_with_multiline():
|
def f_with_multiline():
|
||||||
return (
|
return (
|
||||||
1 /
|
2 + 1 /
|
||||||
0 +
|
0
|
||||||
2
|
|
||||||
)
|
)
|
||||||
|
|
||||||
lineno_f = f_with_multiline.__code__.co_firstlineno
|
lineno_f = f_with_multiline.__code__.co_firstlineno
|
||||||
|
|
@ -511,10 +507,9 @@ def f_with_multiline():
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
' ^^^^^^^^^^\n'
|
|
||||||
f' File "{__file__}", line {lineno_f+2}, in f_with_multiline\n'
|
f' File "{__file__}", line {lineno_f+2}, in f_with_multiline\n'
|
||||||
' 1 /\n'
|
' 2 + 1 /\n'
|
||||||
' ^^^'
|
' ^^^'
|
||||||
)
|
)
|
||||||
result_lines = self.get_exception(f_with_multiline)
|
result_lines = self.get_exception(f_with_multiline)
|
||||||
self.assertEqual(result_lines, expected_f.splitlines())
|
self.assertEqual(result_lines, expected_f.splitlines())
|
||||||
|
|
@ -529,7 +524,6 @@ def f_with_binary_operator():
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
' ^^^^^^^^^^\n'
|
|
||||||
f' File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n'
|
f' File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n'
|
||||||
' return 10 + divisor / 0 + 30\n'
|
' return 10 + divisor / 0 + 30\n'
|
||||||
' ~~~~~~~~^~~\n'
|
' ~~~~~~~~^~~\n'
|
||||||
|
|
@ -547,7 +541,6 @@ def f_with_binary_operator():
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
' ^^^^^^^^^^\n'
|
|
||||||
f' File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n'
|
f' File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n'
|
||||||
' return 10 + divisor // 0 + 30\n'
|
' return 10 + divisor // 0 + 30\n'
|
||||||
' ~~~~~~~~^^~~\n'
|
' ~~~~~~~~^^~~\n'
|
||||||
|
|
@ -565,7 +558,6 @@ def f_with_subscript():
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
' ^^^^^^^^^^\n'
|
|
||||||
f' File "{__file__}", line {lineno_f+2}, in f_with_subscript\n'
|
f' File "{__file__}", line {lineno_f+2}, in f_with_subscript\n'
|
||||||
" return some_dict['x']['y']['z']\n"
|
" return some_dict['x']['y']['z']\n"
|
||||||
' ~~~~~~~~~~~~~~~~~~~^^^^^\n'
|
' ~~~~~~~~~~~~~~~~~~~^^^^^\n'
|
||||||
|
|
@ -589,7 +581,6 @@ def test_traceback_specialization_with_syntax_error(self):
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
' ^^^^^^^^^^\n'
|
|
||||||
f' File "{TESTFN}", line {lineno_f}, in <module>\n'
|
f' File "{TESTFN}", line {lineno_f}, in <module>\n'
|
||||||
" 1 $ 0 / 1 / 2\n"
|
" 1 $ 0 / 1 / 2\n"
|
||||||
' ^^^^^\n'
|
' ^^^^^\n'
|
||||||
|
|
@ -597,7 +588,7 @@ def test_traceback_specialization_with_syntax_error(self):
|
||||||
self.assertEqual(result_lines, expected_error.splitlines())
|
self.assertEqual(result_lines, expected_error.splitlines())
|
||||||
|
|
||||||
def test_traceback_very_long_line(self):
|
def test_traceback_very_long_line(self):
|
||||||
source = "a" * 256
|
source = "if True: " + "a" * 256
|
||||||
bytecode = compile(source, TESTFN, "exec")
|
bytecode = compile(source, TESTFN, "exec")
|
||||||
|
|
||||||
with open(TESTFN, "w") as file:
|
with open(TESTFN, "w") as file:
|
||||||
|
|
@ -612,13 +603,54 @@ def test_traceback_very_long_line(self):
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
' ^^^^^^^^^^\n'
|
|
||||||
f' File "{TESTFN}", line {lineno_f}, in <module>\n'
|
f' File "{TESTFN}", line {lineno_f}, in <module>\n'
|
||||||
f' {source}\n'
|
f' {source}\n'
|
||||||
f' {"^"*len(source)}\n'
|
f' {" "*len("if True: ") + "^"*256}\n'
|
||||||
)
|
)
|
||||||
self.assertEqual(result_lines, expected_error.splitlines())
|
self.assertEqual(result_lines, expected_error.splitlines())
|
||||||
|
|
||||||
|
def test_secondary_caret_not_elided(self):
|
||||||
|
# Always show a line's indicators if they include the secondary character.
|
||||||
|
def f_with_subscript():
|
||||||
|
some_dict = {'x': {'y': None}}
|
||||||
|
some_dict['x']['y']['z']
|
||||||
|
|
||||||
|
lineno_f = f_with_subscript.__code__.co_firstlineno
|
||||||
|
expected_error = (
|
||||||
|
'Traceback (most recent call last):\n'
|
||||||
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
|
' callable()\n'
|
||||||
|
f' File "{__file__}", line {lineno_f+2}, in f_with_subscript\n'
|
||||||
|
" some_dict['x']['y']['z']\n"
|
||||||
|
' ~~~~~~~~~~~~~~~~~~~^^^^^\n'
|
||||||
|
)
|
||||||
|
result_lines = self.get_exception(f_with_subscript)
|
||||||
|
self.assertEqual(result_lines, expected_error.splitlines())
|
||||||
|
|
||||||
|
def test_caret_exception_group(self):
|
||||||
|
# Notably, this covers whether indicators handle margin strings correctly.
|
||||||
|
# (Exception groups use margin strings to display vertical indicators.)
|
||||||
|
# The implementation must account for both "indent" and "margin" offsets.
|
||||||
|
|
||||||
|
def exc():
|
||||||
|
if True: raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])
|
||||||
|
|
||||||
|
expected_error = (
|
||||||
|
f' + Exception Group Traceback (most recent call last):\n'
|
||||||
|
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
|
f' | callable()\n'
|
||||||
|
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 1}, in exc\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'
|
||||||
|
f' +---------------- 2 ----------------\n'
|
||||||
|
f' | TypeError: 2\n')
|
||||||
|
|
||||||
|
result_lines = self.get_exception(exc)
|
||||||
|
self.assertEqual(result_lines, expected_error.splitlines())
|
||||||
|
|
||||||
def assertSpecialized(self, func, expected_specialization):
|
def assertSpecialized(self, func, expected_specialization):
|
||||||
result_lines = self.get_exception(func)
|
result_lines = self.get_exception(func)
|
||||||
specialization_line = result_lines[-1]
|
specialization_line = result_lines[-1]
|
||||||
|
|
@ -672,13 +704,11 @@ def g(): pass
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
' ^^^^^^^^^^\n'
|
|
||||||
f' File "{__file__}", line {lineno_applydescs + 1}, in applydecs\n'
|
f' File "{__file__}", line {lineno_applydescs + 1}, in applydecs\n'
|
||||||
' @dec_error\n'
|
' @dec_error\n'
|
||||||
' ^^^^^^^^^\n'
|
' ^^^^^^^^^\n'
|
||||||
f' File "{__file__}", line {lineno_dec_error + 1}, in dec_error\n'
|
f' File "{__file__}", line {lineno_dec_error + 1}, in dec_error\n'
|
||||||
' raise TypeError\n'
|
' raise TypeError\n'
|
||||||
' ^^^^^^^^^^^^^^^\n'
|
|
||||||
)
|
)
|
||||||
self.assertEqual(result_lines, expected_error.splitlines())
|
self.assertEqual(result_lines, expected_error.splitlines())
|
||||||
|
|
||||||
|
|
@ -692,13 +722,11 @@ class A: pass
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
' ^^^^^^^^^^\n'
|
|
||||||
f' File "{__file__}", line {lineno_applydescs_class + 1}, in applydecs_class\n'
|
f' File "{__file__}", line {lineno_applydescs_class + 1}, in applydecs_class\n'
|
||||||
' @dec_error\n'
|
' @dec_error\n'
|
||||||
' ^^^^^^^^^\n'
|
' ^^^^^^^^^\n'
|
||||||
f' File "{__file__}", line {lineno_dec_error + 1}, in dec_error\n'
|
f' File "{__file__}", line {lineno_dec_error + 1}, in dec_error\n'
|
||||||
' raise TypeError\n'
|
' raise TypeError\n'
|
||||||
' ^^^^^^^^^^^^^^^\n'
|
|
||||||
)
|
)
|
||||||
self.assertEqual(result_lines, expected_error.splitlines())
|
self.assertEqual(result_lines, expected_error.splitlines())
|
||||||
|
|
||||||
|
|
@ -816,12 +844,8 @@ def check_traceback_format(self, cleanup_func=None):
|
||||||
# Make sure that the traceback is properly indented.
|
# Make sure that the traceback is properly indented.
|
||||||
tb_lines = python_fmt.splitlines()
|
tb_lines = python_fmt.splitlines()
|
||||||
banner = tb_lines[0]
|
banner = tb_lines[0]
|
||||||
if has_no_debug_ranges():
|
self.assertEqual(len(tb_lines), 5)
|
||||||
self.assertEqual(len(tb_lines), 5)
|
location, source_line = tb_lines[-2], tb_lines[-1]
|
||||||
location, source_line = tb_lines[-2], tb_lines[-1]
|
|
||||||
else:
|
|
||||||
self.assertEqual(len(tb_lines), 7)
|
|
||||||
location, source_line = tb_lines[-3], tb_lines[-2]
|
|
||||||
self.assertTrue(banner.startswith('Traceback'))
|
self.assertTrue(banner.startswith('Traceback'))
|
||||||
self.assertTrue(location.startswith(' File'))
|
self.assertTrue(location.startswith(' File'))
|
||||||
self.assertTrue(source_line.startswith(' raise'))
|
self.assertTrue(source_line.startswith(' raise'))
|
||||||
|
|
@ -885,16 +909,12 @@ def f():
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {lineno_f+5}, in _check_recursive_traceback_display\n'
|
f' File "{__file__}", line {lineno_f+5}, in _check_recursive_traceback_display\n'
|
||||||
' f()\n'
|
' f()\n'
|
||||||
' ^^^\n'
|
|
||||||
f' File "{__file__}", line {lineno_f+1}, in f\n'
|
f' File "{__file__}", line {lineno_f+1}, in f\n'
|
||||||
' f()\n'
|
' f()\n'
|
||||||
' ^^^\n'
|
|
||||||
f' File "{__file__}", line {lineno_f+1}, in f\n'
|
f' File "{__file__}", line {lineno_f+1}, in f\n'
|
||||||
' f()\n'
|
' f()\n'
|
||||||
' ^^^\n'
|
|
||||||
f' File "{__file__}", line {lineno_f+1}, in f\n'
|
f' File "{__file__}", line {lineno_f+1}, in f\n'
|
||||||
' f()\n'
|
' f()\n'
|
||||||
' ^^^\n'
|
|
||||||
# XXX: The following line changes depending on whether the tests
|
# XXX: The following line changes depending on whether the tests
|
||||||
# are run through the interactive interpreter or with -m
|
# are run through the interactive interpreter or with -m
|
||||||
# It also varies depending on the platform (stack size)
|
# It also varies depending on the platform (stack size)
|
||||||
|
|
@ -945,14 +965,12 @@ def g(count=10):
|
||||||
' [Previous line repeated 7 more times]\n'
|
' [Previous line repeated 7 more times]\n'
|
||||||
f' File "{__file__}", line {lineno_g+3}, in g\n'
|
f' File "{__file__}", line {lineno_g+3}, in g\n'
|
||||||
' raise ValueError\n'
|
' raise ValueError\n'
|
||||||
' ^^^^^^^^^^^^^^^^\n'
|
|
||||||
'ValueError\n'
|
'ValueError\n'
|
||||||
)
|
)
|
||||||
tb_line = (
|
tb_line = (
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {lineno_g+7}, in _check_recursive_traceback_display\n'
|
f' File "{__file__}", line {lineno_g+7}, in _check_recursive_traceback_display\n'
|
||||||
' g()\n'
|
' g()\n'
|
||||||
' ^^^\n'
|
|
||||||
)
|
)
|
||||||
expected = (tb_line + result_g).splitlines()
|
expected = (tb_line + result_g).splitlines()
|
||||||
actual = stderr_g.getvalue().splitlines()
|
actual = stderr_g.getvalue().splitlines()
|
||||||
|
|
@ -977,7 +995,6 @@ def h(count=10):
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {lineno_h+7}, in _check_recursive_traceback_display\n'
|
f' File "{__file__}", line {lineno_h+7}, in _check_recursive_traceback_display\n'
|
||||||
' h()\n'
|
' h()\n'
|
||||||
' ^^^\n'
|
|
||||||
f' File "{__file__}", line {lineno_h+2}, in h\n'
|
f' File "{__file__}", line {lineno_h+2}, in h\n'
|
||||||
' return h(count-1)\n'
|
' return h(count-1)\n'
|
||||||
' ^^^^^^^^^^\n'
|
' ^^^^^^^^^^\n'
|
||||||
|
|
@ -990,7 +1007,6 @@ def h(count=10):
|
||||||
' [Previous line repeated 7 more times]\n'
|
' [Previous line repeated 7 more times]\n'
|
||||||
f' File "{__file__}", line {lineno_h+3}, in h\n'
|
f' File "{__file__}", line {lineno_h+3}, in h\n'
|
||||||
' g()\n'
|
' g()\n'
|
||||||
' ^^^\n'
|
|
||||||
)
|
)
|
||||||
expected = (result_h + result_g).splitlines()
|
expected = (result_h + result_g).splitlines()
|
||||||
actual = stderr_h.getvalue().splitlines()
|
actual = stderr_h.getvalue().splitlines()
|
||||||
|
|
@ -1016,14 +1032,12 @@ def h(count=10):
|
||||||
' ^^^^^^^^^^\n'
|
' ^^^^^^^^^^\n'
|
||||||
f' File "{__file__}", line {lineno_g+3}, in g\n'
|
f' File "{__file__}", line {lineno_g+3}, in g\n'
|
||||||
' raise ValueError\n'
|
' raise ValueError\n'
|
||||||
' ^^^^^^^^^^^^^^^^\n'
|
|
||||||
'ValueError\n'
|
'ValueError\n'
|
||||||
)
|
)
|
||||||
tb_line = (
|
tb_line = (
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {lineno_g+81}, in _check_recursive_traceback_display\n'
|
f' File "{__file__}", line {lineno_g+77}, in _check_recursive_traceback_display\n'
|
||||||
' g(traceback._RECURSIVE_CUTOFF)\n'
|
' g(traceback._RECURSIVE_CUTOFF)\n'
|
||||||
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
|
||||||
)
|
)
|
||||||
expected = (tb_line + result_g).splitlines()
|
expected = (tb_line + result_g).splitlines()
|
||||||
actual = stderr_g.getvalue().splitlines()
|
actual = stderr_g.getvalue().splitlines()
|
||||||
|
|
@ -1050,14 +1064,12 @@ def h(count=10):
|
||||||
' [Previous line repeated 1 more time]\n'
|
' [Previous line repeated 1 more time]\n'
|
||||||
f' File "{__file__}", line {lineno_g+3}, in g\n'
|
f' File "{__file__}", line {lineno_g+3}, in g\n'
|
||||||
' raise ValueError\n'
|
' raise ValueError\n'
|
||||||
' ^^^^^^^^^^^^^^^^\n'
|
|
||||||
'ValueError\n'
|
'ValueError\n'
|
||||||
)
|
)
|
||||||
tb_line = (
|
tb_line = (
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {lineno_g+114}, in _check_recursive_traceback_display\n'
|
f' File "{__file__}", line {lineno_g+108}, in _check_recursive_traceback_display\n'
|
||||||
' g(traceback._RECURSIVE_CUTOFF + 1)\n'
|
' g(traceback._RECURSIVE_CUTOFF + 1)\n'
|
||||||
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
|
||||||
)
|
)
|
||||||
expected = (tb_line + result_g).splitlines()
|
expected = (tb_line + result_g).splitlines()
|
||||||
actual = stderr_g.getvalue().splitlines()
|
actual = stderr_g.getvalue().splitlines()
|
||||||
|
|
@ -1110,16 +1122,10 @@ def __eq__(self, other):
|
||||||
exception_print(exc_val)
|
exception_print(exc_val)
|
||||||
|
|
||||||
tb = stderr_f.getvalue().strip().splitlines()
|
tb = stderr_f.getvalue().strip().splitlines()
|
||||||
if has_no_debug_ranges():
|
self.assertEqual(11, len(tb))
|
||||||
self.assertEqual(11, len(tb))
|
self.assertEqual(context_message.strip(), tb[5])
|
||||||
self.assertEqual(context_message.strip(), tb[5])
|
self.assertIn('UnhashableException: ex2', tb[3])
|
||||||
self.assertIn('UnhashableException: ex2', tb[3])
|
self.assertIn('UnhashableException: ex1', tb[10])
|
||||||
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])
|
|
||||||
|
|
||||||
def deep_eg(self):
|
def deep_eg(self):
|
||||||
e = TypeError(1)
|
e = TypeError(1)
|
||||||
|
|
@ -1255,12 +1261,8 @@ def test_context_suppression(self):
|
||||||
except ZeroDivisionError as _:
|
except ZeroDivisionError as _:
|
||||||
e = _
|
e = _
|
||||||
lines = self.get_report(e).splitlines()
|
lines = self.get_report(e).splitlines()
|
||||||
if has_no_debug_ranges():
|
self.assertEqual(len(lines), 4)
|
||||||
self.assertEqual(len(lines), 4)
|
self.assertTrue(lines[3].startswith('ZeroDivisionError'))
|
||||||
self.assertTrue(lines[3].startswith('ZeroDivisionError'))
|
|
||||||
else:
|
|
||||||
self.assertEqual(len(lines), 5)
|
|
||||||
self.assertTrue(lines[4].startswith('ZeroDivisionError'))
|
|
||||||
self.assertTrue(lines[0].startswith('Traceback'))
|
self.assertTrue(lines[0].startswith('Traceback'))
|
||||||
self.assertTrue(lines[1].startswith(' File'))
|
self.assertTrue(lines[1].startswith(' File'))
|
||||||
self.assertIn('ZeroDivisionError from None', lines[2])
|
self.assertIn('ZeroDivisionError from None', lines[2])
|
||||||
|
|
@ -1510,10 +1512,8 @@ def exc():
|
||||||
f' + Exception Group Traceback (most recent call last):\n'
|
f' + Exception Group Traceback (most recent call last):\n'
|
||||||
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
f' | exception_or_callable()\n'
|
f' | exception_or_callable()\n'
|
||||||
f' | ^^^^^^^^^^^^^^^^^^^^^^^\n'
|
|
||||||
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 1}, in exc\n'
|
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 1}, in exc\n'
|
||||||
f' | raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])\n'
|
f' | raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])\n'
|
||||||
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
|
||||||
f' | ExceptionGroup: eg (2 sub-exceptions)\n'
|
f' | ExceptionGroup: eg (2 sub-exceptions)\n'
|
||||||
f' +-+---------------- 1 ----------------\n'
|
f' +-+---------------- 1 ----------------\n'
|
||||||
f' | ValueError: 1\n'
|
f' | ValueError: 1\n'
|
||||||
|
|
@ -1535,7 +1535,6 @@ def exc():
|
||||||
expected = (f' + Exception Group Traceback (most recent call last):\n'
|
expected = (f' + Exception Group Traceback (most recent call last):\n'
|
||||||
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 3}, in exc\n'
|
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 3}, in exc\n'
|
||||||
f' | raise EG("eg1", [ValueError(1), TypeError(2)])\n'
|
f' | raise EG("eg1", [ValueError(1), TypeError(2)])\n'
|
||||||
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
|
||||||
f' | ExceptionGroup: eg1 (2 sub-exceptions)\n'
|
f' | ExceptionGroup: eg1 (2 sub-exceptions)\n'
|
||||||
f' +-+---------------- 1 ----------------\n'
|
f' +-+---------------- 1 ----------------\n'
|
||||||
f' | ValueError: 1\n'
|
f' | ValueError: 1\n'
|
||||||
|
|
@ -1548,10 +1547,8 @@ def exc():
|
||||||
f' + Exception Group Traceback (most recent call last):\n'
|
f' + Exception Group Traceback (most recent call last):\n'
|
||||||
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
f' | exception_or_callable()\n'
|
f' | exception_or_callable()\n'
|
||||||
f' | ^^^^^^^^^^^^^^^^^^^^^^^\n'
|
|
||||||
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
|
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
|
||||||
f' | raise EG("eg2", [ValueError(3), TypeError(4)]) from e\n'
|
f' | raise EG("eg2", [ValueError(3), TypeError(4)]) from e\n'
|
||||||
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
|
||||||
f' | ExceptionGroup: eg2 (2 sub-exceptions)\n'
|
f' | ExceptionGroup: eg2 (2 sub-exceptions)\n'
|
||||||
f' +-+---------------- 1 ----------------\n'
|
f' +-+---------------- 1 ----------------\n'
|
||||||
f' | ValueError: 3\n'
|
f' | ValueError: 3\n'
|
||||||
|
|
@ -1577,7 +1574,6 @@ def exc():
|
||||||
f' + Exception Group Traceback (most recent call last):\n'
|
f' + Exception Group Traceback (most recent call last):\n'
|
||||||
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 4}, in exc\n'
|
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 4}, in exc\n'
|
||||||
f' | raise EG("eg1", [ValueError(1), TypeError(2)])\n'
|
f' | raise EG("eg1", [ValueError(1), TypeError(2)])\n'
|
||||||
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
|
||||||
f' | ExceptionGroup: eg1 (2 sub-exceptions)\n'
|
f' | ExceptionGroup: eg1 (2 sub-exceptions)\n'
|
||||||
f' +-+---------------- 1 ----------------\n'
|
f' +-+---------------- 1 ----------------\n'
|
||||||
f' | ValueError: 1\n'
|
f' | ValueError: 1\n'
|
||||||
|
|
@ -1590,7 +1586,6 @@ def exc():
|
||||||
f' + Exception Group Traceback (most recent call last):\n'
|
f' + Exception Group Traceback (most recent call last):\n'
|
||||||
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 6}, in exc\n'
|
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 6}, in exc\n'
|
||||||
f' | raise EG("eg2", [ValueError(3), TypeError(4)])\n'
|
f' | raise EG("eg2", [ValueError(3), TypeError(4)])\n'
|
||||||
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
|
||||||
f' | ExceptionGroup: eg2 (2 sub-exceptions)\n'
|
f' | ExceptionGroup: eg2 (2 sub-exceptions)\n'
|
||||||
f' +-+---------------- 1 ----------------\n'
|
f' +-+---------------- 1 ----------------\n'
|
||||||
f' | ValueError: 3\n'
|
f' | ValueError: 3\n'
|
||||||
|
|
@ -1603,10 +1598,8 @@ def exc():
|
||||||
f'Traceback (most recent call last):\n'
|
f'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
f' exception_or_callable()\n'
|
f' exception_or_callable()\n'
|
||||||
f' ^^^^^^^^^^^^^^^^^^^^^^^\n'
|
|
||||||
f' File "{__file__}", line {exc.__code__.co_firstlineno + 8}, in exc\n'
|
f' File "{__file__}", line {exc.__code__.co_firstlineno + 8}, in exc\n'
|
||||||
f' raise ImportError(5)\n'
|
f' raise ImportError(5)\n'
|
||||||
f' ^^^^^^^^^^^^^^^^^^^^\n'
|
|
||||||
f'ImportError: 5\n')
|
f'ImportError: 5\n')
|
||||||
|
|
||||||
report = self.get_report(exc)
|
report = self.get_report(exc)
|
||||||
|
|
@ -1629,7 +1622,6 @@ def exc():
|
||||||
expected = (f' + Exception Group Traceback (most recent call last):\n'
|
expected = (f' + Exception Group Traceback (most recent call last):\n'
|
||||||
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n'
|
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n'
|
||||||
f' | raise EG("eg", [VE(1), exc, VE(4)])\n'
|
f' | raise EG("eg", [VE(1), exc, VE(4)])\n'
|
||||||
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
|
||||||
f' | ExceptionGroup: eg (3 sub-exceptions)\n'
|
f' | ExceptionGroup: eg (3 sub-exceptions)\n'
|
||||||
f' +-+---------------- 1 ----------------\n'
|
f' +-+---------------- 1 ----------------\n'
|
||||||
f' | ValueError: 1\n'
|
f' | ValueError: 1\n'
|
||||||
|
|
@ -1637,7 +1629,6 @@ def exc():
|
||||||
f' | Exception Group Traceback (most recent call last):\n'
|
f' | Exception Group Traceback (most recent call last):\n'
|
||||||
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 6}, in exc\n'
|
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 6}, in exc\n'
|
||||||
f' | raise EG("nested", [TE(2), TE(3)])\n'
|
f' | raise EG("nested", [TE(2), TE(3)])\n'
|
||||||
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
|
||||||
f' | ExceptionGroup: nested (2 sub-exceptions)\n'
|
f' | ExceptionGroup: nested (2 sub-exceptions)\n'
|
||||||
f' +-+---------------- 1 ----------------\n'
|
f' +-+---------------- 1 ----------------\n'
|
||||||
f' | TypeError: 2\n'
|
f' | TypeError: 2\n'
|
||||||
|
|
@ -1653,10 +1644,8 @@ def exc():
|
||||||
f' + Exception Group Traceback (most recent call last):\n'
|
f' + Exception Group Traceback (most recent call last):\n'
|
||||||
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
f' | exception_or_callable()\n'
|
f' | exception_or_callable()\n'
|
||||||
f' | ^^^^^^^^^^^^^^^^^^^^^^^\n'
|
|
||||||
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 11}, in exc\n'
|
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 11}, in exc\n'
|
||||||
f' | raise EG("top", [VE(5)])\n'
|
f' | raise EG("top", [VE(5)])\n'
|
||||||
f' | ^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
|
||||||
f' | ExceptionGroup: top (1 sub-exception)\n'
|
f' | ExceptionGroup: top (1 sub-exception)\n'
|
||||||
f' +-+---------------- 1 ----------------\n'
|
f' +-+---------------- 1 ----------------\n'
|
||||||
f' | ValueError: 5\n'
|
f' | ValueError: 5\n'
|
||||||
|
|
@ -1814,10 +1803,8 @@ def exc():
|
||||||
expected = (f' + Exception Group Traceback (most recent call last):\n'
|
expected = (f' + Exception Group Traceback (most recent call last):\n'
|
||||||
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
f' | exception_or_callable()\n'
|
f' | exception_or_callable()\n'
|
||||||
f' | ^^^^^^^^^^^^^^^^^^^^^^^\n'
|
|
||||||
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n'
|
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n'
|
||||||
f' | raise ExceptionGroup("nested", excs)\n'
|
f' | raise ExceptionGroup("nested", excs)\n'
|
||||||
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
|
||||||
f' | ExceptionGroup: nested (2 sub-exceptions)\n'
|
f' | ExceptionGroup: nested (2 sub-exceptions)\n'
|
||||||
f' | >> Multi line note\n'
|
f' | >> Multi line note\n'
|
||||||
f' | >> Because I am such\n'
|
f' | >> Because I am such\n'
|
||||||
|
|
@ -1829,14 +1816,12 @@ def exc():
|
||||||
f' | Traceback (most recent call last):\n'
|
f' | Traceback (most recent call last):\n'
|
||||||
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
|
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
|
||||||
f' | raise ValueError(msg)\n'
|
f' | raise ValueError(msg)\n'
|
||||||
f' | ^^^^^^^^^^^^^^^^^^^^^\n'
|
|
||||||
f' | ValueError: bad value\n'
|
f' | ValueError: bad value\n'
|
||||||
f' | the bad value\n'
|
f' | the bad value\n'
|
||||||
f' +---------------- 2 ----------------\n'
|
f' +---------------- 2 ----------------\n'
|
||||||
f' | Traceback (most recent call last):\n'
|
f' | Traceback (most recent call last):\n'
|
||||||
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
|
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
|
||||||
f' | raise ValueError(msg)\n'
|
f' | raise ValueError(msg)\n'
|
||||||
f' | ^^^^^^^^^^^^^^^^^^^^^\n'
|
|
||||||
f' | ValueError: terrible value\n'
|
f' | ValueError: terrible value\n'
|
||||||
f' | the terrible value\n'
|
f' | the terrible value\n'
|
||||||
f' +------------------------------------\n')
|
f' +------------------------------------\n')
|
||||||
|
|
@ -1869,10 +1854,8 @@ def exc():
|
||||||
expected = (f' + Exception Group Traceback (most recent call last):\n'
|
expected = (f' + Exception Group Traceback (most recent call last):\n'
|
||||||
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
f' | exception_or_callable()\n'
|
f' | exception_or_callable()\n'
|
||||||
f' | ^^^^^^^^^^^^^^^^^^^^^^^\n'
|
|
||||||
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 10}, in exc\n'
|
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 10}, in exc\n'
|
||||||
f' | raise ExceptionGroup("nested", excs)\n'
|
f' | raise ExceptionGroup("nested", excs)\n'
|
||||||
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
|
||||||
f' | ExceptionGroup: nested (2 sub-exceptions)\n'
|
f' | ExceptionGroup: nested (2 sub-exceptions)\n'
|
||||||
f' | >> Multi line note\n'
|
f' | >> Multi line note\n'
|
||||||
f' | >> Because I am such\n'
|
f' | >> Because I am such\n'
|
||||||
|
|
@ -1885,7 +1868,6 @@ def exc():
|
||||||
f' | Traceback (most recent call last):\n'
|
f' | Traceback (most recent call last):\n'
|
||||||
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
|
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
|
||||||
f' | raise ValueError(msg)\n'
|
f' | raise ValueError(msg)\n'
|
||||||
f' | ^^^^^^^^^^^^^^^^^^^^^\n'
|
|
||||||
f' | ValueError: bad value\n'
|
f' | ValueError: bad value\n'
|
||||||
f' | the bad value\n'
|
f' | the bad value\n'
|
||||||
f' | Goodbye bad value\n'
|
f' | Goodbye bad value\n'
|
||||||
|
|
@ -1893,7 +1875,6 @@ def exc():
|
||||||
f' | Traceback (most recent call last):\n'
|
f' | Traceback (most recent call last):\n'
|
||||||
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
|
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
|
||||||
f' | raise ValueError(msg)\n'
|
f' | raise ValueError(msg)\n'
|
||||||
f' | ^^^^^^^^^^^^^^^^^^^^^\n'
|
|
||||||
f' | ValueError: terrible value\n'
|
f' | ValueError: terrible value\n'
|
||||||
f' | the terrible value\n'
|
f' | the terrible value\n'
|
||||||
f' | Goodbye terrible value\n'
|
f' | Goodbye terrible value\n'
|
||||||
|
|
@ -2669,19 +2650,16 @@ def test_exception_group_format(self):
|
||||||
f' + Exception Group Traceback (most recent call last):',
|
f' + Exception Group Traceback (most recent call last):',
|
||||||
f' | File "{__file__}", line {lno_g+23}, in _get_exception_group',
|
f' | File "{__file__}", line {lno_g+23}, in _get_exception_group',
|
||||||
f' | raise ExceptionGroup("eg2", [exc3, exc4])',
|
f' | raise ExceptionGroup("eg2", [exc3, exc4])',
|
||||||
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^',
|
|
||||||
f' | ExceptionGroup: eg2 (2 sub-exceptions)',
|
f' | ExceptionGroup: eg2 (2 sub-exceptions)',
|
||||||
f' +-+---------------- 1 ----------------',
|
f' +-+---------------- 1 ----------------',
|
||||||
f' | Exception Group Traceback (most recent call last):',
|
f' | Exception Group Traceback (most recent call last):',
|
||||||
f' | File "{__file__}", line {lno_g+16}, in _get_exception_group',
|
f' | File "{__file__}", line {lno_g+16}, in _get_exception_group',
|
||||||
f' | raise ExceptionGroup("eg1", [exc1, exc2])',
|
f' | raise ExceptionGroup("eg1", [exc1, exc2])',
|
||||||
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^',
|
|
||||||
f' | ExceptionGroup: eg1 (2 sub-exceptions)',
|
f' | ExceptionGroup: eg1 (2 sub-exceptions)',
|
||||||
f' +-+---------------- 1 ----------------',
|
f' +-+---------------- 1 ----------------',
|
||||||
f' | Traceback (most recent call last):',
|
f' | Traceback (most recent call last):',
|
||||||
f' | File "{__file__}", line {lno_g+9}, in _get_exception_group',
|
f' | File "{__file__}", line {lno_g+9}, in _get_exception_group',
|
||||||
f' | f()',
|
f' | f()',
|
||||||
f' | ^^^',
|
|
||||||
f' | File "{__file__}", line {lno_f+1}, in f',
|
f' | File "{__file__}", line {lno_f+1}, in f',
|
||||||
f' | 1/0',
|
f' | 1/0',
|
||||||
f' | ~^~',
|
f' | ~^~',
|
||||||
|
|
@ -2690,20 +2668,16 @@ def test_exception_group_format(self):
|
||||||
f' | Traceback (most recent call last):',
|
f' | Traceback (most recent call last):',
|
||||||
f' | File "{__file__}", line {lno_g+13}, in _get_exception_group',
|
f' | File "{__file__}", line {lno_g+13}, in _get_exception_group',
|
||||||
f' | g(42)',
|
f' | g(42)',
|
||||||
f' | ^^^^^',
|
|
||||||
f' | File "{__file__}", line {lno_g+1}, in g',
|
f' | File "{__file__}", line {lno_g+1}, in g',
|
||||||
f' | raise ValueError(v)',
|
f' | raise ValueError(v)',
|
||||||
f' | ^^^^^^^^^^^^^^^^^^^',
|
|
||||||
f' | ValueError: 42',
|
f' | ValueError: 42',
|
||||||
f' +------------------------------------',
|
f' +------------------------------------',
|
||||||
f' +---------------- 2 ----------------',
|
f' +---------------- 2 ----------------',
|
||||||
f' | Traceback (most recent call last):',
|
f' | Traceback (most recent call last):',
|
||||||
f' | File "{__file__}", line {lno_g+20}, in _get_exception_group',
|
f' | File "{__file__}", line {lno_g+20}, in _get_exception_group',
|
||||||
f' | g(24)',
|
f' | g(24)',
|
||||||
f' | ^^^^^',
|
|
||||||
f' | File "{__file__}", line {lno_g+1}, in g',
|
f' | File "{__file__}", line {lno_g+1}, in g',
|
||||||
f' | raise ValueError(v)',
|
f' | raise ValueError(v)',
|
||||||
f' | ^^^^^^^^^^^^^^^^^^^',
|
|
||||||
f' | ValueError: 24',
|
f' | ValueError: 24',
|
||||||
f' +------------------------------------',
|
f' +------------------------------------',
|
||||||
f'']
|
f'']
|
||||||
|
|
|
||||||
|
|
@ -465,7 +465,8 @@ def format_frame_summary(self, frame_summary):
|
||||||
row.append(' File "{}", line {}, in {}\n'.format(
|
row.append(' File "{}", line {}, in {}\n'.format(
|
||||||
frame_summary.filename, frame_summary.lineno, frame_summary.name))
|
frame_summary.filename, frame_summary.lineno, frame_summary.name))
|
||||||
if frame_summary.line:
|
if frame_summary.line:
|
||||||
row.append(' {}\n'.format(frame_summary.line.strip()))
|
stripped_line = frame_summary.line.strip()
|
||||||
|
row.append(' {}\n'.format(stripped_line))
|
||||||
|
|
||||||
orig_line_len = len(frame_summary._original_line)
|
orig_line_len = len(frame_summary._original_line)
|
||||||
frame_line_len = len(frame_summary.line.lstrip())
|
frame_line_len = len(frame_summary.line.lstrip())
|
||||||
|
|
@ -486,19 +487,22 @@ def format_frame_summary(self, frame_summary):
|
||||||
frame_summary._original_line[colno - 1:end_colno - 1]
|
frame_summary._original_line[colno - 1:end_colno - 1]
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
end_colno = stripped_characters + len(frame_summary.line.strip())
|
end_colno = stripped_characters + len(stripped_line)
|
||||||
|
|
||||||
row.append(' ')
|
# show indicators if primary char doesn't span the frame line
|
||||||
row.append(' ' * (colno - stripped_characters))
|
if end_colno - colno < len(stripped_line) or (
|
||||||
|
anchors and anchors.right_start_offset - anchors.left_end_offset > 0):
|
||||||
|
row.append(' ')
|
||||||
|
row.append(' ' * (colno - stripped_characters))
|
||||||
|
|
||||||
if anchors:
|
if anchors:
|
||||||
row.append(anchors.primary_char * (anchors.left_end_offset))
|
row.append(anchors.primary_char * (anchors.left_end_offset))
|
||||||
row.append(anchors.secondary_char * (anchors.right_start_offset - anchors.left_end_offset))
|
row.append(anchors.secondary_char * (anchors.right_start_offset - anchors.left_end_offset))
|
||||||
row.append(anchors.primary_char * (end_colno - colno - anchors.right_start_offset))
|
row.append(anchors.primary_char * (end_colno - colno - anchors.right_start_offset))
|
||||||
else:
|
else:
|
||||||
row.append('^' * (end_colno - colno))
|
row.append('^' * (end_colno - colno))
|
||||||
|
|
||||||
row.append('\n')
|
row.append('\n')
|
||||||
|
|
||||||
if frame_summary.locals:
|
if frame_summary.locals:
|
||||||
for name, value in sorted(frame_summary.locals.items()):
|
for name, value in sorted(frame_summary.locals.items()):
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
Revise the display strategy of traceback enhanced error locations. The indicators are only shown when the location doesn't span the whole line.
|
||||||
|
|
@ -592,7 +592,6 @@ _Py_DisplaySourceLine(PyObject *f, PyObject *filename, int lineno, int indent,
|
||||||
* Traceback (most recent call last):
|
* Traceback (most recent call last):
|
||||||
* File "/home/isidentical/cpython/cpython/t.py", line 10, in <module>
|
* File "/home/isidentical/cpython/cpython/t.py", line 10, in <module>
|
||||||
* add_values(1, 2, 'x', 3, 4)
|
* add_values(1, 2, 'x', 3, 4)
|
||||||
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
* File "/home/isidentical/cpython/cpython/t.py", line 2, in add_values
|
* File "/home/isidentical/cpython/cpython/t.py", line 2, in add_values
|
||||||
* return a + b + c + d + e
|
* return a + b + c + d + e
|
||||||
* ~~~~~~^~~
|
* ~~~~~~^~~
|
||||||
|
|
@ -736,7 +735,7 @@ print_error_location_carets(PyObject *f, int offset, Py_ssize_t start_offset, Py
|
||||||
int special_chars = (left_end_offset != -1 || right_start_offset != -1);
|
int special_chars = (left_end_offset != -1 || right_start_offset != -1);
|
||||||
const char *str;
|
const char *str;
|
||||||
while (++offset <= end_offset) {
|
while (++offset <= end_offset) {
|
||||||
if (offset <= start_offset || offset > end_offset) {
|
if (offset <= start_offset) {
|
||||||
str = " ";
|
str = " ";
|
||||||
} else if (special_chars && left_end_offset < offset && offset <= right_start_offset) {
|
} else if (special_chars && left_end_offset < offset && offset <= right_start_offset) {
|
||||||
str = secondary;
|
str = secondary;
|
||||||
|
|
@ -792,6 +791,7 @@ tb_displayline(PyTracebackObject* tb, PyObject *f, PyObject *filename, int linen
|
||||||
|
|
||||||
int code_offset = tb->tb_lasti;
|
int code_offset = tb->tb_lasti;
|
||||||
PyCodeObject* code = frame->f_frame->f_code;
|
PyCodeObject* code = frame->f_frame->f_code;
|
||||||
|
const Py_ssize_t source_line_len = PyUnicode_GET_LENGTH(source_line);
|
||||||
|
|
||||||
int start_line;
|
int start_line;
|
||||||
int end_line;
|
int end_line;
|
||||||
|
|
@ -813,7 +813,7 @@ tb_displayline(PyTracebackObject* tb, PyObject *f, PyObject *filename, int linen
|
||||||
//
|
//
|
||||||
// ERROR LINE ERROR LINE ERROR LINE ERROR LINE ERROR LINE ERROR LINE ERROR LINE
|
// ERROR LINE ERROR LINE ERROR LINE ERROR LINE ERROR LINE ERROR LINE ERROR LINE
|
||||||
// ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^~~~~~~~~~~~~~~~~~~~
|
// ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^~~~~~~~~~~~~~~~~~~~
|
||||||
// | |-> left_end_offset | |-> left_offset
|
// | |-> left_end_offset | |-> end_offset
|
||||||
// |-> start_offset |-> right_start_offset
|
// |-> start_offset |-> right_start_offset
|
||||||
//
|
//
|
||||||
// In general we will only have (start_offset, end_offset) but we can gather more information
|
// In general we will only have (start_offset, end_offset) but we can gather more information
|
||||||
|
|
@ -822,6 +822,9 @@ tb_displayline(PyTracebackObject* tb, PyObject *f, PyObject *filename, int linen
|
||||||
// the different ranges (primary_error_char and secondary_error_char). If we cannot obtain the
|
// the different ranges (primary_error_char and secondary_error_char). If we cannot obtain the
|
||||||
// AST information or we cannot identify special ranges within it, then left_end_offset and
|
// AST information or we cannot identify special ranges within it, then left_end_offset and
|
||||||
// right_end_offset will be set to -1.
|
// right_end_offset will be set to -1.
|
||||||
|
//
|
||||||
|
// To keep the column indicators pertinent, they are not shown when the primary character
|
||||||
|
// spans the whole line.
|
||||||
|
|
||||||
// Convert the utf-8 byte offset to the actual character offset so we print the right number of carets.
|
// Convert the utf-8 byte offset to the actual character offset so we print the right number of carets.
|
||||||
assert(source_line);
|
assert(source_line);
|
||||||
|
|
@ -859,7 +862,7 @@ tb_displayline(PyTracebackObject* tb, PyObject *f, PyObject *filename, int linen
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
Py_ssize_t i = PyUnicode_GET_LENGTH(source_line);
|
Py_ssize_t i = source_line_len;
|
||||||
while (--i >= 0) {
|
while (--i >= 0) {
|
||||||
if (!IS_WHITESPACE(source_line_str[i])) {
|
if (!IS_WHITESPACE(source_line_str[i])) {
|
||||||
break;
|
break;
|
||||||
|
|
@ -869,6 +872,13 @@ tb_displayline(PyTracebackObject* tb, PyObject *f, PyObject *filename, int linen
|
||||||
end_offset = i + 1;
|
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;
|
||||||
|
bool has_secondary_ranges = (left_end_offset != -1 || right_start_offset != -1);
|
||||||
|
if (end_offset - start_offset == stripped_line_len && !has_secondary_ranges) {
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
if (_Py_WriteIndentedMargin(margin_indent, margin, f) < 0) {
|
if (_Py_WriteIndentedMargin(margin_indent, margin, f) < 0) {
|
||||||
err = -1;
|
err = -1;
|
||||||
goto done;
|
goto done;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue