Skip to content

Commit c8fe6f6

Browse files
committed
pythongh-109972: Split test_gdb into a package of 3 tests
Split test_gdb.py file into a test_gdb package made 3 tests, so tests can now be run in parallel. * Create Lib/test/test_gdb/ directory. * Split test_gdb.py into multiple files in Lib/test/test_gdb/ directory. * Move Lib/test/gdb_sample.py to Lib/test/test_gdb/ directory. * Split PyBtTests.test_pycfunction() into 6 functions: * test_pycfunction_noargs() * test_pycfunction_o() * test_pycfunction_varargs() * test_pycfunction_varargs_keywords() * test_pycfunction_fastcall() * test_pycfunction_fastcall_keywords() * In verbose mode, these "pycfunction" tests now log each tested call.
1 parent 62881a7 commit c8fe6f6

File tree

9 files changed

+1128
-1065
lines changed

9 files changed

+1128
-1065
lines changed

Lib/test/libregrtest/findtests.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"test_asyncio",
2020
"test_concurrent_futures",
2121
"test_future_stmt",
22+
"test_gdb",
2223
"test_multiprocessing_fork",
2324
"test_multiprocessing_forkserver",
2425
"test_multiprocessing_spawn",

Lib/test/test_gdb.py

Lines changed: 0 additions & 1065 deletions
This file was deleted.

Lib/test/test_gdb/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Verify that gdb can pretty-print the various PyObject* types
2+
#
3+
# The code for testing gdb was adapted from similar work in Unladen Swallow's
4+
# Lib/test/test_jit_gdb.py
5+
6+
import os
7+
from test.support import load_package_tests
8+
9+
def load_tests(*args):
10+
return load_package_tests(os.path.dirname(__file__), *args)
File renamed without changes.

Lib/test/test_gdb/test_backtrace.py

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
import re
2+
import textwrap
3+
import unittest
4+
5+
from test import support
6+
from test.support import python_is_optimized
7+
8+
from .util import (
9+
CET_PROTECTION,
10+
setup_module, DebuggerTests)
11+
12+
13+
def setUpModule():
14+
setup_module()
15+
16+
17+
class PyBtTests(DebuggerTests):
18+
@unittest.skipIf(python_is_optimized(),
19+
"Python was compiled with optimizations")
20+
def test_bt(self):
21+
'Verify that the "py-bt" command works'
22+
bt = self.get_stack_trace(script=self.get_sample_script(),
23+
cmds_after_breakpoint=['py-bt'])
24+
self.assertMultilineMatches(bt,
25+
r'''^.*
26+
Traceback \(most recent call first\):
27+
<built-in method id of module object .*>
28+
File ".*gdb_sample.py", line 10, in baz
29+
id\(42\)
30+
File ".*gdb_sample.py", line 7, in bar
31+
baz\(a, b, c\)
32+
File ".*gdb_sample.py", line 4, in foo
33+
bar\(a=a, b=b, c=c\)
34+
File ".*gdb_sample.py", line 12, in <module>
35+
foo\(1, 2, 3\)
36+
''')
37+
38+
@unittest.skipIf(python_is_optimized(),
39+
"Python was compiled with optimizations")
40+
def test_bt_full(self):
41+
'Verify that the "py-bt-full" command works'
42+
bt = self.get_stack_trace(script=self.get_sample_script(),
43+
cmds_after_breakpoint=['py-bt-full'])
44+
self.assertMultilineMatches(bt,
45+
r'''^.*
46+
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
47+
baz\(a, b, c\)
48+
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 4, in foo \(a=1, b=2, c=3\)
49+
bar\(a=a, b=b, c=c\)
50+
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 12, in <module> \(\)
51+
foo\(1, 2, 3\)
52+
''')
53+
54+
@unittest.skipIf(python_is_optimized(),
55+
"Python was compiled with optimizations")
56+
@support.requires_resource('cpu')
57+
def test_threads(self):
58+
'Verify that "py-bt" indicates threads that are waiting for the GIL'
59+
cmd = '''
60+
from threading import Thread
61+
62+
class TestThread(Thread):
63+
# These threads would run forever, but we'll interrupt things with the
64+
# debugger
65+
def run(self):
66+
i = 0
67+
while 1:
68+
i += 1
69+
70+
t = {}
71+
for i in range(4):
72+
t[i] = TestThread()
73+
t[i].start()
74+
75+
# Trigger a breakpoint on the main thread
76+
id(42)
77+
78+
'''
79+
# Verify with "py-bt":
80+
gdb_output = self.get_stack_trace(cmd,
81+
cmds_after_breakpoint=['thread apply all py-bt'])
82+
self.assertIn('Waiting for the GIL', gdb_output)
83+
84+
# Verify with "py-bt-full":
85+
gdb_output = self.get_stack_trace(cmd,
86+
cmds_after_breakpoint=['thread apply all py-bt-full'])
87+
self.assertIn('Waiting for the GIL', gdb_output)
88+
89+
@unittest.skipIf(python_is_optimized(),
90+
"Python was compiled with optimizations")
91+
# Some older versions of gdb will fail with
92+
# "Cannot find new threads: generic error"
93+
# unless we add LD_PRELOAD=PATH-TO-libpthread.so.1 as a workaround
94+
def test_gc(self):
95+
'Verify that "py-bt" indicates if a thread is garbage-collecting'
96+
cmd = ('from gc import collect\n'
97+
'id(42)\n'
98+
'def foo():\n'
99+
' collect()\n'
100+
'def bar():\n'
101+
' foo()\n'
102+
'bar()\n')
103+
# Verify with "py-bt":
104+
gdb_output = self.get_stack_trace(cmd,
105+
cmds_after_breakpoint=['break update_refs', 'continue', 'py-bt'],
106+
)
107+
self.assertIn('Garbage-collecting', gdb_output)
108+
109+
# Verify with "py-bt-full":
110+
gdb_output = self.get_stack_trace(cmd,
111+
cmds_after_breakpoint=['break update_refs', 'continue', 'py-bt-full'],
112+
)
113+
self.assertIn('Garbage-collecting', gdb_output)
114+
115+
116+
@unittest.skipIf(python_is_optimized(),
117+
"Python was compiled with optimizations")
118+
@support.requires_resource('cpu')
119+
# Some older versions of gdb will fail with
120+
# "Cannot find new threads: generic error"
121+
# unless we add LD_PRELOAD=PATH-TO-libpthread.so.1 as a workaround
122+
#
123+
# gdb will also generate many erroneous errors such as:
124+
# Function "meth_varargs" not defined.
125+
# This is because we are calling functions from an "external" module
126+
# (_testcapimodule) rather than compiled-in functions. It seems difficult
127+
# to suppress these. See also the comment in DebuggerTests.get_stack_trace
128+
def check_pycfunction(self, func_name, args):
129+
'Verify that "py-bt" displays invocations of PyCFunction instances'
130+
131+
if support.verbose:
132+
print()
133+
134+
# Various optimizations multiply the code paths by which these are
135+
# called, so test a variety of calling conventions.
136+
for obj in (
137+
'_testcapi',
138+
'_testcapi.MethClass',
139+
'_testcapi.MethClass()',
140+
'_testcapi.MethStatic()',
141+
142+
# XXX: bound methods don't yet give nice tracebacks
143+
# '_testcapi.MethInstance()',
144+
):
145+
with self.subTest(f'{obj}.{func_name}'):
146+
call = f'{obj}.{func_name}({args})'
147+
cmd = textwrap.dedent(f'''
148+
import _testcapi
149+
def foo():
150+
{call}
151+
def bar():
152+
foo()
153+
bar()
154+
''')
155+
if support.verbose:
156+
print(f' test call: {call}', flush=True)
157+
# Verify with "py-bt":
158+
gdb_output = self.get_stack_trace(
159+
cmd,
160+
breakpoint=func_name,
161+
cmds_after_breakpoint=['bt', 'py-bt'],
162+
# bpo-45207: Ignore 'Function "meth_varargs" not
163+
# defined.' message in stderr.
164+
ignore_stderr=True,
165+
)
166+
self.assertIn(f'<built-in method {func_name}', gdb_output)
167+
168+
# Verify with "py-bt-full":
169+
gdb_output = self.get_stack_trace(
170+
cmd,
171+
breakpoint=func_name,
172+
cmds_after_breakpoint=['py-bt-full'],
173+
# bpo-45207: Ignore 'Function "meth_varargs" not
174+
# defined.' message in stderr.
175+
ignore_stderr=True,
176+
)
177+
178+
# bpo-46600: If the compiler inlines _null_to_none() in
179+
# meth_varargs() (ex: clang -Og), _null_to_none() is the
180+
# frame #1. Otherwise, meth_varargs() is the frame #1.
181+
regex = r'#(1|2)'
182+
regex += re.escape(f' <built-in method {func_name}')
183+
self.assertRegex(gdb_output, regex)
184+
185+
def test_pycfunction_noargs(self):
186+
self.check_pycfunction('meth_noargs', '')
187+
188+
def test_pycfunction_o(self):
189+
self.check_pycfunction('meth_o', '[]')
190+
191+
def test_pycfunction_varargs(self):
192+
self.check_pycfunction('meth_varargs', '')
193+
194+
def test_pycfunction_varargs_keywords(self):
195+
self.check_pycfunction('meth_varargs_keywords', '')
196+
197+
def test_pycfunction_fastcall(self):
198+
self.check_pycfunction('meth_fastcall', '')
199+
200+
def test_pycfunction_fastcall_keywords(self):
201+
self.check_pycfunction('meth_fastcall_keywords', '')
202+
203+
@unittest.skipIf(python_is_optimized(),
204+
"Python was compiled with optimizations")
205+
def test_wrapper_call(self):
206+
cmd = textwrap.dedent('''
207+
class MyList(list):
208+
def __init__(self):
209+
super(*[]).__init__() # wrapper_call()
210+
211+
id("first break point")
212+
l = MyList()
213+
''')
214+
cmds_after_breakpoint = ['break wrapper_call', 'continue']
215+
if CET_PROTECTION:
216+
# bpo-32962: same case as in get_stack_trace():
217+
# we need an additional 'next' command in order to read
218+
# arguments of the innermost function of the call stack.
219+
cmds_after_breakpoint.append('next')
220+
cmds_after_breakpoint.append('py-bt')
221+
222+
# Verify with "py-bt":
223+
gdb_output = self.get_stack_trace(cmd,
224+
cmds_after_breakpoint=cmds_after_breakpoint)
225+
self.assertRegex(gdb_output,
226+
r"<method-wrapper u?'__init__' of MyList object at ")

0 commit comments

Comments
 (0)