Skip to content

Commit 8f324b7

Browse files
authored
gh-109972: Split test_gdb.py into test_gdb package (#109977)
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 8f324b7

File tree

11 files changed

+1124
-1066
lines changed

11 files changed

+1124
-1066
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)

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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 134 additions & 0 deletions
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

Lines changed: 83 additions & 0 deletions
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 CFunctionTests(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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy