gh-117174: Fix reference leak and gdb tests (#131095)

This commit is contained in:
Pablo Galindo Salgado 2025-03-11 23:17:58 +00:00 committed by GitHub
parent c00ac57824
commit ebc24d54bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 88 additions and 83 deletions

View file

@ -51,9 +51,11 @@ def _getline_from_code(filename, lineno):
return lines[lineno - 1] return lines[lineno - 1]
return '' return ''
def _make_key(code):
return (code.co_filename, code.co_qualname, code.co_firstlineno)
def _getlines_from_code(code): def _getlines_from_code(code):
code_id = id(code) code_id = _make_key(code)
if code_id in _interactive_cache: if code_id in _interactive_cache:
entry = _interactive_cache[code_id] entry = _interactive_cache[code_id]
if len(entry) != 1: if len(entry) != 1:
@ -215,7 +217,6 @@ def lazycache(filename, module_globals):
return True return True
return False return False
def _register_code(code, string, name): def _register_code(code, string, name):
entry = (len(string), entry = (len(string),
None, None,
@ -227,4 +228,4 @@ def _register_code(code, string, name):
for const in code.co_consts: for const in code.co_consts:
if isinstance(const, type(code)): if isinstance(const, type(code)):
stack.append(const) stack.append(const)
_interactive_cache[id(code)] = entry _interactive_cache[_make_key(code)] = entry

View file

@ -3,6 +3,7 @@ import sys
import warnings import warnings
from inspect import isabstract from inspect import isabstract
from typing import Any from typing import Any
import linecache
from test import support from test import support
from test.support import os_helper from test.support import os_helper
@ -73,6 +74,11 @@ def runtest_refleak(test_name, test_func,
ps = copyreg.dispatch_table.copy() ps = copyreg.dispatch_table.copy()
pic = sys.path_importer_cache.copy() pic = sys.path_importer_cache.copy()
zdc: dict[str, Any] | None zdc: dict[str, Any] | None
# Linecache holds a cache with the source of interactive code snippets
# (e.g. code typed in the REPL). This cache is not cleared by
# linecache.clearcache(). We need to save and restore it to avoid false
# positives.
linecache_data = linecache.cache.copy(), linecache._interactive_cache.copy() # type: ignore[attr-defined]
try: try:
import zipimport import zipimport
except ImportError: except ImportError:
@ -122,7 +128,7 @@ def runtest_refleak(test_name, test_func,
xml_filename = 'refleak-xml.tmp' xml_filename = 'refleak-xml.tmp'
result = None result = None
dash_R_cleanup(fs, ps, pic, zdc, abcs) dash_R_cleanup(fs, ps, pic, zdc, abcs, linecache_data)
for i in rep_range: for i in rep_range:
support.gc_collect() support.gc_collect()
@ -134,7 +140,7 @@ def runtest_refleak(test_name, test_func,
refleak_helper._hunting_for_refleaks = current refleak_helper._hunting_for_refleaks = current
save_support_xml(xml_filename) save_support_xml(xml_filename)
dash_R_cleanup(fs, ps, pic, zdc, abcs) dash_R_cleanup(fs, ps, pic, zdc, abcs, linecache_data)
support.gc_collect() support.gc_collect()
# Read memory statistics immediately after the garbage collection. # Read memory statistics immediately after the garbage collection.
@ -223,7 +229,7 @@ def runtest_refleak(test_name, test_func,
return (failed, result) return (failed, result)
def dash_R_cleanup(fs, ps, pic, zdc, abcs): def dash_R_cleanup(fs, ps, pic, zdc, abcs, linecache_data):
import copyreg import copyreg
import collections.abc import collections.abc
@ -233,6 +239,11 @@ def dash_R_cleanup(fs, ps, pic, zdc, abcs):
copyreg.dispatch_table.update(ps) copyreg.dispatch_table.update(ps)
sys.path_importer_cache.clear() sys.path_importer_cache.clear()
sys.path_importer_cache.update(pic) sys.path_importer_cache.update(pic)
lcache, linteractive = linecache_data
linecache._interactive_cache.clear()
linecache._interactive_cache.update(linteractive)
linecache.cache.clear()
linecache.cache.update(lcache)
try: try:
import zipimport import zipimport
except ImportError: except ImportError:

View file

@ -1,5 +1,4 @@
# Sample script for use by test_gdb # Sample script for use by test_gdb
from _typing import _idfunc
def foo(a, b, c): def foo(a, b, c):
bar(a=a, b=b, c=c) bar(a=a, b=b, c=c)
@ -8,6 +7,6 @@ def bar(a, b, c):
baz(a, b, c) baz(a, b, c)
def baz(*args): def baz(*args):
_idfunc(42) id(42)
foo(1, 2, 3) foo(1, 2, 3)

View file

@ -20,14 +20,14 @@ class PyBtTests(DebuggerTests):
self.assertMultilineMatches(bt, self.assertMultilineMatches(bt,
r'''^.* r'''^.*
Traceback \(most recent call first\): Traceback \(most recent call first\):
<built-in method _idfunc of module object .*> <built-in method id of module object .*>
File ".*gdb_sample.py", line 11, in baz File ".*gdb_sample.py", line 10, in baz
_idfunc\(42\) id\(42\)
File ".*gdb_sample.py", line 8, in bar File ".*gdb_sample.py", line 7, in bar
baz\(a, b, c\) baz\(a, b, c\)
File ".*gdb_sample.py", line 5, in foo File ".*gdb_sample.py", line 4, in foo
bar\(a=a, b=b, c=c\) bar\(a=a, b=b, c=c\)
File ".*gdb_sample.py", line 13, in <module> File ".*gdb_sample.py", line 12, in <module>
foo\(1, 2, 3\) foo\(1, 2, 3\)
''') ''')
@ -39,11 +39,11 @@ Traceback \(most recent call first\):
cmds_after_breakpoint=['py-bt-full']) cmds_after_breakpoint=['py-bt-full'])
self.assertMultilineMatches(bt, self.assertMultilineMatches(bt,
r'''^.* r'''^.*
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 8, in bar \(a=1, b=2, c=3\) #[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
baz\(a, b, c\) baz\(a, b, c\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 5, in foo \(a=1, b=2, c=3\) #[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 4, in foo \(a=1, b=2, c=3\)
bar\(a=a, b=b, c=c\) bar\(a=a, b=b, c=c\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 13, in <module> \(\) #[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 12, in <module> \(\)
foo\(1, 2, 3\) foo\(1, 2, 3\)
''') ''')
@ -55,7 +55,6 @@ Traceback \(most recent call first\):
'Verify that "py-bt" indicates threads that are waiting for the GIL' 'Verify that "py-bt" indicates threads that are waiting for the GIL'
cmd = ''' cmd = '''
from threading import Thread from threading import Thread
from _typing import _idfunc
class TestThread(Thread): class TestThread(Thread):
# These threads would run forever, but we'll interrupt things with the # These threads would run forever, but we'll interrupt things with the
@ -71,7 +70,7 @@ for i in range(4):
t[i].start() t[i].start()
# Trigger a breakpoint on the main thread # Trigger a breakpoint on the main thread
_idfunc(42) id(42)
''' '''
# Verify with "py-bt": # Verify with "py-bt":
@ -91,8 +90,8 @@ _idfunc(42)
# unless we add LD_PRELOAD=PATH-TO-libpthread.so.1 as a workaround # unless we add LD_PRELOAD=PATH-TO-libpthread.so.1 as a workaround
def test_gc(self): def test_gc(self):
'Verify that "py-bt" indicates if a thread is garbage-collecting' 'Verify that "py-bt" indicates if a thread is garbage-collecting'
cmd = ('from gc import collect; from _typing import _idfunc\n' cmd = ('from gc import collect\n'
'_idfunc(42)\n' 'id(42)\n'
'def foo():\n' 'def foo():\n'
' collect()\n' ' collect()\n'
'def bar():\n' 'def bar():\n'
@ -114,12 +113,11 @@ _idfunc(42)
"Python was compiled with optimizations") "Python was compiled with optimizations")
def test_wrapper_call(self): def test_wrapper_call(self):
cmd = textwrap.dedent(''' cmd = textwrap.dedent('''
from typing import _idfunc
class MyList(list): class MyList(list):
def __init__(self): def __init__(self):
super(*[]).__init__() # wrapper_call() super(*[]).__init__() # wrapper_call()
_idfunc("first break point") id("first break point")
l = MyList() l = MyList()
''') ''')
cmds_after_breakpoint = ['break wrapper_call', 'continue'] cmds_after_breakpoint = ['break wrapper_call', 'continue']

View file

@ -35,14 +35,14 @@ class PyListTests(DebuggerTests):
bt = self.get_stack_trace(script=SAMPLE_SCRIPT, bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
cmds_after_breakpoint=['py-list']) cmds_after_breakpoint=['py-list'])
self.assertListing(' 6 \n' self.assertListing(' 5 \n'
' 7 def bar(a, b, c):\n' ' 6 def bar(a, b, c):\n'
' 8 baz(a, b, c)\n' ' 7 baz(a, b, c)\n'
' 9 \n' ' 8 \n'
' 10 def baz(*args):\n' ' 9 def baz(*args):\n'
' >11 _idfunc(42)\n' ' >10 id(42)\n'
' 12 \n' ' 11 \n'
' 13 foo(1, 2, 3)\n', ' 12 foo(1, 2, 3)\n',
bt) bt)
def test_one_abs_arg(self): def test_one_abs_arg(self):
@ -50,27 +50,25 @@ class PyListTests(DebuggerTests):
bt = self.get_stack_trace(script=SAMPLE_SCRIPT, bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
cmds_after_breakpoint=['py-list 9']) cmds_after_breakpoint=['py-list 9'])
self.assertListing(' 10 def baz(*args):\n' self.assertListing(' 9 def baz(*args):\n'
' >11 _idfunc(42)\n' ' >10 id(42)\n'
' 12 \n' ' 11 \n'
' 13 foo(1, 2, 3)\n', ' 12 foo(1, 2, 3)\n',
bt) bt)
def test_two_abs_args(self): def test_two_abs_args(self):
'Verify the "py-list" command with two absolute arguments' 'Verify the "py-list" command with two absolute arguments'
bt = self.get_stack_trace(script=SAMPLE_SCRIPT, bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
cmds_after_breakpoint=['py-list 1,4']) cmds_after_breakpoint=['py-list 1,3'])
self.assertListing(' 1 # Sample script for use by test_gdb\n' self.assertListing(' 1 # Sample script for use by test_gdb\n'
' 2 from _typing import _idfunc\n' ' 2 \n'
' 3 \n' ' 3 def foo(a, b, c):\n',
' 4 def foo(a, b, c):\n',
bt) bt)
SAMPLE_WITH_C_CALL = """ SAMPLE_WITH_C_CALL = """
from _testcapi import pyobject_vectorcall from _testcapi import pyobject_vectorcall
from _typing import _idfunc
def foo(a, b, c): def foo(a, b, c):
bar(a, b, c) bar(a, b, c)
@ -79,7 +77,7 @@ def bar(a, b, c):
pyobject_vectorcall(baz, (a, b, c), None) pyobject_vectorcall(baz, (a, b, c), None)
def baz(*args): def baz(*args):
_idfunc(42) id(42)
foo(1, 2, 3) foo(1, 2, 3)
@ -96,7 +94,7 @@ class StackNavigationTests(DebuggerTests):
cmds_after_breakpoint=['py-up', 'py-up']) cmds_after_breakpoint=['py-up', 'py-up'])
self.assertMultilineMatches(bt, self.assertMultilineMatches(bt,
r'''^.* r'''^.*
#[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 13, in baz \(args=\(1, 2, 3\)\) #[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 12, in baz \(args=\(1, 2, 3\)\)
#[0-9]+ <built-in method pyobject_vectorcall of module object at remote 0x[0-9a-f]+> #[0-9]+ <built-in method pyobject_vectorcall of module object at remote 0x[0-9a-f]+>
$''') $''')
@ -125,9 +123,9 @@ $''')
cmds_after_breakpoint=['py-up', 'py-up', 'py-down']) cmds_after_breakpoint=['py-up', 'py-up', 'py-down'])
self.assertMultilineMatches(bt, self.assertMultilineMatches(bt,
r'''^.* r'''^.*
#[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 13, in baz \(args=\(1, 2, 3\)\) #[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 12, in baz \(args=\(1, 2, 3\)\)
#[0-9]+ <built-in method pyobject_vectorcall of module object at remote 0x[0-9a-f]+> #[0-9]+ <built-in method pyobject_vectorcall of module object at remote 0x[0-9a-f]+>
#[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 13, in baz \(args=\(1, 2, 3\)\) #[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 12, in baz \(args=\(1, 2, 3\)\)
$''') $''')
class PyPrintTests(DebuggerTests): class PyPrintTests(DebuggerTests):

View file

@ -17,7 +17,7 @@ class PrettyPrintTests(DebuggerTests):
import_site=False): import_site=False):
# Given an input python source representation of data, # Given an input python source representation of data,
# run "python -c'id(DATA)'" under gdb with a breakpoint on # run "python -c'id(DATA)'" under gdb with a breakpoint on
# _typing__idfunc and scrape out gdb's representation of the "op" # builtin_id and scrape out gdb's representation of the "op"
# parameter, and verify that the gdb displays the same string # parameter, and verify that the gdb displays the same string
# #
# Verify that the gdb displays the expected string # Verify that the gdb displays the expected string
@ -29,7 +29,6 @@ class PrettyPrintTests(DebuggerTests):
# undecodable characters may lurk there in optimized mode # undecodable characters may lurk there in optimized mode
# (issue #19743). # (issue #19743).
cmds_after_breakpoint = cmds_after_breakpoint or ["backtrace 1"] 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, gdb_output = self.get_stack_trace(source, breakpoint=BREAKPOINT_FN,
cmds_after_breakpoint=cmds_after_breakpoint, cmds_after_breakpoint=cmds_after_breakpoint,
import_site=import_site) import_site=import_site)
@ -37,10 +36,10 @@ class PrettyPrintTests(DebuggerTests):
# in its output, depending on the width of the terminal it's connected # in its output, depending on the width of the terminal it's connected
# to (using its "wrap_here" function) # to (using its "wrap_here" function)
m = re.search( m = re.search(
# Match '#0 _typing_idfunc(module=..., x=...)' # Match '#0 builtin_id(self=..., v=...)'
r'#0\s+_typing__idfunc\s+\(module\=.*,\s+x=\s*(.*?)?\)' r'#0\s+builtin_id\s+\(self\=.*,\s+v=\s*(.*?)?\)'
# Match ' at Python/bltinmodule.c'. # Match ' at Python/bltinmodule.c'.
# bpo-38239: typing_idfunc() is defined in Module/_typingmldule.c, # bpo-38239: builtin_id() is defined in Python/bltinmodule.c,
# but accept any "Directory\file.c" to support Link Time # but accept any "Directory\file.c" to support Link Time
# Optimization (LTO). # Optimization (LTO).
r'\s+at\s+\S*[A-Za-z]+/[A-Za-z0-9_-]+\.c', r'\s+at\s+\S*[A-Za-z]+/[A-Za-z0-9_-]+\.c',
@ -50,13 +49,13 @@ class PrettyPrintTests(DebuggerTests):
return m.group(1), gdb_output return m.group(1), gdb_output
def test_getting_backtrace(self): def test_getting_backtrace(self):
gdb_output = self.get_stack_trace('from _typing import _idfunc;_idfunc(42)') gdb_output = self.get_stack_trace('id(42)')
self.assertTrue(BREAKPOINT_FN in gdb_output) self.assertTrue(BREAKPOINT_FN in gdb_output)
def assertGdbRepr(self, val, exp_repr=None): def assertGdbRepr(self, val, exp_repr=None):
# Ensure that gdb's rendering of the value in a debugged process # Ensure that gdb's rendering of the value in a debugged process
# matches repr(value) in this process: # matches repr(value) in this process:
gdb_repr, gdb_output = self.get_gdb_repr('_idfunc(' + ascii(val) + ')') gdb_repr, gdb_output = self.get_gdb_repr('id(' + ascii(val) + ')')
if not exp_repr: if not exp_repr:
exp_repr = repr(val) exp_repr = repr(val)
self.assertEqual(gdb_repr, exp_repr, self.assertEqual(gdb_repr, exp_repr,
@ -174,7 +173,7 @@ class PrettyPrintTests(DebuggerTests):
# which happens on deletion: # which happens on deletion:
gdb_repr, gdb_output = self.get_gdb_repr('''s = set(['a','b']) gdb_repr, gdb_output = self.get_gdb_repr('''s = set(['a','b'])
s.remove('a') s.remove('a')
_idfunc(s)''') id(s)''')
self.assertEqual(gdb_repr, "{'b'}") self.assertEqual(gdb_repr, "{'b'}")
@support.requires_resource('cpu') @support.requires_resource('cpu')
@ -195,7 +194,7 @@ _idfunc(s)''')
try: try:
raise RuntimeError("I am an error") raise RuntimeError("I am an error")
except RuntimeError as e: except RuntimeError as e:
_idfunc(e) id(e)
''') ''')
self.assertEqual(gdb_repr, self.assertEqual(gdb_repr,
"RuntimeError('I am an error',)") "RuntimeError('I am an error',)")
@ -206,7 +205,7 @@ except RuntimeError as e:
try: try:
a = 1 / 0 a = 1 / 0
except ZeroDivisionError as e: except ZeroDivisionError as e:
_idfunc(e) id(e)
''') ''')
self.assertEqual(gdb_repr, self.assertEqual(gdb_repr,
"ZeroDivisionError('division by zero',)") "ZeroDivisionError('division by zero',)")
@ -218,7 +217,7 @@ class Foo:
pass pass
foo = Foo() foo = Foo()
foo.an_int = 42 foo.an_int = 42
_idfunc(foo)''') id(foo)''')
m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr) m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
self.assertTrue(m, self.assertTrue(m,
msg='Unexpected new-style class rendering %r' % gdb_repr) msg='Unexpected new-style class rendering %r' % gdb_repr)
@ -231,7 +230,7 @@ class Foo(list):
foo = Foo() foo = Foo()
foo += [1, 2, 3] foo += [1, 2, 3]
foo.an_int = 42 foo.an_int = 42
_idfunc(foo)''') id(foo)''')
m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr) m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
self.assertTrue(m, self.assertTrue(m,
@ -246,7 +245,7 @@ class Foo(tuple):
pass pass
foo = Foo((1, 2, 3)) foo = Foo((1, 2, 3))
foo.an_int = 42 foo.an_int = 42
_idfunc(foo)''') id(foo)''')
m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr) m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
self.assertTrue(m, self.assertTrue(m,
@ -284,8 +283,8 @@ _idfunc(foo)''')
def test_NULL_ptr(self): def test_NULL_ptr(self):
'Ensure that a NULL PyObject* is handled gracefully' 'Ensure that a NULL PyObject* is handled gracefully'
gdb_repr, gdb_output = ( gdb_repr, gdb_output = (
self.get_gdb_repr('_idfunc(42)', self.get_gdb_repr('id(42)',
cmds_after_breakpoint=['set variable x=0', cmds_after_breakpoint=['set variable v=0',
'backtrace']) 'backtrace'])
) )
@ -293,25 +292,25 @@ _idfunc(foo)''')
def test_NULL_ob_type(self): def test_NULL_ob_type(self):
'Ensure that a PyObject* with NULL ob_type is handled gracefully' 'Ensure that a PyObject* with NULL ob_type is handled gracefully'
self.assertSane('_idfunc(42)', self.assertSane('id(42)',
'set x->ob_type=0') 'set v->ob_type=0')
def test_corrupt_ob_type(self): def test_corrupt_ob_type(self):
'Ensure that a PyObject* with a corrupt ob_type is handled gracefully' 'Ensure that a PyObject* with a corrupt ob_type is handled gracefully'
self.assertSane('_idfunc(42)', self.assertSane('id(42)',
'set x->ob_type=0xDEADBEEF', 'set v->ob_type=0xDEADBEEF',
exprepr='42') exprepr='42')
def test_corrupt_tp_flags(self): def test_corrupt_tp_flags(self):
'Ensure that a PyObject* with a type with corrupt tp_flags is handled' 'Ensure that a PyObject* with a type with corrupt tp_flags is handled'
self.assertSane('_idfunc(42)', self.assertSane('id(42)',
'set x->ob_type->tp_flags=0x0', 'set v->ob_type->tp_flags=0x0',
exprepr='42') exprepr='42')
def test_corrupt_tp_name(self): def test_corrupt_tp_name(self):
'Ensure that a PyObject* with a type with corrupt tp_name is handled' 'Ensure that a PyObject* with a type with corrupt tp_name is handled'
self.assertSane('_idfunc(42)', self.assertSane('id(42)',
'set x->ob_type->tp_name=0xDEADBEEF', 'set v->ob_type->tp_name=0xDEADBEEF',
exprepr='42') exprepr='42')
def test_builtins_help(self): def test_builtins_help(self):
@ -322,7 +321,7 @@ _idfunc(foo)''')
# (this was the issue causing tracebacks in # (this was the issue causing tracebacks in
# http://bugs.python.org/issue8032#msg100537 ) # http://bugs.python.org/issue8032#msg100537 )
gdb_repr, gdb_output = self.get_gdb_repr('_idfunc(__builtins__.help)', import_site=True) gdb_repr, gdb_output = self.get_gdb_repr('id(__builtins__.help)', import_site=True)
m = re.match(r'<_Helper\(\) at remote 0x-?[0-9a-f]+>', gdb_repr) m = re.match(r'<_Helper\(\) at remote 0x-?[0-9a-f]+>', gdb_repr)
self.assertTrue(m, self.assertTrue(m,
@ -332,18 +331,18 @@ _idfunc(foo)''')
'''Ensure that a reference loop involving a list doesn't lead proxyval '''Ensure that a reference loop involving a list doesn't lead proxyval
into an infinite loop:''' into an infinite loop:'''
gdb_repr, gdb_output = \ gdb_repr, gdb_output = \
self.get_gdb_repr("a = [3, 4, 5] ; a.append(a) ; _idfunc(a)") self.get_gdb_repr("a = [3, 4, 5] ; a.append(a) ; id(a)")
self.assertEqual(gdb_repr, '[3, 4, 5, [...]]') self.assertEqual(gdb_repr, '[3, 4, 5, [...]]')
gdb_repr, gdb_output = \ gdb_repr, gdb_output = \
self.get_gdb_repr("a = [3, 4, 5] ; b = [a] ; a.append(b) ; _idfunc(a)") self.get_gdb_repr("a = [3, 4, 5] ; b = [a] ; a.append(b) ; id(a)")
self.assertEqual(gdb_repr, '[3, 4, 5, [[...]]]') self.assertEqual(gdb_repr, '[3, 4, 5, [[...]]]')
def test_selfreferential_dict(self): def test_selfreferential_dict(self):
'''Ensure that a reference loop involving a dict doesn't lead proxyval '''Ensure that a reference loop involving a dict doesn't lead proxyval
into an infinite loop:''' into an infinite loop:'''
gdb_repr, gdb_output = \ gdb_repr, gdb_output = \
self.get_gdb_repr("a = {} ; b = {'bar':a} ; a['foo'] = b ; _idfunc(a)") self.get_gdb_repr("a = {} ; b = {'bar':a} ; a['foo'] = b ; id(a)")
self.assertEqual(gdb_repr, "{'foo': {'bar': {...}}}") self.assertEqual(gdb_repr, "{'foo': {'bar': {...}}}")
@ -354,7 +353,7 @@ class Foo:
pass pass
foo = Foo() foo = Foo()
foo.an_attr = foo foo.an_attr = foo
_idfunc(foo)''') id(foo)''')
self.assertTrue(re.match(r'<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>', self.assertTrue(re.match(r'<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>',
gdb_repr), gdb_repr),
'Unexpected gdb representation: %r\n%s' % \ 'Unexpected gdb representation: %r\n%s' % \
@ -367,7 +366,7 @@ class Foo(object):
pass pass
foo = Foo() foo = Foo()
foo.an_attr = foo foo.an_attr = foo
_idfunc(foo)''') id(foo)''')
self.assertTrue(re.match(r'<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>', self.assertTrue(re.match(r'<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>',
gdb_repr), gdb_repr),
'Unexpected gdb representation: %r\n%s' % \ 'Unexpected gdb representation: %r\n%s' % \
@ -381,7 +380,7 @@ a = Foo()
b = Foo() b = Foo()
a.an_attr = b a.an_attr = b
b.an_attr = a b.an_attr = a
_idfunc(a)''') id(a)''')
self.assertTrue(re.match(r'<Foo\(an_attr=<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>\) at remote 0x-?[0-9a-f]+>', self.assertTrue(re.match(r'<Foo\(an_attr=<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>\) at remote 0x-?[0-9a-f]+>',
gdb_repr), gdb_repr),
'Unexpected gdb representation: %r\n%s' % \ 'Unexpected gdb representation: %r\n%s' % \
@ -389,7 +388,7 @@ _idfunc(a)''')
def test_truncation(self): def test_truncation(self):
'Verify that very long output is truncated' 'Verify that very long output is truncated'
gdb_repr, gdb_output = self.get_gdb_repr('_idfunc(list(range(1000)))') gdb_repr, gdb_output = self.get_gdb_repr('id(list(range(1000)))')
self.assertEqual(gdb_repr, self.assertEqual(gdb_repr,
"[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, " "[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, " "14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, "
@ -416,7 +415,7 @@ _idfunc(a)''')
1024 + len('...(truncated)')) 1024 + len('...(truncated)'))
def test_builtin_method(self): def test_builtin_method(self):
gdb_repr, gdb_output = self.get_gdb_repr('import sys; _idfunc(sys.stdout.readlines)') gdb_repr, gdb_output = self.get_gdb_repr('import sys; id(sys.stdout.readlines)')
self.assertTrue(re.match(r'<built-in method readlines of _io.TextIOWrapper object at remote 0x-?[0-9a-f]+>', self.assertTrue(re.match(r'<built-in method readlines of _io.TextIOWrapper object at remote 0x-?[0-9a-f]+>',
gdb_repr), gdb_repr),
'Unexpected gdb representation: %r\n%s' % \ 'Unexpected gdb representation: %r\n%s' % \
@ -425,16 +424,15 @@ _idfunc(a)''')
def test_frames(self): def test_frames(self):
gdb_output = self.get_stack_trace(''' gdb_output = self.get_stack_trace('''
import sys import sys
from _typing import _idfunc
def foo(a, b, c): def foo(a, b, c):
return sys._getframe(0) return sys._getframe(0)
f = foo(3, 4, 5) f = foo(3, 4, 5)
_idfunc(f)''', id(f)''',
breakpoint='_typing__idfunc', breakpoint='builtin_id',
cmds_after_breakpoint=['print (PyFrameObject*)x'] cmds_after_breakpoint=['print (PyFrameObject*)v']
) )
self.assertTrue(re.match(r'.*\s+\$1 =\s+Frame 0x-?[0-9a-f]+, for file <string>, line 5, in foo \(a=3.*', self.assertTrue(re.match(r'.*\s+\$1 =\s+Frame 0x-?[0-9a-f]+, for file <string>, line 4, in foo \(a=3.*',
gdb_output, gdb_output,
re.DOTALL), re.DOTALL),
'Unexpected gdb representation: %r\n%s' % (gdb_output, gdb_output)) 'Unexpected gdb representation: %r\n%s' % (gdb_output, gdb_output))

View file

@ -16,7 +16,7 @@ CHECKOUT_HOOK_PATH = os.path.join(os.path.dirname(sys.executable),
'python-gdb.py') 'python-gdb.py')
SAMPLE_SCRIPT = os.path.join(os.path.dirname(__file__), 'gdb_sample.py') SAMPLE_SCRIPT = os.path.join(os.path.dirname(__file__), 'gdb_sample.py')
BREAKPOINT_FN = '_typing__idfunc' BREAKPOINT_FN = 'builtin_id'
PYTHONHASHSEED = '123' PYTHONHASHSEED = '123'

View file

@ -213,7 +213,7 @@ class TestInteractiveInterpreter(unittest.TestCase):
p.stdin.write(user_input) p.stdin.write(user_input)
user_input2 = dedent(""" user_input2 = dedent("""
import linecache import linecache
print(linecache._interactive_cache[id(foo.__code__)]) print(linecache._interactive_cache[linecache._make_key(foo.__code__)])
""") """)
p.stdin.write(user_input2) p.stdin.write(user_input2)
output = kill_python(p) output = kill_python(p)