Skip to content

Commit c29b162

Browse files
committed
pythongh-109972: Split test_gdb.py into test_gdb package
Split test_gdb.py file into a test_gdb package made of multiple 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. Update get_sample_script(): use __file__ to locate gdb_sample.py. * Move gdb_has_frame_select() and HAS_PYUP_PYDOWN to test_misc.py. * Explicitly skip test_gdb on Windows. Previously, test_gdb was skipped even if gdb was available because of gdb_has_frame_select().
1 parent 98c0c1d commit c29b162

11 files changed

+1124
-1066
lines changed

Lib/test/libregrtest/findtests.py

+1
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

-1,065
This file was deleted.

Lib/test/test_gdb/__init__.py

+10
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)

Lib/test/gdb_sample.py renamed to Lib/test/test_gdb/gdb_sample.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Sample script for use by test_gdb.py
1+
# Sample script for use by test_gdb
22

33
def foo(a, b, c):
44
bar(a=a, b=b, c=c)

Lib/test/test_gdb/test_backtrace.py

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import textwrap
2+
import unittest
3+
from test import support
4+
from test.support import python_is_optimized
5+
6+
from .util import setup_module, DebuggerTests, CET_PROTECTION
7+
8+
9+
def setUpModule():
10+
setup_module()
11+
12+
13+
class PyBtTests(DebuggerTests):
14+
@unittest.skipIf(python_is_optimized(),
15+
"Python was compiled with optimizations")
16+
def test_bt(self):
17+
'Verify that the "py-bt" command works'
18+
bt = self.get_stack_trace(script=self.get_sample_script(),
19+
cmds_after_breakpoint=['py-bt'])
20+
self.assertMultilineMatches(bt,
21+
r'''^.*
22+
Traceback \(most recent call first\):
23+
<built-in method id of module object .*>
24+
File ".*gdb_sample.py", line 10, in baz
25+
id\(42\)
26+
File ".*gdb_sample.py", line 7, in bar
27+
baz\(a, b, c\)
28+
File ".*gdb_sample.py", line 4, in foo
29+
bar\(a=a, b=b, c=c\)
30+
File ".*gdb_sample.py", line 12, in <module>
31+
foo\(1, 2, 3\)
32+
''')
33+
34+
@unittest.skipIf(python_is_optimized(),
35+
"Python was compiled with optimizations")
36+
def test_bt_full(self):
37+
'Verify that the "py-bt-full" command works'
38+
bt = self.get_stack_trace(script=self.get_sample_script(),
39+
cmds_after_breakpoint=['py-bt-full'])
40+
self.assertMultilineMatches(bt,
41+
r'''^.*
42+
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
43+
baz\(a, b, c\)
44+
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 4, in foo \(a=1, b=2, c=3\)
45+
bar\(a=a, b=b, c=c\)
46+
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 12, in <module> \(\)
47+
foo\(1, 2, 3\)
48+
''')
49+
50+
@unittest.skipIf(python_is_optimized(),
51+
"Python was compiled with optimizations")
52+
@support.requires_resource('cpu')
53+
def test_threads(self):
54+
'Verify that "py-bt" indicates threads that are waiting for the GIL'
55+
cmd = '''
56+
from threading import Thread
57+
58+
class TestThread(Thread):
59+
# These threads would run forever, but we'll interrupt things with the
60+
# debugger
61+
def run(self):
62+
i = 0
63+
while 1:
64+
i += 1
65+
66+
t = {}
67+
for i in range(4):
68+
t[i] = TestThread()
69+
t[i].start()
70+
71+
# Trigger a breakpoint on the main thread
72+
id(42)
73+
74+
'''
75+
# Verify with "py-bt":
76+
gdb_output = self.get_stack_trace(cmd,
77+
cmds_after_breakpoint=['thread apply all py-bt'])
78+
self.assertIn('Waiting for the GIL', gdb_output)
79+
80+
# Verify with "py-bt-full":
81+
gdb_output = self.get_stack_trace(cmd,
82+
cmds_after_breakpoint=['thread apply all py-bt-full'])
83+
self.assertIn('Waiting for the GIL', gdb_output)
84+
85+
@unittest.skipIf(python_is_optimized(),
86+
"Python was compiled with optimizations")
87+
# Some older versions of gdb will fail with
88+
# "Cannot find new threads: generic error"
89+
# unless we add LD_PRELOAD=PATH-TO-libpthread.so.1 as a workaround
90+
def test_gc(self):
91+
'Verify that "py-bt" indicates if a thread is garbage-collecting'
92+
cmd = ('from gc import collect\n'
93+
'id(42)\n'
94+
'def foo():\n'
95+
' collect()\n'
96+
'def bar():\n'
97+
' foo()\n'
98+
'bar()\n')
99+
# Verify with "py-bt":
100+
gdb_output = self.get_stack_trace(cmd,
101+
cmds_after_breakpoint=['break update_refs', 'continue', 'py-bt'],
102+
)
103+
self.assertIn('Garbage-collecting', gdb_output)
104+
105+
# Verify with "py-bt-full":
106+
gdb_output = self.get_stack_trace(cmd,
107+
cmds_after_breakpoint=['break update_refs', 'continue', 'py-bt-full'],
108+
)
109+
self.assertIn('Garbage-collecting', gdb_output)
110+
111+
@unittest.skipIf(python_is_optimized(),
112+
"Python was compiled with optimizations")
113+
def test_wrapper_call(self):
114+
cmd = textwrap.dedent('''
115+
class MyList(list):
116+
def __init__(self):
117+
super(*[]).__init__() # wrapper_call()
118+
119+
id("first break point")
120+
l = MyList()
121+
''')
122+
cmds_after_breakpoint = ['break wrapper_call', 'continue']
123+
if CET_PROTECTION:
124+
# bpo-32962: same case as in get_stack_trace():
125+
# we need an additional 'next' command in order to read
126+
# arguments of the innermost function of the call stack.
127+
cmds_after_breakpoint.append('next')
128+
cmds_after_breakpoint.append('py-bt')
129+
130+
# Verify with "py-bt":
131+
gdb_output = self.get_stack_trace(cmd,
132+
cmds_after_breakpoint=cmds_after_breakpoint)
133+
self.assertRegex(gdb_output,
134+
r"<method-wrapper u?'__init__' of MyList object at ")

Lib/test/test_gdb/test_cfunction.py

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import re
2+
import textwrap
3+
import unittest
4+
from test import support
5+
from test.support import python_is_optimized
6+
7+
from .util import setup_module, DebuggerTests
8+
9+
10+
def setUpModule():
11+
setup_module()
12+
13+
14+
@unittest.skipIf(python_is_optimized(),
15+
"Python was compiled with optimizations")
16+
@support.requires_resource('cpu')
17+
class PyCFunctionTests(DebuggerTests):
18+
# Some older versions of gdb will fail with
19+
# "Cannot find new threads: generic error"
20+
# unless we add LD_PRELOAD=PATH-TO-libpthread.so.1 as a workaround
21+
#
22+
# gdb will also generate many erroneous errors such as:
23+
# Function "meth_varargs" not defined.
24+
# This is because we are calling functions from an "external" module
25+
# (_testcapimodule) rather than compiled-in functions. It seems difficult
26+
# to suppress these. See also the comment in DebuggerTests.get_stack_trace
27+
def test_pycfunction(self):
28+
'Verify that "py-bt" displays invocations of PyCFunction instances'
29+
# bpo-46600: If the compiler inlines _null_to_none() in meth_varargs()
30+
# (ex: clang -Og), _null_to_none() is the frame #1. Otherwise,
31+
# meth_varargs() is the frame #1.
32+
expected_frame = r'#(1|2)'
33+
# Various optimizations multiply the code paths by which these are
34+
# called, so test a variety of calling conventions.
35+
for func_name, args in (
36+
('meth_varargs', ''),
37+
('meth_varargs_keywords', ''),
38+
('meth_o', '[]'),
39+
('meth_noargs', ''),
40+
('meth_fastcall', ''),
41+
('meth_fastcall_keywords', ''),
42+
):
43+
for obj in (
44+
'_testcapi',
45+
'_testcapi.MethClass',
46+
'_testcapi.MethClass()',
47+
'_testcapi.MethStatic()',
48+
49+
# XXX: bound methods don't yet give nice tracebacks
50+
# '_testcapi.MethInstance()',
51+
):
52+
with self.subTest(f'{obj}.{func_name}'):
53+
cmd = textwrap.dedent(f'''
54+
import _testcapi
55+
def foo():
56+
{obj}.{func_name}({args})
57+
def bar():
58+
foo()
59+
bar()
60+
''')
61+
# Verify with "py-bt":
62+
gdb_output = self.get_stack_trace(
63+
cmd,
64+
breakpoint=func_name,
65+
cmds_after_breakpoint=['bt', 'py-bt'],
66+
# bpo-45207: Ignore 'Function "meth_varargs" not
67+
# defined.' message in stderr.
68+
ignore_stderr=True,
69+
)
70+
self.assertIn(f'<built-in method {func_name}', gdb_output)
71+
72+
# Verify with "py-bt-full":
73+
gdb_output = self.get_stack_trace(
74+
cmd,
75+
breakpoint=func_name,
76+
cmds_after_breakpoint=['py-bt-full'],
77+
# bpo-45207: Ignore 'Function "meth_varargs" not
78+
# defined.' message in stderr.
79+
ignore_stderr=True,
80+
)
81+
regex = expected_frame
82+
regex += re.escape(f' <built-in method {func_name}')
83+
self.assertRegex(gdb_output, regex)

0 commit comments

Comments
 (0)