Skip to content

Commit 757cbd4

Browse files
authored
gh-109972: Enhance test_gdb (#110026)
* Split test_pycfunction.py: add test_cfunction_full.py. Split the function into the following 6 functions. In verbose mode, these "pycfunction" tests now log each tested call. * test_pycfunction_noargs() * test_pycfunction_o() * test_pycfunction_varargs() * test_pycfunction_varargs_keywords() * test_pycfunction_fastcall() * test_pycfunction_fastcall_keywords() * 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. * run_gdb() uses "backslashreplace" error handler instead of "replace". * Add check_gdb() function to util.py. * Enhance support.check_cflags_pgo(): check also for sysconfig PGO_PROF_USE_FLAG (if available) in compiler flags. * Move some SkipTest checks to test_gdb/__init__.py. * Elaborate why gdb cannot be tested on Windows: gdb doesn't support PDB debug symbol files.
1 parent c4eda57 commit 757cbd4

File tree

8 files changed

+299
-218
lines changed

8 files changed

+299
-218
lines changed

Lib/test/support/__init__.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -777,14 +777,17 @@ def check_cflags_pgo():
777777
# Check if Python was built with ./configure --enable-optimizations:
778778
# with Profile Guided Optimization (PGO).
779779
cflags_nodist = sysconfig.get_config_var('PY_CFLAGS_NODIST') or ''
780-
pgo_options = (
780+
pgo_options = [
781781
# GCC
782782
'-fprofile-use',
783783
# clang: -fprofile-instr-use=code.profclangd
784784
'-fprofile-instr-use',
785785
# ICC
786786
"-prof-use",
787-
)
787+
]
788+
PGO_PROF_USE_FLAG = sysconfig.get_config_var('PGO_PROF_USE_FLAG')
789+
if PGO_PROF_USE_FLAG:
790+
pgo_options.append(PGO_PROF_USE_FLAG)
788791
return any(option in cflags_nodist for option in pgo_options)
789792

790793

Lib/test/test_gdb/__init__.py

+22-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,27 @@
44
# Lib/test/test_jit_gdb.py
55

66
import os
7-
from test.support import load_package_tests
7+
import sysconfig
8+
import unittest
9+
from test import support
10+
11+
12+
MS_WINDOWS = (os.name == 'nt')
13+
if MS_WINDOWS:
14+
# On Windows, Python is usually built by MSVC. Passing /p:DebugSymbols=true
15+
# option to MSBuild produces PDB debug symbols, but gdb doesn't support PDB
16+
# debug symbol files.
17+
raise unittest.SkipTest("test_gdb doesn't work on Windows")
18+
19+
if support.PGO:
20+
raise unittest.SkipTest("test_gdb is not useful for PGO")
21+
22+
if not sysconfig.is_python_build():
23+
raise unittest.SkipTest("test_gdb only works on source builds at the moment.")
24+
25+
if support.check_cflags_pgo():
26+
raise unittest.SkipTest("test_gdb is not reliable on PGO builds")
27+
828

929
def load_tests(*args):
10-
return load_package_tests(os.path.dirname(__file__), *args)
30+
return support.load_package_tests(os.path.dirname(__file__), *args)

Lib/test/test_gdb/test_backtrace.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from test import support
44
from test.support import python_is_optimized
55

6-
from .util import setup_module, DebuggerTests, CET_PROTECTION
6+
from .util import setup_module, DebuggerTests, CET_PROTECTION, SAMPLE_SCRIPT
77

88

99
def setUpModule():
@@ -15,7 +15,7 @@ class PyBtTests(DebuggerTests):
1515
"Python was compiled with optimizations")
1616
def test_bt(self):
1717
'Verify that the "py-bt" command works'
18-
bt = self.get_stack_trace(script=self.get_sample_script(),
18+
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
1919
cmds_after_breakpoint=['py-bt'])
2020
self.assertMultilineMatches(bt,
2121
r'''^.*
@@ -35,7 +35,7 @@ def test_bt(self):
3535
"Python was compiled with optimizations")
3636
def test_bt_full(self):
3737
'Verify that the "py-bt-full" command works'
38-
bt = self.get_stack_trace(script=self.get_sample_script(),
38+
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
3939
cmds_after_breakpoint=['py-bt-full'])
4040
self.assertMultilineMatches(bt,
4141
r'''^.*

Lib/test/test_gdb/test_cfunction.py

+58-56
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import re
21
import textwrap
32
import unittest
43
from test import support
5-
from test.support import python_is_optimized
64

75
from .util import setup_module, DebuggerTests
86

@@ -11,10 +9,22 @@ def setUpModule():
119
setup_module()
1210

1311

14-
@unittest.skipIf(python_is_optimized(),
12+
@unittest.skipIf(support.python_is_optimized(),
1513
"Python was compiled with optimizations")
1614
@support.requires_resource('cpu')
1715
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+
1828
# Some older versions of gdb will fail with
1929
# "Cannot find new threads: generic error"
2030
# unless we add LD_PRELOAD=PATH-TO-libpthread.so.1 as a workaround
@@ -24,60 +34,52 @@ class CFunctionTests(DebuggerTests):
2434
# This is because we are calling functions from an "external" module
2535
# (_testcapimodule) rather than compiled-in functions. It seems difficult
2636
# to suppress these. See also the comment in DebuggerTests.get_stack_trace
27-
def test_pycfunction(self):
37+
def check_pycfunction(self, func_name, args):
2838
'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)'
39+
40+
if support.verbose:
41+
print()
42+
3343
# Various optimizations multiply the code paths by which these are
3444
# 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', ''),
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()',
4253
):
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)
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

Lib/test/test_gdb/test_misc.py

+10-10
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import unittest
33
from test.support import python_is_optimized
44

5-
from .util import run_gdb, setup_module, DebuggerTests
5+
from .util import run_gdb, setup_module, DebuggerTests, SAMPLE_SCRIPT
66

77

88
def setUpModule():
@@ -32,7 +32,7 @@ def assertListing(self, expected, actual):
3232

3333
def test_basic_command(self):
3434
'Verify that the "py-list" command works'
35-
bt = self.get_stack_trace(script=self.get_sample_script(),
35+
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
3636
cmds_after_breakpoint=['py-list'])
3737

3838
self.assertListing(' 5 \n'
@@ -47,7 +47,7 @@ def test_basic_command(self):
4747

4848
def test_one_abs_arg(self):
4949
'Verify the "py-list" command with one absolute argument'
50-
bt = self.get_stack_trace(script=self.get_sample_script(),
50+
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
5151
cmds_after_breakpoint=['py-list 9'])
5252

5353
self.assertListing(' 9 def baz(*args):\n'
@@ -58,7 +58,7 @@ def test_one_abs_arg(self):
5858

5959
def test_two_abs_args(self):
6060
'Verify the "py-list" command with two absolute arguments'
61-
bt = self.get_stack_trace(script=self.get_sample_script(),
61+
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
6262
cmds_after_breakpoint=['py-list 1,3'])
6363

6464
self.assertListing(' 1 # Sample script for use by test_gdb\n'
@@ -101,15 +101,15 @@ def test_pyup_command(self):
101101
@unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
102102
def test_down_at_bottom(self):
103103
'Verify handling of "py-down" at the bottom of the stack'
104-
bt = self.get_stack_trace(script=self.get_sample_script(),
104+
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
105105
cmds_after_breakpoint=['py-down'])
106106
self.assertEndsWith(bt,
107107
'Unable to find a newer python frame\n')
108108

109109
@unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
110110
def test_up_at_top(self):
111111
'Verify handling of "py-up" at the top of the stack'
112-
bt = self.get_stack_trace(script=self.get_sample_script(),
112+
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
113113
cmds_after_breakpoint=['py-up'] * 5)
114114
self.assertEndsWith(bt,
115115
'Unable to find an older python frame\n')
@@ -150,15 +150,15 @@ def test_print_after_up(self):
150150
@unittest.skipIf(python_is_optimized(),
151151
"Python was compiled with optimizations")
152152
def test_printing_global(self):
153-
bt = self.get_stack_trace(script=self.get_sample_script(),
153+
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
154154
cmds_after_breakpoint=['py-up', 'py-print __name__'])
155155
self.assertMultilineMatches(bt,
156156
r".*\nglobal '__name__' = '__main__'\n.*")
157157

158158
@unittest.skipIf(python_is_optimized(),
159159
"Python was compiled with optimizations")
160160
def test_printing_builtin(self):
161-
bt = self.get_stack_trace(script=self.get_sample_script(),
161+
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
162162
cmds_after_breakpoint=['py-up', 'py-print len'])
163163
self.assertMultilineMatches(bt,
164164
r".*\nbuiltin 'len' = <built-in method len of module object at remote 0x-?[0-9a-f]+>\n.*")
@@ -167,7 +167,7 @@ class PyLocalsTests(DebuggerTests):
167167
@unittest.skipIf(python_is_optimized(),
168168
"Python was compiled with optimizations")
169169
def test_basic_command(self):
170-
bt = self.get_stack_trace(script=self.get_sample_script(),
170+
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
171171
cmds_after_breakpoint=['py-up', 'py-locals'])
172172
self.assertMultilineMatches(bt,
173173
r".*\nargs = \(1, 2, 3\)\n.*")
@@ -176,7 +176,7 @@ def test_basic_command(self):
176176
@unittest.skipIf(python_is_optimized(),
177177
"Python was compiled with optimizations")
178178
def test_locals_after_up(self):
179-
bt = self.get_stack_trace(script=self.get_sample_script(),
179+
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
180180
cmds_after_breakpoint=['py-up', 'py-up', 'py-locals'])
181181
self.assertMultilineMatches(bt,
182182
r'''^.*

0 commit comments

Comments
 (0)