Skip to content

Commit 754d551

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 2 files (test_cfunction and test_cfunction_full) and 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. * Move get_gdb_repr() to PrettyPrintTests. * Replace DebuggerTests.get_sample_script() with SAMPLE_SCRIPT. * Rename checkout_hook_path to CHECKOUT_HOOK_PATH. * Rename gdb_version to GDB_VERSION_TEXT. * Replace (gdb_major_version, gdb_minor_version) with GDB_VERSION.
1 parent 62881a7 commit 754d551

11 files changed

+1171
-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, SAMPLE_SCRIPT
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=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=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

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

0 commit comments

Comments
 (0)