diff --git a/Lib/_pyrepl/console.py b/Lib/_pyrepl/console.py index 0d78890b4f45d5..df2b4222640829 100644 --- a/Lib/_pyrepl/console.py +++ b/Lib/_pyrepl/console.py @@ -24,6 +24,7 @@ from abc import ABC, abstractmethod import ast import code +import linecache from dataclasses import dataclass, field import os.path import sys @@ -193,6 +194,7 @@ def runsource(self, source, filename="", symbol="single"): item = wrapper([stmt]) try: code = self.compile.compiler(item, filename, the_symbol) + linecache._register_code(code, source, filename) except SyntaxError as e: if e.args[0] == "'await' outside function": python = os.path.basename(sys.executable) diff --git a/Lib/_pyrepl/simple_interact.py b/Lib/_pyrepl/simple_interact.py index 66e66eae7ead9c..c6f40abfab4226 100644 --- a/Lib/_pyrepl/simple_interact.py +++ b/Lib/_pyrepl/simple_interact.py @@ -26,7 +26,6 @@ from __future__ import annotations import _sitebuiltins -import linecache import functools import os import sys @@ -148,7 +147,6 @@ def maybe_run_command(statement: str) -> bool: continue input_name = f"" - linecache._register_code(input_name, statement, "") # type: ignore[attr-defined] more = console.push(_strip_final_indent(statement), filename=input_name, _symbol="single") # type: ignore[call-arg] assert not more input_n += 1 diff --git a/Lib/inspect.py b/Lib/inspect.py index 161928dfa9fb09..81be29a33b577d 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -1072,6 +1072,8 @@ def findsource(object): module = getmodule(object, file) if module: lines = linecache.getlines(file, module.__dict__) + if not lines and file.startswith('<') and hasattr(object, "__code__"): + lines = linecache._getlines_from_code(object.__code__) else: lines = linecache.getlines(file) if not lines: diff --git a/Lib/linecache.py b/Lib/linecache.py index 8ba2df73d5a8fb..5d738ce520234f 100644 --- a/Lib/linecache.py +++ b/Lib/linecache.py @@ -11,6 +11,7 @@ # The cache. Maps filenames to either a thunk which will provide source code, # or a tuple (size, mtime, lines, fullname) once loaded. cache = {} +_interactive_cache = {} def clearcache(): @@ -44,6 +45,22 @@ def getlines(filename, module_globals=None): return [] +def _getline_from_code(filename, lineno): + lines = _getlines_from_code(filename) + if 1 <= lineno <= len(lines): + return lines[lineno - 1] + return '' + + +def _getlines_from_code(code): + code_id = id(code) + if code_id in _interactive_cache: + entry = _interactive_cache[code_id] + if len(entry) != 1: + return _interactive_cache[code_id][2] + return [] + + def checkcache(filename=None): """Discard cache entries that are out of date. (This is not checked upon each call!)""" @@ -88,9 +105,13 @@ def updatecache(filename, module_globals=None): # These imports are not at top level because linecache is in the critical # path of the interpreter startup and importing os and sys take a lot of time # and slows down the startup sequence. - import os - import sys - import tokenize + try: + import os + import sys + import tokenize + except ImportError: + # These import can fail if the interpreter is shutting down + return [] if filename in cache: if len(cache[filename]) != 1: @@ -196,8 +217,14 @@ def get_lines(name=name, *args, **kwargs): def _register_code(code, string, name): - cache[code] = ( - len(string), - None, - [line + '\n' for line in string.splitlines()], - name) + entry = (len(string), + None, + [line + '\n' for line in string.splitlines()], + name) + stack = [code] + while stack: + code = stack.pop() + for const in code.co_consts: + if isinstance(const, type(code)): + stack.append(const) + _interactive_cache[id(code)] = entry diff --git a/Lib/test/test_capi/test_exceptions.py b/Lib/test/test_capi/test_exceptions.py index c475b6d78d0c56..12032954220483 100644 --- a/Lib/test/test_capi/test_exceptions.py +++ b/Lib/test/test_capi/test_exceptions.py @@ -85,9 +85,7 @@ def foo(): warnings = proc.err.splitlines() self.assertEqual(warnings, [ b':6: RuntimeWarning: Testing PyErr_WarnEx', - b' foo() # line 6', b':9: RuntimeWarning: Testing PyErr_WarnEx', - b' foo() # line 9', ]) def test_warn_during_finalization(self): diff --git a/Lib/test/test_gdb/gdb_sample.py b/Lib/test/test_gdb/gdb_sample.py index a7f23db73ea6e6..375d8987fa80c3 100644 --- a/Lib/test/test_gdb/gdb_sample.py +++ b/Lib/test/test_gdb/gdb_sample.py @@ -1,4 +1,5 @@ # Sample script for use by test_gdb +from _typing import _idfunc def foo(a, b, c): bar(a=a, b=b, c=c) @@ -7,6 +8,6 @@ def bar(a, b, c): baz(a, b, c) def baz(*args): - id(42) + _idfunc(42) foo(1, 2, 3) diff --git a/Lib/test/test_gdb/test_backtrace.py b/Lib/test/test_gdb/test_backtrace.py index 714853c7b4732d..656f0c125313aa 100644 --- a/Lib/test/test_gdb/test_backtrace.py +++ b/Lib/test/test_gdb/test_backtrace.py @@ -20,14 +20,14 @@ def test_bt(self): self.assertMultilineMatches(bt, r'''^.* Traceback \(most recent call first\): - - File ".*gdb_sample.py", line 10, in baz - id\(42\) - File ".*gdb_sample.py", line 7, in bar + + File ".*gdb_sample.py", line 11, in baz + _idfunc\(42\) + File ".*gdb_sample.py", line 8, in bar baz\(a, b, c\) - File ".*gdb_sample.py", line 4, in foo + File ".*gdb_sample.py", line 5, in foo bar\(a=a, b=b, c=c\) - File ".*gdb_sample.py", line 12, in + File ".*gdb_sample.py", line 13, in foo\(1, 2, 3\) ''') @@ -39,11 +39,11 @@ def test_bt_full(self): cmds_after_breakpoint=['py-bt-full']) self.assertMultilineMatches(bt, r'''^.* -#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\) +#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 8, in bar \(a=1, b=2, c=3\) baz\(a, b, c\) -#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 4, in foo \(a=1, b=2, c=3\) +#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 5, in foo \(a=1, b=2, c=3\) bar\(a=a, b=b, c=c\) -#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 12, in \(\) +#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 13, in \(\) foo\(1, 2, 3\) ''') @@ -55,6 +55,7 @@ def test_threads(self): 'Verify that "py-bt" indicates threads that are waiting for the GIL' cmd = ''' from threading import Thread +from _typing import _idfunc class TestThread(Thread): # These threads would run forever, but we'll interrupt things with the @@ -70,7 +71,7 @@ def run(self): t[i].start() # Trigger a breakpoint on the main thread -id(42) +_idfunc(42) ''' # Verify with "py-bt": @@ -90,8 +91,8 @@ def run(self): # unless we add LD_PRELOAD=PATH-TO-libpthread.so.1 as a workaround def test_gc(self): 'Verify that "py-bt" indicates if a thread is garbage-collecting' - cmd = ('from gc import collect\n' - 'id(42)\n' + cmd = ('from gc import collect; from _typing import _idfunc\n' + '_idfunc(42)\n' 'def foo():\n' ' collect()\n' 'def bar():\n' @@ -113,11 +114,12 @@ def test_gc(self): "Python was compiled with optimizations") def test_wrapper_call(self): cmd = textwrap.dedent(''' + from typing import _idfunc class MyList(list): def __init__(self): super(*[]).__init__() # wrapper_call() - id("first break point") + _idfunc("first break point") l = MyList() ''') cmds_after_breakpoint = ['break wrapper_call', 'continue'] diff --git a/Lib/test/test_gdb/test_misc.py b/Lib/test/test_gdb/test_misc.py index 1047f4867c1d03..b3dd24777cf1fb 100644 --- a/Lib/test/test_gdb/test_misc.py +++ b/Lib/test/test_gdb/test_misc.py @@ -35,14 +35,14 @@ def test_basic_command(self): bt = self.get_stack_trace(script=SAMPLE_SCRIPT, cmds_after_breakpoint=['py-list']) - self.assertListing(' 5 \n' - ' 6 def bar(a, b, c):\n' - ' 7 baz(a, b, c)\n' - ' 8 \n' - ' 9 def baz(*args):\n' - ' >10 id(42)\n' - ' 11 \n' - ' 12 foo(1, 2, 3)\n', + self.assertListing(' 6 \n' + ' 7 def bar(a, b, c):\n' + ' 8 baz(a, b, c)\n' + ' 9 \n' + ' 10 def baz(*args):\n' + ' >11 _idfunc(42)\n' + ' 12 \n' + ' 13 foo(1, 2, 3)\n', bt) def test_one_abs_arg(self): @@ -50,25 +50,27 @@ def test_one_abs_arg(self): bt = self.get_stack_trace(script=SAMPLE_SCRIPT, cmds_after_breakpoint=['py-list 9']) - self.assertListing(' 9 def baz(*args):\n' - ' >10 id(42)\n' - ' 11 \n' - ' 12 foo(1, 2, 3)\n', + self.assertListing(' 10 def baz(*args):\n' + ' >11 _idfunc(42)\n' + ' 12 \n' + ' 13 foo(1, 2, 3)\n', bt) def test_two_abs_args(self): 'Verify the "py-list" command with two absolute arguments' bt = self.get_stack_trace(script=SAMPLE_SCRIPT, - cmds_after_breakpoint=['py-list 1,3']) + cmds_after_breakpoint=['py-list 1,4']) self.assertListing(' 1 # Sample script for use by test_gdb\n' - ' 2 \n' - ' 3 def foo(a, b, c):\n', + ' 2 from _typing import _idfunc\n' + ' 3 \n' + ' 4 def foo(a, b, c):\n', bt) SAMPLE_WITH_C_CALL = """ from _testcapi import pyobject_vectorcall +from _typing import _idfunc def foo(a, b, c): bar(a, b, c) @@ -77,7 +79,7 @@ def bar(a, b, c): pyobject_vectorcall(baz, (a, b, c), None) def baz(*args): - id(42) + _idfunc(42) foo(1, 2, 3) @@ -94,7 +96,7 @@ def test_pyup_command(self): cmds_after_breakpoint=['py-up', 'py-up']) self.assertMultilineMatches(bt, r'''^.* -#[0-9]+ Frame 0x-?[0-9a-f]+, for file , line 12, in baz \(args=\(1, 2, 3\)\) +#[0-9]+ Frame 0x-?[0-9a-f]+, for file , line 13, in baz \(args=\(1, 2, 3\)\) #[0-9]+ $''') @@ -123,9 +125,9 @@ def test_up_then_down(self): cmds_after_breakpoint=['py-up', 'py-up', 'py-down']) self.assertMultilineMatches(bt, r'''^.* -#[0-9]+ Frame 0x-?[0-9a-f]+, for file , line 12, in baz \(args=\(1, 2, 3\)\) +#[0-9]+ Frame 0x-?[0-9a-f]+, for file , line 13, in baz \(args=\(1, 2, 3\)\) #[0-9]+ -#[0-9]+ Frame 0x-?[0-9a-f]+, for file , line 12, in baz \(args=\(1, 2, 3\)\) +#[0-9]+ Frame 0x-?[0-9a-f]+, for file , line 13, in baz \(args=\(1, 2, 3\)\) $''') class PyPrintTests(DebuggerTests): diff --git a/Lib/test/test_gdb/test_pretty_print.py b/Lib/test/test_gdb/test_pretty_print.py index dfc77d65ab16a4..0ea714cea27674 100644 --- a/Lib/test/test_gdb/test_pretty_print.py +++ b/Lib/test/test_gdb/test_pretty_print.py @@ -17,7 +17,7 @@ def get_gdb_repr(self, source, import_site=False): # Given an input python source representation of data, # run "python -c'id(DATA)'" under gdb with a breakpoint on - # builtin_id and scrape out gdb's representation of the "op" + # _typing__idfunc and scrape out gdb's representation of the "op" # parameter, and verify that the gdb displays the same string # # Verify that the gdb displays the expected string @@ -29,6 +29,7 @@ def get_gdb_repr(self, source, # undecodable characters may lurk there in optimized mode # (issue #19743). cmds_after_breakpoint = cmds_after_breakpoint or ["backtrace 1"] + source = "from _typing import _idfunc\n" + source gdb_output = self.get_stack_trace(source, breakpoint=BREAKPOINT_FN, cmds_after_breakpoint=cmds_after_breakpoint, import_site=import_site) @@ -36,10 +37,10 @@ def get_gdb_repr(self, source, # in its output, depending on the width of the terminal it's connected # to (using its "wrap_here" function) m = re.search( - # Match '#0 builtin_id(self=..., v=...)' - r'#0\s+builtin_id\s+\(self\=.*,\s+v=\s*(.*?)?\)' + # Match '#0 _typing_idfunc(module=..., x=...)' + r'#0\s+_typing__idfunc\s+\(module\=.*,\s+x=\s*(.*?)?\)' # Match ' at Python/bltinmodule.c'. - # bpo-38239: builtin_id() is defined in Python/bltinmodule.c, + # bpo-38239: typing_idfunc() is defined in Module/_typingmldule.c, # but accept any "Directory\file.c" to support Link Time # Optimization (LTO). r'\s+at\s+\S*[A-Za-z]+/[A-Za-z0-9_-]+\.c', @@ -49,13 +50,13 @@ def get_gdb_repr(self, source, return m.group(1), gdb_output def test_getting_backtrace(self): - gdb_output = self.get_stack_trace('id(42)') + gdb_output = self.get_stack_trace('from _typing import _idfunc;_idfunc(42)') self.assertTrue(BREAKPOINT_FN in gdb_output) def assertGdbRepr(self, val, exp_repr=None): # Ensure that gdb's rendering of the value in a debugged process # matches repr(value) in this process: - gdb_repr, gdb_output = self.get_gdb_repr('id(' + ascii(val) + ')') + gdb_repr, gdb_output = self.get_gdb_repr('_idfunc(' + ascii(val) + ')') if not exp_repr: exp_repr = repr(val) self.assertEqual(gdb_repr, exp_repr, @@ -173,7 +174,7 @@ def test_sets(self): # which happens on deletion: gdb_repr, gdb_output = self.get_gdb_repr('''s = set(['a','b']) s.remove('a') -id(s)''') +_idfunc(s)''') self.assertEqual(gdb_repr, "{'b'}") @support.requires_resource('cpu') @@ -194,7 +195,7 @@ def test_exceptions(self): try: raise RuntimeError("I am an error") except RuntimeError as e: - id(e) + _idfunc(e) ''') self.assertEqual(gdb_repr, "RuntimeError('I am an error',)") @@ -205,7 +206,7 @@ def test_exceptions(self): try: a = 1 / 0 except ZeroDivisionError as e: - id(e) + _idfunc(e) ''') self.assertEqual(gdb_repr, "ZeroDivisionError('division by zero',)") @@ -217,7 +218,7 @@ class Foo: pass foo = Foo() foo.an_int = 42 -id(foo)''') +_idfunc(foo)''') m = re.match(r'', gdb_repr) self.assertTrue(m, msg='Unexpected new-style class rendering %r' % gdb_repr) @@ -230,7 +231,7 @@ class Foo(list): foo = Foo() foo += [1, 2, 3] foo.an_int = 42 -id(foo)''') +_idfunc(foo)''') m = re.match(r'', gdb_repr) self.assertTrue(m, @@ -245,7 +246,7 @@ class Foo(tuple): pass foo = Foo((1, 2, 3)) foo.an_int = 42 -id(foo)''') +_idfunc(foo)''') m = re.match(r'', gdb_repr) self.assertTrue(m, @@ -283,8 +284,8 @@ def assertSane(self, source, corruption, exprepr=None): def test_NULL_ptr(self): 'Ensure that a NULL PyObject* is handled gracefully' gdb_repr, gdb_output = ( - self.get_gdb_repr('id(42)', - cmds_after_breakpoint=['set variable v=0', + self.get_gdb_repr('_idfunc(42)', + cmds_after_breakpoint=['set variable x=0', 'backtrace']) ) @@ -292,25 +293,25 @@ def test_NULL_ptr(self): def test_NULL_ob_type(self): 'Ensure that a PyObject* with NULL ob_type is handled gracefully' - self.assertSane('id(42)', - 'set v->ob_type=0') + self.assertSane('_idfunc(42)', + 'set x->ob_type=0') def test_corrupt_ob_type(self): 'Ensure that a PyObject* with a corrupt ob_type is handled gracefully' - self.assertSane('id(42)', - 'set v->ob_type=0xDEADBEEF', + self.assertSane('_idfunc(42)', + 'set x->ob_type=0xDEADBEEF', exprepr='42') def test_corrupt_tp_flags(self): 'Ensure that a PyObject* with a type with corrupt tp_flags is handled' - self.assertSane('id(42)', - 'set v->ob_type->tp_flags=0x0', + self.assertSane('_idfunc(42)', + 'set x->ob_type->tp_flags=0x0', exprepr='42') def test_corrupt_tp_name(self): 'Ensure that a PyObject* with a type with corrupt tp_name is handled' - self.assertSane('id(42)', - 'set v->ob_type->tp_name=0xDEADBEEF', + self.assertSane('_idfunc(42)', + 'set x->ob_type->tp_name=0xDEADBEEF', exprepr='42') def test_builtins_help(self): @@ -321,7 +322,7 @@ def test_builtins_help(self): # (this was the issue causing tracebacks in # http://bugs.python.org/issue8032#msg100537 ) - gdb_repr, gdb_output = self.get_gdb_repr('id(__builtins__.help)', import_site=True) + gdb_repr, gdb_output = self.get_gdb_repr('_idfunc(__builtins__.help)', import_site=True) m = re.match(r'<_Helper\(\) at remote 0x-?[0-9a-f]+>', gdb_repr) self.assertTrue(m, @@ -331,18 +332,18 @@ def test_selfreferential_list(self): '''Ensure that a reference loop involving a list doesn't lead proxyval into an infinite loop:''' gdb_repr, gdb_output = \ - self.get_gdb_repr("a = [3, 4, 5] ; a.append(a) ; id(a)") + self.get_gdb_repr("a = [3, 4, 5] ; a.append(a) ; _idfunc(a)") self.assertEqual(gdb_repr, '[3, 4, 5, [...]]') gdb_repr, gdb_output = \ - self.get_gdb_repr("a = [3, 4, 5] ; b = [a] ; a.append(b) ; id(a)") + self.get_gdb_repr("a = [3, 4, 5] ; b = [a] ; a.append(b) ; _idfunc(a)") self.assertEqual(gdb_repr, '[3, 4, 5, [[...]]]') def test_selfreferential_dict(self): '''Ensure that a reference loop involving a dict doesn't lead proxyval into an infinite loop:''' gdb_repr, gdb_output = \ - self.get_gdb_repr("a = {} ; b = {'bar':a} ; a['foo'] = b ; id(a)") + self.get_gdb_repr("a = {} ; b = {'bar':a} ; a['foo'] = b ; _idfunc(a)") self.assertEqual(gdb_repr, "{'foo': {'bar': {...}}}") @@ -353,7 +354,7 @@ class Foo: pass foo = Foo() foo.an_attr = foo -id(foo)''') +_idfunc(foo)''') self.assertTrue(re.match(r'\) at remote 0x-?[0-9a-f]+>', gdb_repr), 'Unexpected gdb representation: %r\n%s' % \ @@ -366,7 +367,7 @@ class Foo(object): pass foo = Foo() foo.an_attr = foo -id(foo)''') +_idfunc(foo)''') self.assertTrue(re.match(r'\) at remote 0x-?[0-9a-f]+>', gdb_repr), 'Unexpected gdb representation: %r\n%s' % \ @@ -380,7 +381,7 @@ class Foo(object): b = Foo() a.an_attr = b b.an_attr = a -id(a)''') +_idfunc(a)''') self.assertTrue(re.match(r'\) at remote 0x-?[0-9a-f]+>\) at remote 0x-?[0-9a-f]+>', gdb_repr), 'Unexpected gdb representation: %r\n%s' % \ @@ -388,7 +389,7 @@ class Foo(object): def test_truncation(self): 'Verify that very long output is truncated' - gdb_repr, gdb_output = self.get_gdb_repr('id(list(range(1000)))') + gdb_repr, gdb_output = self.get_gdb_repr('_idfunc(list(range(1000)))') self.assertEqual(gdb_repr, "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, " "14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, " @@ -415,7 +416,7 @@ def test_truncation(self): 1024 + len('...(truncated)')) def test_builtin_method(self): - gdb_repr, gdb_output = self.get_gdb_repr('import sys; id(sys.stdout.readlines)') + gdb_repr, gdb_output = self.get_gdb_repr('import sys; _idfunc(sys.stdout.readlines)') self.assertTrue(re.match(r'', gdb_repr), 'Unexpected gdb representation: %r\n%s' % \ @@ -424,15 +425,16 @@ def test_builtin_method(self): def test_frames(self): gdb_output = self.get_stack_trace(''' import sys +from _typing import _idfunc def foo(a, b, c): return sys._getframe(0) f = foo(3, 4, 5) -id(f)''', - breakpoint='builtin_id', - cmds_after_breakpoint=['print (PyFrameObject*)v'] +_idfunc(f)''', + breakpoint='_typing__idfunc', + cmds_after_breakpoint=['print (PyFrameObject*)x'] ) - self.assertTrue(re.match(r'.*\s+\$1 =\s+Frame 0x-?[0-9a-f]+, for file , line 4, in foo \(a=3.*', + self.assertTrue(re.match(r'.*\s+\$1 =\s+Frame 0x-?[0-9a-f]+, for file , line 5, in foo \(a=3.*', gdb_output, re.DOTALL), 'Unexpected gdb representation: %r\n%s' % (gdb_output, gdb_output)) diff --git a/Lib/test/test_gdb/util.py b/Lib/test/test_gdb/util.py index 54c6b2de7cc99d..694774bc5ab702 100644 --- a/Lib/test/test_gdb/util.py +++ b/Lib/test/test_gdb/util.py @@ -17,7 +17,7 @@ 'python-gdb.py') SAMPLE_SCRIPT = os.path.join(os.path.dirname(__file__), 'gdb_sample.py') -BREAKPOINT_FN = 'builtin_id' +BREAKPOINT_FN = '_typing__idfunc' PYTHONHASHSEED = '123' diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index f1f8ce57668f3b..112495512e192c 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -4546,11 +4546,11 @@ def test_check_encoding_warning(self): ''') proc = assert_python_ok('-X', 'warn_default_encoding', '-c', code) warnings = proc.err.splitlines() - self.assertEqual(len(warnings), 4) + self.assertEqual(len(warnings), 2) self.assertTrue( warnings[0].startswith(b":5: EncodingWarning: ")) self.assertTrue( - warnings[2].startswith(b":8: EncodingWarning: ")) + warnings[1].startswith(b":8: EncodingWarning: ")) def test_text_encoding(self): # PEP 597, bpo-47000. io.text_encoding() returns "locale" or "utf-8" diff --git a/Lib/test/test_linecache.py b/Lib/test/test_linecache.py index 6f5955791407ea..e23e1cc942856b 100644 --- a/Lib/test/test_linecache.py +++ b/Lib/test/test_linecache.py @@ -8,6 +8,7 @@ from importlib.machinery import ModuleSpec from test import support from test.support import os_helper +from test.support.script_helper import assert_python_ok FILENAME = linecache.__file__ @@ -311,6 +312,12 @@ def test_invalid_names(self): # just to be sure that we did not mess with cache linecache.clearcache() + def test_linecache_python_string(self): + cmdline = "import linecache;assert len(linecache.cache) == 0" + retcode, stdout, stderr = assert_python_ok('-c', cmdline) + self.assertEqual(retcode, 0) + self.assertEqual(stdout, b'') + self.assertEqual(stderr, b'') class LineCacheInvalidationTests(unittest.TestCase): def setUp(self): diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 356ff5b198d637..66262d352c2ec2 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -213,7 +213,7 @@ def bar(x): p.stdin.write(user_input) user_input2 = dedent(""" import linecache - print(linecache.cache['-1']) + print(linecache._interactive_cache[id(foo.__code__)]) """) p.stdin.write(user_input2) output = kill_python(p) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 9b73be0b95e947..bf77779b32c8ef 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -1803,9 +1803,9 @@ def test_encoding_warning(self): cp = subprocess.run([sys.executable, "-Xwarn_default_encoding", "-c", code], capture_output=True) lines = cp.stderr.splitlines() - self.assertEqual(len(lines), 4, lines) + self.assertEqual(len(lines), 2, lines) self.assertTrue(lines[0].startswith(b":2: EncodingWarning: ")) - self.assertTrue(lines[2].startswith(b":3: EncodingWarning: ")) + self.assertTrue(lines[1].startswith(b":3: EncodingWarning: ")) def _get_test_grp_name(): diff --git a/Lib/traceback.py b/Lib/traceback.py index c748e4bc849c43..15f59bba54d0db 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -288,11 +288,11 @@ class FrameSummary: """ __slots__ = ('filename', 'lineno', 'end_lineno', 'colno', 'end_colno', - 'name', '_lines', '_lines_dedented', 'locals') + 'name', '_lines', '_lines_dedented', 'locals', '_code') def __init__(self, filename, lineno, name, *, lookup_line=True, locals=None, line=None, - end_lineno=None, colno=None, end_colno=None): + end_lineno=None, colno=None, end_colno=None, **kwargs): """Construct a FrameSummary. :param lookup_line: If True, `linecache` is consulted for the source @@ -308,6 +308,7 @@ def __init__(self, filename, lineno, name, *, lookup_line=True, self.colno = colno self.end_colno = end_colno self.name = name + self._code = kwargs.get("_code") self._lines = line self._lines_dedented = None if lookup_line: @@ -347,7 +348,10 @@ def _set_lines(self): lines = [] for lineno in range(self.lineno, self.end_lineno + 1): # treat errors (empty string) and empty lines (newline) as the same - lines.append(linecache.getline(self.filename, lineno).rstrip()) + line = linecache.getline(self.filename, lineno).rstrip() + if not line and self._code is not None and self.filename.startswith("<"): + line = linecache._getline_from_code(self._code, lineno).rstrip() + lines.append(line) self._lines = "\n".join(lines) + "\n" @property @@ -480,9 +484,13 @@ def _extract_from_extended_frame_gen(klass, frame_gen, *, limit=None, f_locals = f.f_locals else: f_locals = None - result.append(FrameSummary( - filename, lineno, name, lookup_line=False, locals=f_locals, - end_lineno=end_lineno, colno=colno, end_colno=end_colno)) + result.append( + FrameSummary(filename, lineno, name, + lookup_line=False, locals=f_locals, + end_lineno=end_lineno, colno=colno, end_colno=end_colno, + _code=f.f_code, + ) + ) for filename in fnames: linecache.checkcache(filename) diff --git a/Python/pythonrun.c b/Python/pythonrun.c index eea638354fe8f3..62b70931778b1d 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -1442,7 +1442,7 @@ run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals, PyObject* result = PyObject_CallFunction( print_tb_func, "OOO", - interactive_filename, + co, interactive_src, filename );