mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Update traceback from v3.14.3
This commit is contained in:
1 parent
c3212e7cd0
commit
7ccab4a4c8
2 files changed
+341
-156
No files matched your search
Vendored
+177
-97
@@ -18,8 +18,8 @@ import shutil
|
||||
from test.support import (Error, captured_output, cpython_only, ALWAYS_EQ,
|
||||
requires_debug_ranges, has_no_debug_ranges,
|
||||
requires_subprocess)
|
||||
from test.support.os_helper import TESTFN, unlink
|
||||
from test.support.script_helper import assert_python_ok, assert_python_failure
|
||||
from test.support.os_helper import TESTFN, temp_dir, unlink
|
||||
from test.support.script_helper import assert_python_ok, assert_python_failure, make_script
|
||||
from test.support.import_helper import forget
|
||||
from test.support import force_not_colorized, force_not_colorized_test_class
|
||||
|
||||
@@ -37,6 +37,12 @@ test_code.co_positions = lambda _: iter([(6, 6, 0, 0)])
|
||||
test_frame = namedtuple('frame', ['f_code', 'f_globals', 'f_locals'])
|
||||
test_tb = namedtuple('tb', ['tb_frame', 'tb_lineno', 'tb_next', 'tb_lasti'])
|
||||
|
||||
color_overrides = {"reset": "z", "filename": "fn", "error_highlight": "E"}
|
||||
colors = {
|
||||
color_overrides.get(k, k[0].lower()): v
|
||||
for k, v in _colorize.default_theme.traceback.items()
|
||||
}
|
||||
|
||||
|
||||
LEVENSHTEIN_DATA_FILE = Path(__file__).parent / 'levenshtein_examples.json'
|
||||
|
||||
@@ -87,7 +93,7 @@ class TracebackCases(unittest.TestCase):
|
||||
err = self.get_exception_format(self.syntax_error_with_caret,
|
||||
SyntaxError)
|
||||
self.assertEqual(len(err), 4)
|
||||
self.assertTrue(err[1].strip() == "return x!")
|
||||
self.assertEqual(err[1].strip(), "return x!")
|
||||
self.assertIn("^", err[2]) # third line has caret
|
||||
self.assertEqual(err[1].find("!"), err[2].find("^")) # in the right place
|
||||
self.assertEqual(err[2].count("^"), 1)
|
||||
@@ -402,8 +408,7 @@ class TracebackCases(unittest.TestCase):
|
||||
self.assertEqual(len(err), 1)
|
||||
self.assertEqual(err[-1], 'SyntaxError: error\n')
|
||||
|
||||
# TODO: RUSTPYTHON; IndexError: index out of range
|
||||
@unittest.expectedFailure
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; IndexError: index out of range
|
||||
@requires_subprocess()
|
||||
@force_not_colorized
|
||||
def test_encoded_file(self):
|
||||
@@ -447,16 +452,10 @@ class TracebackCases(unittest.TestCase):
|
||||
err_line = "raise RuntimeError('{0}')".format(message_ascii)
|
||||
err_msg = "RuntimeError: {0}".format(message_ascii)
|
||||
|
||||
self.assertIn(("line %s" % lineno), stdout[1],
|
||||
"Invalid line number: {0!r} instead of {1}".format(
|
||||
stdout[1], lineno))
|
||||
self.assertTrue(stdout[2].endswith(err_line),
|
||||
"Invalid traceback line: {0!r} instead of {1!r}".format(
|
||||
stdout[2], err_line))
|
||||
self.assertIn("line %s" % lineno, stdout[1])
|
||||
self.assertEndsWith(stdout[2], err_line)
|
||||
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))
|
||||
self.assertEqual(actual_err_msg, err_msg)
|
||||
|
||||
do_test("", "foo", "ascii", 3)
|
||||
for charset in ("ascii", "iso-8859-1", "utf-8", "GBK"):
|
||||
@@ -509,6 +508,33 @@ class TracebackCases(unittest.TestCase):
|
||||
b'ZeroDivisionError: division by zero']
|
||||
self.assertEqual(stderr.splitlines(), expected)
|
||||
|
||||
@cpython_only
|
||||
def test_lost_io_open(self):
|
||||
# GH-142737: Display the traceback even if io.open is lost
|
||||
crasher = textwrap.dedent("""\
|
||||
import io
|
||||
import traceback
|
||||
# Trigger fallback mode
|
||||
traceback._print_exception_bltin = None
|
||||
del io.open
|
||||
raise RuntimeError("should not crash")
|
||||
""")
|
||||
|
||||
# Create a temporary script to exercise _Py_FindSourceFile
|
||||
with temp_dir() as script_dir:
|
||||
script = make_script(
|
||||
script_dir=script_dir,
|
||||
script_basename='tb_test_no_io_open',
|
||||
source=crasher)
|
||||
rc, stdout, stderr = assert_python_failure(script)
|
||||
|
||||
self.assertEqual(rc, 1) # Make sure it's not a crash
|
||||
|
||||
expected = [b'Traceback (most recent call last):',
|
||||
f' File "{script}", line 6, in <module>'.encode(),
|
||||
b'RuntimeError: should not crash']
|
||||
self.assertEqual(stderr.splitlines(), expected)
|
||||
|
||||
def test_print_exception(self):
|
||||
output = StringIO()
|
||||
traceback.print_exception(
|
||||
@@ -611,7 +637,7 @@ class CAPIExceptionFormattingMixin:
|
||||
class CAPIExceptionFormattingLegacyMixin(CAPIExceptionFormattingMixin):
|
||||
LEGACY = 1
|
||||
|
||||
# @requires_debug_ranges() # XXX: RUSTPYTHON patch
|
||||
@requires_debug_ranges()
|
||||
class TracebackErrorLocationCaretTestBase:
|
||||
"""
|
||||
Tests for printing code error expressions as part of PEP 657
|
||||
@@ -658,6 +684,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||
def f_with_type():
|
||||
def foo(a: THIS_DOES_NOT_EXIST ) -> int:
|
||||
return 0
|
||||
foo.__annotations__
|
||||
|
||||
lineno_f = f_with_type.__code__.co_firstlineno
|
||||
expected_f = (
|
||||
@@ -665,7 +692,9 @@ class TracebackErrorLocationCaretTestBase:
|
||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||
' callable()\n'
|
||||
' ~~~~~~~~^^\n'
|
||||
f' File "{__file__}", line {lineno_f+1}, in f_with_type\n'
|
||||
f' File "{__file__}", line {lineno_f+3}, in f_with_type\n'
|
||||
' foo.__annotations__\n'
|
||||
f' File "{__file__}", line {lineno_f+1}, in __annotate__\n'
|
||||
' def foo(a: THIS_DOES_NOT_EXIST ) -> int:\n'
|
||||
' ^^^^^^^^^^^^^^^^^^^\n'
|
||||
)
|
||||
@@ -1742,8 +1771,53 @@ class TracebackErrorLocationCaretTestBase:
|
||||
]
|
||||
self.assertEqual(result_lines, expected)
|
||||
|
||||
class TestKeywordTypoSuggestions(unittest.TestCase):
|
||||
TYPO_CASES = [
|
||||
("with block ad something:\n pass", "and"),
|
||||
("fur a in b:\n pass", "for"),
|
||||
("for a in b:\n pass\nelso:\n pass", "else"),
|
||||
("whille True:\n pass", "while"),
|
||||
("iff x > 5:\n pass", "if"),
|
||||
("if x:\n pass\nelseif y:\n pass", "elif"),
|
||||
("tyo:\n pass\nexcept y:\n pass", "try"),
|
||||
("classe MyClass:\n pass", "class"),
|
||||
("impor math", "import"),
|
||||
("form x import y", "from"),
|
||||
("defn calculate_sum(a, b):\n return a + b", "def"),
|
||||
("def foo():\n returm result", "return"),
|
||||
("lamda x: x ** 2", "lambda"),
|
||||
("def foo():\n yeld i", "yield"),
|
||||
("def foo():\n globel counter", "global"),
|
||||
("frum math import sqrt", "from"),
|
||||
("asynch def fetch_data():\n pass", "async"),
|
||||
("async def foo():\n awaid fetch_data()", "await"),
|
||||
('raisee ValueError("Error")', "raise"),
|
||||
("[x for x\nin range(3)\nof x]", "if"),
|
||||
("[123 fur x\nin range(3)\nif x]", "for"),
|
||||
("for x im n:\n pass", "in"),
|
||||
]
|
||||
|
||||
# @requires_debug_ranges() # XXX: RUSTPYTHON patch
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_keyword_suggestions_from_file(self):
|
||||
with tempfile.TemporaryDirectory() as script_dir:
|
||||
for i, (code, expected_kw) in enumerate(self.TYPO_CASES):
|
||||
with self.subTest(typo=expected_kw):
|
||||
source = textwrap.dedent(code).strip()
|
||||
script_name = make_script(script_dir, f"script_{i}", source)
|
||||
rc, stdout, stderr = assert_python_failure(script_name)
|
||||
stderr_text = stderr.decode('utf-8')
|
||||
self.assertIn(f"Did you mean '{expected_kw}'", stderr_text)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_keyword_suggestions_from_command_string(self):
|
||||
for code, expected_kw in self.TYPO_CASES:
|
||||
with self.subTest(typo=expected_kw):
|
||||
source = textwrap.dedent(code).strip()
|
||||
rc, stdout, stderr = assert_python_failure('-c', source)
|
||||
stderr_text = stderr.decode('utf-8')
|
||||
self.assertIn(f"Did you mean '{expected_kw}'", stderr_text)
|
||||
|
||||
@requires_debug_ranges()
|
||||
@force_not_colorized_test_class
|
||||
class PurePythonTracebackErrorCaretTests(
|
||||
PurePythonExceptionFormattingMixin,
|
||||
@@ -1869,7 +1943,7 @@ class PurePythonTracebackErrorCaretTests(
|
||||
|
||||
|
||||
@cpython_only
|
||||
# @requires_debug_ranges() # XXX: RUSTPYTHON patch
|
||||
@requires_debug_ranges()
|
||||
@force_not_colorized_test_class
|
||||
class CPythonTracebackErrorCaretTests(
|
||||
CAPIExceptionFormattingMixin,
|
||||
@@ -1881,7 +1955,7 @@ class CPythonTracebackErrorCaretTests(
|
||||
"""
|
||||
|
||||
@cpython_only
|
||||
# @requires_debug_ranges() # XXX: RUSTPYTHON patch
|
||||
@requires_debug_ranges()
|
||||
@force_not_colorized_test_class
|
||||
class CPythonTracebackLegacyErrorCaretTests(
|
||||
CAPIExceptionFormattingLegacyMixin,
|
||||
@@ -1953,9 +2027,9 @@ class TracebackFormatMixin:
|
||||
banner = tb_lines[0]
|
||||
self.assertEqual(len(tb_lines), 5)
|
||||
location, source_line = tb_lines[-2], tb_lines[-1]
|
||||
self.assertTrue(banner.startswith('Traceback'))
|
||||
self.assertTrue(location.startswith(' File'))
|
||||
self.assertTrue(source_line.startswith(' raise'))
|
||||
self.assertStartsWith(banner, 'Traceback')
|
||||
self.assertStartsWith(location, ' File')
|
||||
self.assertStartsWith(source_line, ' raise')
|
||||
|
||||
def test_traceback_format(self):
|
||||
self.check_traceback_format()
|
||||
@@ -2188,7 +2262,7 @@ class TracebackFormatMixin:
|
||||
actual = stderr_g.getvalue().splitlines()
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
# @requires_debug_ranges() # XXX: RUSTPYTHON patch
|
||||
@requires_debug_ranges()
|
||||
def test_recursive_traceback(self):
|
||||
if self.DEBUG_RANGES:
|
||||
self._check_recursive_traceback_display(traceback.print_exc)
|
||||
@@ -2244,6 +2318,7 @@ class TracebackFormatMixin:
|
||||
return e
|
||||
|
||||
@cpython_only
|
||||
@support.skip_emscripten_stack_overflow()
|
||||
def test_exception_group_deep_recursion_capi(self):
|
||||
from _testcapi import exception_print
|
||||
LIMIT = 75
|
||||
@@ -2255,6 +2330,7 @@ class TracebackFormatMixin:
|
||||
self.assertIn('ExceptionGroup', output)
|
||||
self.assertLessEqual(output.count('ExceptionGroup'), LIMIT)
|
||||
|
||||
@support.skip_emscripten_stack_overflow()
|
||||
def test_exception_group_deep_recursion_traceback(self):
|
||||
LIMIT = 75
|
||||
eg = self.deep_eg()
|
||||
@@ -2332,12 +2408,12 @@ class BaseExceptionReportingTests:
|
||||
def check_zero_div(self, msg):
|
||||
lines = msg.splitlines()
|
||||
if has_no_debug_ranges():
|
||||
self.assertTrue(lines[-3].startswith(' File'))
|
||||
self.assertStartsWith(lines[-3], ' File')
|
||||
self.assertIn('1/0 # In zero_div', lines[-2])
|
||||
else:
|
||||
self.assertTrue(lines[-4].startswith(' File'))
|
||||
self.assertStartsWith(lines[-4], ' File')
|
||||
self.assertIn('1/0 # In zero_div', lines[-3])
|
||||
self.assertTrue(lines[-1].startswith('ZeroDivisionError'), lines[-1])
|
||||
self.assertStartsWith(lines[-1], 'ZeroDivisionError')
|
||||
|
||||
def test_simple(self):
|
||||
try:
|
||||
@@ -2347,12 +2423,12 @@ class BaseExceptionReportingTests:
|
||||
lines = self.get_report(e).splitlines()
|
||||
if has_no_debug_ranges():
|
||||
self.assertEqual(len(lines), 4)
|
||||
self.assertTrue(lines[3].startswith('ZeroDivisionError'))
|
||||
self.assertStartsWith(lines[3], 'ZeroDivisionError')
|
||||
else:
|
||||
self.assertEqual(len(lines), 5)
|
||||
self.assertTrue(lines[4].startswith('ZeroDivisionError'))
|
||||
self.assertTrue(lines[0].startswith('Traceback'))
|
||||
self.assertTrue(lines[1].startswith(' File'))
|
||||
self.assertStartsWith(lines[4], 'ZeroDivisionError')
|
||||
self.assertStartsWith(lines[0], 'Traceback')
|
||||
self.assertStartsWith(lines[1], ' File')
|
||||
self.assertIn('1/0 # Marker', lines[2])
|
||||
|
||||
def test_cause(self):
|
||||
@@ -2393,9 +2469,9 @@ class BaseExceptionReportingTests:
|
||||
e = _
|
||||
lines = self.get_report(e).splitlines()
|
||||
self.assertEqual(len(lines), 4)
|
||||
self.assertTrue(lines[3].startswith('ZeroDivisionError'))
|
||||
self.assertTrue(lines[0].startswith('Traceback'))
|
||||
self.assertTrue(lines[1].startswith(' File'))
|
||||
self.assertStartsWith(lines[3], 'ZeroDivisionError')
|
||||
self.assertStartsWith(lines[0], 'Traceback')
|
||||
self.assertStartsWith(lines[1], ' File')
|
||||
self.assertIn('ZeroDivisionError from None', lines[2])
|
||||
|
||||
def test_cause_and_context(self):
|
||||
@@ -3061,8 +3137,6 @@ class BaseExceptionReportingTests:
|
||||
report = self.get_report(exc)
|
||||
self.assertEqual(report, expected)
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
'''
|
||||
def test_exception_group_wrapped_naked(self):
|
||||
# See gh-128799
|
||||
|
||||
@@ -3114,7 +3188,7 @@ class BaseExceptionReportingTests:
|
||||
# remove trailing writespace:
|
||||
report = '\n'.join([l.rstrip() for l in report.split('\n')])
|
||||
self.assertEqual(report, expected)
|
||||
'''
|
||||
|
||||
|
||||
@force_not_colorized_test_class
|
||||
class PyExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
|
||||
@@ -3132,6 +3206,10 @@ class PyExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
|
||||
self.assertEqual(sio.getvalue(), s)
|
||||
return s
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; Diff is 1103 characters long. Set self.maxDiff to None to see it.
|
||||
def test_exception_group_wrapped_naked(self):
|
||||
return super().test_exception_group_wrapped_naked()
|
||||
|
||||
|
||||
@force_not_colorized_test_class
|
||||
class CExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
|
||||
@@ -3357,11 +3435,17 @@ class TestStack(unittest.TestCase):
|
||||
def test_walk_stack(self):
|
||||
def deeper():
|
||||
return list(traceback.walk_stack(None))
|
||||
s1 = list(traceback.walk_stack(None))
|
||||
s2 = deeper()
|
||||
s1, s2 = list(traceback.walk_stack(None)), deeper()
|
||||
self.assertEqual(len(s2) - len(s1), 1)
|
||||
self.assertEqual(s2[1:], s1)
|
||||
|
||||
def test_walk_innermost_frame(self):
|
||||
def inner():
|
||||
return list(traceback.walk_stack(None))
|
||||
frames = inner()
|
||||
innermost_frame, _ = frames[0]
|
||||
self.assertEqual(innermost_frame.f_code.co_name, "inner")
|
||||
|
||||
def test_walk_tb(self):
|
||||
try:
|
||||
1/0
|
||||
@@ -3729,6 +3813,7 @@ class TestTracebackException(unittest.TestCase):
|
||||
self.assertIsNone(te.exc_type)
|
||||
|
||||
def test_no_refs_to_exception_and_traceback_objects(self):
|
||||
exc_obj = None
|
||||
try:
|
||||
1/0
|
||||
except Exception as e:
|
||||
@@ -3870,8 +3955,8 @@ class TestTracebackException(unittest.TestCase):
|
||||
exc = traceback.TracebackException(Exception, Exception("haven"), None)
|
||||
self.assertEqual(list(exc.format()), ["Exception: haven\n"])
|
||||
|
||||
# @requires_debug_ranges() # XXX: RUSTPYTHON patch
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; ? ^ +
|
||||
@requires_debug_ranges()
|
||||
def test_print(self):
|
||||
def f():
|
||||
x = 12
|
||||
@@ -4788,9 +4873,8 @@ class MiscTest(unittest.TestCase):
|
||||
|
||||
def test_all(self):
|
||||
expected = set()
|
||||
denylist = {'print_list'}
|
||||
for name in dir(traceback):
|
||||
if name.startswith('_') or name in denylist:
|
||||
if name.startswith('_'):
|
||||
continue
|
||||
module_object = getattr(traceback, name)
|
||||
if getattr(module_object, '__module__', None) == 'traceback':
|
||||
@@ -4883,6 +4967,8 @@ class MiscTest(unittest.TestCase):
|
||||
|
||||
|
||||
class TestColorizedTraceback(unittest.TestCase):
|
||||
maxDiff = None
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "y = \x1b[31mx['a']['b']\x1b[0m\x1b[1;31m['c']\x1b[0m" not found in 'Traceback (most recent call last):\n File \x1b[35m"/Users/al03219714/Projects/RustPython/crates/pylib/Lib/test/test_traceback.py"\x1b[0m, line \x1b[35m4764\x1b[0m, in \x1b[35mtest_colorized_traceback\x1b[0m\n \x1b[31mbar\x1b[0m\x1b[1;31m()\x1b[0m\n \x1b[31m~~~\x1b[0m\x1b[1;31m^^\x1b[0m\n bar = <function TestColorizedTraceback.test_colorized_traceback.<locals>.bar at 0xb57b09180>\n baz1 = <function TestColorizedTraceback.test_colorized_traceback.<locals>.baz1 at 0xb57b09e00>\n baz2 = <function TestColorizedTraceback.test_colorized_traceback.<locals>.baz2 at 0xb57b09cc0>\n e = TypeError("\'NoneType\' object is not subscriptable")\n foo = <function TestColorizedTraceback.test_colorized_traceback.<locals>.foo at 0xb57b08140>\n self = <test.test_traceback.TestColorizedTraceback testMethod=test_colorized_traceback>\n File \x1b[35m"/Users/al03219714/Projects/RustPython/crates/pylib/Lib/test/test_traceback.py"\x1b[0m, line \x1b[35m4760\x1b[0m, in \x1b[35mbar\x1b[0m\n return baz1(1,\n 2,3\n ,4)\n baz1 = <function TestColorizedTraceback.test_colorized_traceback.<locals>.baz1 at 0xb57b09e00>\n File \x1b[35m"/Users/al03219714/Projects/RustPython/crates/pylib/Lib/test/test_traceback.py"\x1b[0m, line \x1b[35m4757\x1b[0m, in \x1b[35mbaz1\x1b[0m\n return baz2(1,2,3,4)\n args = (1, 2, 3, 4)\n baz2 = <function TestColorizedTraceback.test_colorized_traceback.<locals>.baz2 at 0xb57b09cc0>\n File \x1b[35m"/Users/al03219714/Projects/RustPython/crates/pylib/Lib/test/test_traceback.py"\x1b[0m, line \x1b[35m4754\x1b[0m, in \x1b[35mbaz2\x1b[0m\n return \x1b[31m(lambda *args: foo(*args))\x1b[0m\x1b[1;31m(1,2,3,4)\x1b[0m\n \x1b[31m~~~~~~~~~~~~~~~~~~~~~~~~~~\x1b[0m\x1b[1;31m^^^^^^^^^\x1b[0m\n args = (1, 2, 3, 4)\n foo = <function TestColorizedTraceback.test_colorized_traceback.<locals>.foo at 0xb57b08140>\n File \x1b[35m"/Users/al03219714/Projects/RustPython/crates/pylib/Lib/test/test_traceback.py"\x1b[0m, line \x1b[35m4754\x1b[0m, in \x1b[35m<lambda>\x1b[0m\n return (lambda *args: \x1b[31mfoo\x1b[0m\x1b[1;31m(*args)\x1b[0m)(1,2,3,4)\n \x1b[31m~~~\x1b[0m\x1b[1;31m^^^^^^^\x1b[0m\n args = (1, 2, 3, 4)\n foo = <function TestColorizedTraceback.test_colorized_traceback.<locals>.foo at 0xb57b08140>\n File \x1b[35m"/Users/al03219714/Projects/RustPython/crates/pylib/Lib/test/test_traceback.py"\x1b[0m, line \x1b[35m4751\x1b[0m, in \x1b[35mfoo\x1b[0m\n y = x[\'a\'][\'b\'][\x1b[1;31m\'c\'\x1b[0m]\n \x1b[1;31m^^^\x1b[0m\n args = (1, 2, 3, 4)\n x = {\'a\': {\'b\': None}}\n\x1b[1;35mTypeError\x1b[0m: \x1b[35m\'NoneType\' object is not subscriptable\x1b[0m\n'
|
||||
def test_colorized_traceback(self):
|
||||
def foo(*args):
|
||||
@@ -4906,9 +4992,9 @@ class TestColorizedTraceback(unittest.TestCase):
|
||||
e, capture_locals=True
|
||||
)
|
||||
lines = "".join(exc.format(colorize=True))
|
||||
red = _colorize.ANSIColors.RED
|
||||
boldr = _colorize.ANSIColors.BOLD_RED
|
||||
reset = _colorize.ANSIColors.RESET
|
||||
red = colors["e"]
|
||||
boldr = colors["E"]
|
||||
reset = colors["z"]
|
||||
self.assertIn("y = " + red + "x['a']['b']" + reset + boldr + "['c']" + reset, lines)
|
||||
self.assertIn("return " + red + "(lambda *args: foo(*args))" + reset + boldr + "(1,2,3,4)" + reset, lines)
|
||||
self.assertIn("return (lambda *args: " + red + "foo" + reset + boldr + "(*args)" + reset + ")(1,2,3,4)", lines)
|
||||
@@ -4925,18 +5011,16 @@ class TestColorizedTraceback(unittest.TestCase):
|
||||
e, capture_locals=True
|
||||
)
|
||||
actual = "".join(exc.format(colorize=True))
|
||||
red = _colorize.ANSIColors.RED
|
||||
magenta = _colorize.ANSIColors.MAGENTA
|
||||
boldm = _colorize.ANSIColors.BOLD_MAGENTA
|
||||
boldr = _colorize.ANSIColors.BOLD_RED
|
||||
reset = _colorize.ANSIColors.RESET
|
||||
expected = "".join([
|
||||
f' File {magenta}"<string>"{reset}, line {magenta}1{reset}\n',
|
||||
f' a {boldr}${reset} b\n',
|
||||
f' {boldr}^{reset}\n',
|
||||
f'{boldm}SyntaxError{reset}: {magenta}invalid syntax{reset}\n']
|
||||
)
|
||||
self.assertIn(expected, actual)
|
||||
def expected(t, m, fn, l, f, E, e, z):
|
||||
return "".join(
|
||||
[
|
||||
f' File {fn}"<string>"{z}, line {l}1{z}\n',
|
||||
f' a {E}${z} b\n',
|
||||
f' {E}^{z}\n',
|
||||
f'{t}SyntaxError{z}: {m}invalid syntax{z}\n'
|
||||
]
|
||||
)
|
||||
self.assertIn(expected(**colors), actual)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; ModuleNotFoundError: No module named '_testcapi'
|
||||
def test_colorized_traceback_is_the_default(self):
|
||||
@@ -4953,23 +5037,21 @@ class TestColorizedTraceback(unittest.TestCase):
|
||||
exception_print(e)
|
||||
actual = tbstderr.getvalue().splitlines()
|
||||
|
||||
red = _colorize.ANSIColors.RED
|
||||
boldr = _colorize.ANSIColors.BOLD_RED
|
||||
magenta = _colorize.ANSIColors.MAGENTA
|
||||
boldm = _colorize.ANSIColors.BOLD_MAGENTA
|
||||
reset = _colorize.ANSIColors.RESET
|
||||
lno_foo = foo.__code__.co_firstlineno
|
||||
expected = ['Traceback (most recent call last):',
|
||||
f' File {magenta}"{__file__}"{reset}, '
|
||||
f'line {magenta}{lno_foo+5}{reset}, in {magenta}test_colorized_traceback_is_the_default{reset}',
|
||||
f' {red}foo{reset+boldr}(){reset}',
|
||||
f' {red}~~~{reset+boldr}^^{reset}',
|
||||
f' File {magenta}"{__file__}"{reset}, '
|
||||
f'line {magenta}{lno_foo+1}{reset}, in {magenta}foo{reset}',
|
||||
f' {red}1{reset+boldr}/{reset+red}0{reset}',
|
||||
f' {red}~{reset+boldr}^{reset+red}~{reset}',
|
||||
f'{boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}']
|
||||
self.assertEqual(actual, expected)
|
||||
def expected(t, m, fn, l, f, E, e, z):
|
||||
return [
|
||||
'Traceback (most recent call last):',
|
||||
f' File {fn}"{__file__}"{z}, '
|
||||
f'line {l}{lno_foo+5}{z}, in {f}test_colorized_traceback_is_the_default{z}',
|
||||
f' {e}foo{z}{E}(){z}',
|
||||
f' {e}~~~{z}{E}^^{z}',
|
||||
f' File {fn}"{__file__}"{z}, '
|
||||
f'line {l}{lno_foo+1}{z}, in {f}foo{z}',
|
||||
f' {e}1{z}{E}/{z}{e}0{z}',
|
||||
f' {e}~{z}{E}^{z}{e}~{z}',
|
||||
f'{t}ZeroDivisionError{z}: {m}division by zero{z}',
|
||||
]
|
||||
self.assertEqual(actual, expected(**colors))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; Diff is 1795 characters long. Set self.maxDiff to None to see it.
|
||||
def test_colorized_traceback_from_exception_group(self):
|
||||
@@ -4988,33 +5070,31 @@ class TestColorizedTraceback(unittest.TestCase):
|
||||
e, capture_locals=True
|
||||
)
|
||||
|
||||
red = _colorize.ANSIColors.RED
|
||||
boldr = _colorize.ANSIColors.BOLD_RED
|
||||
magenta = _colorize.ANSIColors.MAGENTA
|
||||
boldm = _colorize.ANSIColors.BOLD_MAGENTA
|
||||
reset = _colorize.ANSIColors.RESET
|
||||
lno_foo = foo.__code__.co_firstlineno
|
||||
actual = "".join(exc.format(colorize=True)).splitlines()
|
||||
expected = [f" + Exception Group Traceback (most recent call last):",
|
||||
f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+9}{reset}, in {magenta}test_colorized_traceback_from_exception_group{reset}',
|
||||
f' | {red}foo{reset}{boldr}(){reset}',
|
||||
f' | {red}~~~{reset}{boldr}^^{reset}',
|
||||
f" | e = ExceptionGroup('test', [ZeroDivisionError('division by zero')])",
|
||||
f" | foo = {foo}",
|
||||
f' | self = <{__name__}.TestColorizedTraceback testMethod=test_colorized_traceback_from_exception_group>',
|
||||
f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+6}{reset}, in {magenta}foo{reset}',
|
||||
f' | raise ExceptionGroup("test", exceptions)',
|
||||
f" | exceptions = [ZeroDivisionError('division by zero')]",
|
||||
f' | {boldm}ExceptionGroup{reset}: {magenta}test (1 sub-exception){reset}',
|
||||
f' +-+---------------- 1 ----------------',
|
||||
f' | Traceback (most recent call last):',
|
||||
f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+3}{reset}, in {magenta}foo{reset}',
|
||||
f' | {red}1 {reset}{boldr}/{reset}{red} 0{reset}',
|
||||
f' | {red}~~{reset}{boldr}^{reset}{red}~~{reset}',
|
||||
f" | exceptions = [ZeroDivisionError('division by zero')]",
|
||||
f' | {boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}',
|
||||
f' +------------------------------------']
|
||||
self.assertEqual(actual, expected)
|
||||
def expected(t, m, fn, l, f, E, e, z):
|
||||
return [
|
||||
f" + Exception Group Traceback (most recent call last):",
|
||||
f' | File {fn}"{__file__}"{z}, line {l}{lno_foo+9}{z}, in {f}test_colorized_traceback_from_exception_group{z}',
|
||||
f' | {e}foo{z}{E}(){z}',
|
||||
f' | {e}~~~{z}{E}^^{z}',
|
||||
f" | e = ExceptionGroup('test', [ZeroDivisionError('division by zero')])",
|
||||
f" | foo = {foo}",
|
||||
f' | self = <{__name__}.TestColorizedTraceback testMethod=test_colorized_traceback_from_exception_group>',
|
||||
f' | File {fn}"{__file__}"{z}, line {l}{lno_foo+6}{z}, in {f}foo{z}',
|
||||
f' | raise ExceptionGroup("test", exceptions)',
|
||||
f" | exceptions = [ZeroDivisionError('division by zero')]",
|
||||
f' | {t}ExceptionGroup{z}: {m}test (1 sub-exception){z}',
|
||||
f' +-+---------------- 1 ----------------',
|
||||
f' | Traceback (most recent call last):',
|
||||
f' | File {fn}"{__file__}"{z}, line {l}{lno_foo+3}{z}, in {f}foo{z}',
|
||||
f' | {e}1 {z}{E}/{z}{e} 0{z}',
|
||||
f' | {e}~~{z}{E}^{z}{e}~~{z}',
|
||||
f" | exceptions = [ZeroDivisionError('division by zero')]",
|
||||
f' | {t}ZeroDivisionError{z}: {m}division by zero{z}',
|
||||
f' +------------------------------------',
|
||||
]
|
||||
self.assertEqual(actual, expected(**colors))
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Vendored
+164
-59
@@ -6,16 +6,20 @@ import linecache
|
||||
import sys
|
||||
import textwrap
|
||||
import warnings
|
||||
from contextlib import suppress
|
||||
import codeop
|
||||
import keyword
|
||||
import tokenize
|
||||
import io
|
||||
import _colorize
|
||||
from _colorize import ANSIColors
|
||||
|
||||
from contextlib import suppress
|
||||
|
||||
__all__ = ['extract_stack', 'extract_tb', 'format_exception',
|
||||
'format_exception_only', 'format_list', 'format_stack',
|
||||
'format_tb', 'print_exc', 'format_exc', 'print_exception',
|
||||
'print_last', 'print_stack', 'print_tb', 'clear_frames',
|
||||
'FrameSummary', 'StackSummary', 'TracebackException',
|
||||
'walk_stack', 'walk_tb']
|
||||
'walk_stack', 'walk_tb', 'print_list']
|
||||
|
||||
#
|
||||
# Formatting and printing lists of traceback lines.
|
||||
@@ -183,15 +187,13 @@ def _format_final_exc_line(etype, value, *, insert_final_newline=True, colorize=
|
||||
valuestr = _safe_string(value, 'exception')
|
||||
end_char = "\n" if insert_final_newline else ""
|
||||
if colorize:
|
||||
if value is None or not valuestr:
|
||||
line = f"{ANSIColors.BOLD_MAGENTA}{etype}{ANSIColors.RESET}{end_char}"
|
||||
else:
|
||||
line = f"{ANSIColors.BOLD_MAGENTA}{etype}{ANSIColors.RESET}: {ANSIColors.MAGENTA}{valuestr}{ANSIColors.RESET}{end_char}"
|
||||
theme = _colorize.get_theme(force_color=True).traceback
|
||||
else:
|
||||
if value is None or not valuestr:
|
||||
line = f"{etype}{end_char}"
|
||||
else:
|
||||
line = f"{etype}: {valuestr}{end_char}"
|
||||
theme = _colorize.get_theme(force_no_color=True).traceback
|
||||
if value is None or not valuestr:
|
||||
line = f"{theme.type}{etype}{theme.reset}{end_char}"
|
||||
else:
|
||||
line = f"{theme.type}{etype}{theme.reset}: {theme.message}{valuestr}{theme.reset}{end_char}"
|
||||
return line
|
||||
|
||||
|
||||
@@ -384,10 +386,14 @@ def walk_stack(f):
|
||||
current stack is used. Usually used with StackSummary.extract.
|
||||
"""
|
||||
if f is None:
|
||||
f = sys._getframe().f_back.f_back.f_back.f_back
|
||||
while f is not None:
|
||||
yield f, f.f_lineno
|
||||
f = f.f_back
|
||||
f = sys._getframe().f_back
|
||||
|
||||
def walk_stack_generator(frame):
|
||||
while frame is not None:
|
||||
yield frame, frame.f_lineno
|
||||
frame = frame.f_back
|
||||
|
||||
return walk_stack_generator(f)
|
||||
|
||||
|
||||
def walk_tb(tb):
|
||||
@@ -531,21 +537,22 @@ class StackSummary(list):
|
||||
if frame_summary.filename.startswith("<stdin-") and frame_summary.filename.endswith('>'):
|
||||
filename = "<stdin>"
|
||||
if colorize:
|
||||
row.append(' File {}"{}"{}, line {}{}{}, in {}{}{}\n'.format(
|
||||
ANSIColors.MAGENTA,
|
||||
filename,
|
||||
ANSIColors.RESET,
|
||||
ANSIColors.MAGENTA,
|
||||
frame_summary.lineno,
|
||||
ANSIColors.RESET,
|
||||
ANSIColors.MAGENTA,
|
||||
frame_summary.name,
|
||||
ANSIColors.RESET,
|
||||
)
|
||||
)
|
||||
theme = _colorize.get_theme(force_color=True).traceback
|
||||
else:
|
||||
row.append(' File "{}", line {}, in {}\n'.format(
|
||||
filename, frame_summary.lineno, frame_summary.name))
|
||||
theme = _colorize.get_theme(force_no_color=True).traceback
|
||||
row.append(
|
||||
' File {}"{}"{}, line {}{}{}, in {}{}{}\n'.format(
|
||||
theme.filename,
|
||||
filename,
|
||||
theme.reset,
|
||||
theme.line_no,
|
||||
frame_summary.lineno,
|
||||
theme.reset,
|
||||
theme.frame,
|
||||
frame_summary.name,
|
||||
theme.reset,
|
||||
)
|
||||
)
|
||||
if frame_summary._dedented_lines and frame_summary._dedented_lines.strip():
|
||||
if (
|
||||
frame_summary.colno is None or
|
||||
@@ -664,11 +671,11 @@ class StackSummary(list):
|
||||
for color, group in itertools.groupby(itertools.zip_longest(line, carets, fillvalue=""), key=lambda x: x[1]):
|
||||
caret_group = list(group)
|
||||
if color == "^":
|
||||
colorized_line_parts.append(ANSIColors.BOLD_RED + "".join(char for char, _ in caret_group) + ANSIColors.RESET)
|
||||
colorized_carets_parts.append(ANSIColors.BOLD_RED + "".join(caret for _, caret in caret_group) + ANSIColors.RESET)
|
||||
colorized_line_parts.append(theme.error_highlight + "".join(char for char, _ in caret_group) + theme.reset)
|
||||
colorized_carets_parts.append(theme.error_highlight + "".join(caret for _, caret in caret_group) + theme.reset)
|
||||
elif color == "~":
|
||||
colorized_line_parts.append(ANSIColors.RED + "".join(char for char, _ in caret_group) + ANSIColors.RESET)
|
||||
colorized_carets_parts.append(ANSIColors.RED + "".join(caret for _, caret in caret_group) + ANSIColors.RESET)
|
||||
colorized_line_parts.append(theme.error_range + "".join(char for char, _ in caret_group) + theme.reset)
|
||||
colorized_carets_parts.append(theme.error_range + "".join(caret for _, caret in caret_group) + theme.reset)
|
||||
else:
|
||||
colorized_line_parts.append("".join(char for char, _ in caret_group))
|
||||
colorized_carets_parts.append("".join(caret for _, caret in caret_group))
|
||||
@@ -1086,6 +1093,7 @@ class TracebackException:
|
||||
self.end_offset = exc_value.end_offset
|
||||
self.msg = exc_value.msg
|
||||
self._is_syntax_error = True
|
||||
self._exc_metadata = getattr(exc_value, "_metadata", None)
|
||||
elif exc_type and issubclass(exc_type, ImportError) and \
|
||||
getattr(exc_value, "name_from", None) is not None:
|
||||
wrong_name = getattr(exc_value, "name_from", None)
|
||||
@@ -1269,24 +1277,120 @@ class TracebackException:
|
||||
for ex in self.exceptions:
|
||||
yield from ex.format_exception_only(show_group=show_group, _depth=_depth+1, colorize=colorize)
|
||||
|
||||
def _find_keyword_typos(self):
|
||||
assert self._is_syntax_error
|
||||
try:
|
||||
import _suggestions
|
||||
except ImportError:
|
||||
_suggestions = None
|
||||
|
||||
# Only try to find keyword typos if there is no custom message
|
||||
if self.msg != "invalid syntax" and "Perhaps you forgot a comma" not in self.msg:
|
||||
return
|
||||
|
||||
if not self._exc_metadata:
|
||||
return
|
||||
|
||||
line, offset, source = self._exc_metadata
|
||||
end_line = int(self.lineno) if self.lineno is not None else 0
|
||||
lines = None
|
||||
from_filename = False
|
||||
|
||||
if source is None:
|
||||
if self.filename:
|
||||
try:
|
||||
with open(self.filename) as f:
|
||||
lines = f.read().splitlines()
|
||||
except Exception:
|
||||
line, end_line, offset = 0,1,0
|
||||
else:
|
||||
from_filename = True
|
||||
lines = lines if lines is not None else self.text.splitlines()
|
||||
else:
|
||||
lines = source.splitlines()
|
||||
|
||||
error_code = lines[line -1 if line > 0 else 0:end_line]
|
||||
error_code = textwrap.dedent('\n'.join(error_code))
|
||||
|
||||
# Do not continue if the source is too large
|
||||
if len(error_code) > 1024:
|
||||
return
|
||||
|
||||
error_lines = error_code.splitlines()
|
||||
tokens = tokenize.generate_tokens(io.StringIO(error_code).readline)
|
||||
tokens_left_to_process = 10
|
||||
import difflib
|
||||
for token in tokens:
|
||||
start, end = token.start, token.end
|
||||
if token.type != tokenize.NAME:
|
||||
continue
|
||||
# Only consider NAME tokens on the same line as the error
|
||||
the_end = end_line if line == 0 else end_line + 1
|
||||
if from_filename and token.start[0]+line != the_end:
|
||||
continue
|
||||
wrong_name = token.string
|
||||
if wrong_name in keyword.kwlist:
|
||||
continue
|
||||
|
||||
# Limit the number of valid tokens to consider to not spend
|
||||
# to much time in this function
|
||||
tokens_left_to_process -= 1
|
||||
if tokens_left_to_process < 0:
|
||||
break
|
||||
# Limit the number of possible matches to try
|
||||
max_matches = 3
|
||||
matches = []
|
||||
if _suggestions is not None:
|
||||
suggestion = _suggestions._generate_suggestions(keyword.kwlist, wrong_name)
|
||||
if suggestion:
|
||||
matches.append(suggestion)
|
||||
matches.extend(difflib.get_close_matches(wrong_name, keyword.kwlist, n=max_matches, cutoff=0.5))
|
||||
matches = matches[:max_matches]
|
||||
for suggestion in matches:
|
||||
if not suggestion or suggestion == wrong_name:
|
||||
continue
|
||||
# Try to replace the token with the keyword
|
||||
the_lines = error_lines.copy()
|
||||
the_line = the_lines[start[0] - 1][:]
|
||||
chars = list(the_line)
|
||||
chars[token.start[1]:token.end[1]] = suggestion
|
||||
the_lines[start[0] - 1] = ''.join(chars)
|
||||
code = '\n'.join(the_lines)
|
||||
|
||||
# Check if it works
|
||||
try:
|
||||
codeop.compile_command(code, symbol="exec", flags=codeop.PyCF_ONLY_AST)
|
||||
except SyntaxError:
|
||||
continue
|
||||
|
||||
# Keep token.line but handle offsets correctly
|
||||
self.text = token.line
|
||||
self.offset = token.start[1] + 1
|
||||
self.end_offset = token.end[1] + 1
|
||||
self.lineno = start[0]
|
||||
self.end_lineno = end[0]
|
||||
self.msg = f"invalid syntax. Did you mean '{suggestion}'?"
|
||||
return
|
||||
|
||||
|
||||
def _format_syntax_error(self, stype, **kwargs):
|
||||
"""Format SyntaxError exceptions (internal helper)."""
|
||||
# Show exactly where the problem was found.
|
||||
colorize = kwargs.get("colorize", False)
|
||||
if colorize:
|
||||
theme = _colorize.get_theme(force_color=True).traceback
|
||||
else:
|
||||
theme = _colorize.get_theme(force_no_color=True).traceback
|
||||
filename_suffix = ''
|
||||
if self.lineno is not None:
|
||||
if colorize:
|
||||
yield ' File {}"{}"{}, line {}{}{}\n'.format(
|
||||
ANSIColors.MAGENTA,
|
||||
self.filename or "<string>",
|
||||
ANSIColors.RESET,
|
||||
ANSIColors.MAGENTA,
|
||||
self.lineno,
|
||||
ANSIColors.RESET,
|
||||
)
|
||||
else:
|
||||
yield ' File "{}", line {}\n'.format(
|
||||
self.filename or "<string>", self.lineno)
|
||||
yield ' File {}"{}"{}, line {}{}{}\n'.format(
|
||||
theme.filename,
|
||||
self.filename or "<string>",
|
||||
theme.reset,
|
||||
theme.line_no,
|
||||
self.lineno,
|
||||
theme.reset,
|
||||
)
|
||||
elif self.filename is not None:
|
||||
filename_suffix = ' ({})'.format(self.filename)
|
||||
|
||||
@@ -1295,6 +1399,9 @@ class TracebackException:
|
||||
# text = " foo\n"
|
||||
# rtext = " foo"
|
||||
# ltext = "foo"
|
||||
with suppress(Exception):
|
||||
self._find_keyword_typos()
|
||||
text = self.text
|
||||
rtext = text.rstrip('\n')
|
||||
ltext = rtext.lstrip(' \n\f')
|
||||
spaces = len(rtext) - len(ltext)
|
||||
@@ -1333,11 +1440,11 @@ class TracebackException:
|
||||
# colorize from colno to end_colno
|
||||
ltext = (
|
||||
ltext[:colno] +
|
||||
ANSIColors.BOLD_RED + ltext[colno:end_colno] + ANSIColors.RESET +
|
||||
theme.error_highlight + ltext[colno:end_colno] + theme.reset +
|
||||
ltext[end_colno:]
|
||||
)
|
||||
start_color = ANSIColors.BOLD_RED
|
||||
end_color = ANSIColors.RESET
|
||||
start_color = theme.error_highlight
|
||||
end_color = theme.reset
|
||||
yield ' {}\n'.format(ltext)
|
||||
yield ' {}{}{}{}\n'.format(
|
||||
"".join(caretspace),
|
||||
@@ -1348,17 +1455,15 @@ class TracebackException:
|
||||
else:
|
||||
yield ' {}\n'.format(ltext)
|
||||
msg = self.msg or "<no detail available>"
|
||||
if colorize:
|
||||
yield "{}{}{}: {}{}{}{}\n".format(
|
||||
ANSIColors.BOLD_MAGENTA,
|
||||
stype,
|
||||
ANSIColors.RESET,
|
||||
ANSIColors.MAGENTA,
|
||||
msg,
|
||||
ANSIColors.RESET,
|
||||
filename_suffix)
|
||||
else:
|
||||
yield "{}: {}{}\n".format(stype, msg, filename_suffix)
|
||||
yield "{}{}{}: {}{}{}{}\n".format(
|
||||
theme.type,
|
||||
stype,
|
||||
theme.reset,
|
||||
theme.message,
|
||||
msg,
|
||||
theme.reset,
|
||||
filename_suffix,
|
||||
)
|
||||
|
||||
def format(self, *, chain=True, _ctx=None, **kwargs):
|
||||
"""Format the exception.
|
||||
|
||||
Reference in new issue
Block a user