Skip to content

Commit d79ce89

Browse files
committed
pythongh-109972: Enhance test_gdb
* 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. * run_gdb() uses "backslashreplace" error handler. * Add check_gdb() function to util.py.
1 parent 9be283e commit d79ce89

File tree

7 files changed

+285
-211
lines changed

7 files changed

+285
-211
lines changed

Lib/test/test_gdb/test_backtrace.py

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

Lines changed: 58 additions & 56 deletions
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', '')
Lines changed: 36 additions & 0 deletions
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

Lines changed: 10 additions & 10 deletions
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'''^.*

Lib/test/test_gdb/test_pretty_print.py

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from test import support
44

55
from .util import (
6-
BREAKPOINT_FN, gdb_major_version, gdb_minor_version,
6+
BREAKPOINT_FN, GDB_VERSION,
77
run_gdb, setup_module, DebuggerTests)
88

99

@@ -12,6 +12,42 @@ def setUpModule():
1212

1313

1414
class PrettyPrintTests(DebuggerTests):
15+
def get_gdb_repr(self, source,
16+
cmds_after_breakpoint=None,
17+
import_site=False):
18+
# Given an input python source representation of data,
19+
# run "python -c'id(DATA)'" under gdb with a breakpoint on
20+
# builtin_id and scrape out gdb's representation of the "op"
21+
# parameter, and verify that the gdb displays the same string
22+
#
23+
# Verify that the gdb displays the expected string
24+
#
25+
# For a nested structure, the first time we hit the breakpoint will
26+
# give us the top-level structure
27+
28+
# NOTE: avoid decoding too much of the traceback as some
29+
# undecodable characters may lurk there in optimized mode
30+
# (issue #19743).
31+
cmds_after_breakpoint = cmds_after_breakpoint or ["backtrace 1"]
32+
gdb_output = self.get_stack_trace(source, breakpoint=BREAKPOINT_FN,
33+
cmds_after_breakpoint=cmds_after_breakpoint,
34+
import_site=import_site)
35+
# gdb can insert additional '\n' and space characters in various places
36+
# in its output, depending on the width of the terminal it's connected
37+
# to (using its "wrap_here" function)
38+
m = re.search(
39+
# Match '#0 builtin_id(self=..., v=...)'
40+
r'#0\s+builtin_id\s+\(self\=.*,\s+v=\s*(.*?)?\)'
41+
# Match ' at Python/bltinmodule.c'.
42+
# bpo-38239: builtin_id() is defined in Python/bltinmodule.c,
43+
# but accept any "Directory\file.c" to support Link Time
44+
# Optimization (LTO).
45+
r'\s+at\s+\S*[A-Za-z]+/[A-Za-z0-9_-]+\.c',
46+
gdb_output, re.DOTALL)
47+
if not m:
48+
self.fail('Unexpected gdb output: %r\n%s' % (gdb_output, gdb_output))
49+
return m.group(1), gdb_output
50+
1551
def test_getting_backtrace(self):
1652
gdb_output = self.get_stack_trace('id(42)')
1753
self.assertTrue(BREAKPOINT_FN in gdb_output)
@@ -75,15 +111,17 @@ def test_strings(self):
75111
# as GDB might have been linked against a different version
76112
# of Python with a different encoding and coercion policy
77113
# with respect to PEP 538 and PEP 540.
78-
out, err = run_gdb(
114+
stdout, stderr = run_gdb(
79115
'--eval-command',
80116
'python import locale; print(locale.getpreferredencoding())')
81117

82-
encoding = out.rstrip()
83-
if err or not encoding:
118+
encoding = stdout
119+
if stderr or not encoding:
84120
raise RuntimeError(
85-
f'unable to determine the preferred encoding '
86-
f'of embedded Python in GDB: {err}')
121+
f'unable to determine the Python locale preferred encoding '
122+
f'of embedded Python in GDB\n'
123+
f'stdout={stdout!r}\n'
124+
f'stderr={stderr!r}')
87125

88126
def check_repr(text):
89127
try:
@@ -122,7 +160,7 @@ def test_tuples(self):
122160
@support.requires_resource('cpu')
123161
def test_sets(self):
124162
'Verify the pretty-printing of sets'
125-
if (gdb_major_version, gdb_minor_version) < (7, 3):
163+
if GDB_VERSION < (7, 3):
126164
self.skipTest("pretty-printing of sets needs gdb 7.3 or later")
127165
self.assertGdbRepr(set(), "set()")
128166
self.assertGdbRepr(set(['a']), "{'a'}")
@@ -141,7 +179,7 @@ def test_sets(self):
141179
@support.requires_resource('cpu')
142180
def test_frozensets(self):
143181
'Verify the pretty-printing of frozensets'
144-
if (gdb_major_version, gdb_minor_version) < (7, 3):
182+
if GDB_VERSION < (7, 3):
145183
self.skipTest("pretty-printing of frozensets needs gdb 7.3 or later")
146184
self.assertGdbRepr(frozenset(), "frozenset()")
147185
self.assertGdbRepr(frozenset(['a']), "frozenset({'a'})")

0 commit comments

Comments
 (0)