Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-109972: Enhance test_gdb #110026

Merged
merged 2 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -777,14 +777,17 @@ def check_cflags_pgo():
# Check if Python was built with ./configure --enable-optimizations:
# with Profile Guided Optimization (PGO).
cflags_nodist = sysconfig.get_config_var('PY_CFLAGS_NODIST') or ''
pgo_options = (
pgo_options = [
# GCC
'-fprofile-use',
# clang: -fprofile-instr-use=code.profclangd
'-fprofile-instr-use',
# ICC
"-prof-use",
)
]
PGO_PROF_USE_FLAG = sysconfig.get_config_var('PGO_PROF_USE_FLAG')
if PGO_PROF_USE_FLAG:
pgo_options.append(PGO_PROF_USE_FLAG)
return any(option in cflags_nodist for option in pgo_options)


Expand Down
24 changes: 22 additions & 2 deletions Lib/test/test_gdb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,27 @@
# Lib/test/test_jit_gdb.py

import os
from test.support import load_package_tests
import sysconfig
import unittest
from test import support


MS_WINDOWS = (os.name == 'nt')
if MS_WINDOWS:
# On Windows, Python is usually built by MSVC. Passing /p:DebugSymbols=true
# option to MSBuild produces PDB debug symbols, but gdb doesn't support PDB
# debug symbol files.
raise unittest.SkipTest("test_gdb doesn't work on Windows")

if support.PGO:
raise unittest.SkipTest("test_gdb is not useful for PGO")

if not sysconfig.is_python_build():
raise unittest.SkipTest("test_gdb only works on source builds at the moment.")

if support.check_cflags_pgo():
raise unittest.SkipTest("test_gdb is not reliable on PGO builds")


def load_tests(*args):
return load_package_tests(os.path.dirname(__file__), *args)
return support.load_package_tests(os.path.dirname(__file__), *args)
6 changes: 3 additions & 3 deletions Lib/test/test_gdb/test_backtrace.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from test import support
from test.support import python_is_optimized

from .util import setup_module, DebuggerTests, CET_PROTECTION
from .util import setup_module, DebuggerTests, CET_PROTECTION, SAMPLE_SCRIPT


def setUpModule():
Expand All @@ -15,7 +15,7 @@ class PyBtTests(DebuggerTests):
"Python was compiled with optimizations")
def test_bt(self):
'Verify that the "py-bt" command works'
bt = self.get_stack_trace(script=self.get_sample_script(),
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
cmds_after_breakpoint=['py-bt'])
self.assertMultilineMatches(bt,
r'''^.*
Expand All @@ -35,7 +35,7 @@ def test_bt(self):
"Python was compiled with optimizations")
def test_bt_full(self):
'Verify that the "py-bt-full" command works'
bt = self.get_stack_trace(script=self.get_sample_script(),
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
cmds_after_breakpoint=['py-bt-full'])
self.assertMultilineMatches(bt,
r'''^.*
Expand Down
114 changes: 58 additions & 56 deletions Lib/test/test_gdb/test_cfunction.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import re
import textwrap
import unittest
from test import support
from test.support import python_is_optimized

from .util import setup_module, DebuggerTests

Expand All @@ -11,10 +9,22 @@ def setUpModule():
setup_module()


@unittest.skipIf(python_is_optimized(),
@unittest.skipIf(support.python_is_optimized(),
"Python was compiled with optimizations")
@support.requires_resource('cpu')
class CFunctionTests(DebuggerTests):
def check(self, func_name, cmd):
# Verify with "py-bt":
gdb_output = self.get_stack_trace(
cmd,
breakpoint=func_name,
cmds_after_breakpoint=['bt', 'py-bt'],
# bpo-45207: Ignore 'Function "meth_varargs" not
# defined.' message in stderr.
ignore_stderr=True,
)
self.assertIn(f'<built-in method {func_name}', gdb_output)

# Some older versions of gdb will fail with
# "Cannot find new threads: generic error"
# unless we add LD_PRELOAD=PATH-TO-libpthread.so.1 as a workaround
Expand All @@ -24,60 +34,52 @@ class CFunctionTests(DebuggerTests):
# This is because we are calling functions from an "external" module
# (_testcapimodule) rather than compiled-in functions. It seems difficult
# to suppress these. See also the comment in DebuggerTests.get_stack_trace
def test_pycfunction(self):
def check_pycfunction(self, func_name, args):
'Verify that "py-bt" displays invocations of PyCFunction instances'
# bpo-46600: If the compiler inlines _null_to_none() in meth_varargs()
# (ex: clang -Og), _null_to_none() is the frame #1. Otherwise,
# meth_varargs() is the frame #1.
expected_frame = r'#(1|2)'

if support.verbose:
print()

# Various optimizations multiply the code paths by which these are
# called, so test a variety of calling conventions.
for func_name, args in (
('meth_varargs', ''),
('meth_varargs_keywords', ''),
('meth_o', '[]'),
('meth_noargs', ''),
('meth_fastcall', ''),
('meth_fastcall_keywords', ''),
for obj in (
'_testcapi',
'_testcapi.MethClass',
'_testcapi.MethClass()',
'_testcapi.MethStatic()',

# XXX: bound methods don't yet give nice tracebacks
# '_testcapi.MethInstance()',
):
for obj in (
'_testcapi',
'_testcapi.MethClass',
'_testcapi.MethClass()',
'_testcapi.MethStatic()',

# XXX: bound methods don't yet give nice tracebacks
# '_testcapi.MethInstance()',
):
with self.subTest(f'{obj}.{func_name}'):
cmd = textwrap.dedent(f'''
import _testcapi
def foo():
{obj}.{func_name}({args})
def bar():
foo()
bar()
''')
# Verify with "py-bt":
gdb_output = self.get_stack_trace(
cmd,
breakpoint=func_name,
cmds_after_breakpoint=['bt', 'py-bt'],
# bpo-45207: Ignore 'Function "meth_varargs" not
# defined.' message in stderr.
ignore_stderr=True,
)
self.assertIn(f'<built-in method {func_name}', gdb_output)

# Verify with "py-bt-full":
gdb_output = self.get_stack_trace(
cmd,
breakpoint=func_name,
cmds_after_breakpoint=['py-bt-full'],
# bpo-45207: Ignore 'Function "meth_varargs" not
# defined.' message in stderr.
ignore_stderr=True,
)
regex = expected_frame
regex += re.escape(f' <built-in method {func_name}')
self.assertRegex(gdb_output, regex)
with self.subTest(f'{obj}.{func_name}'):
call = f'{obj}.{func_name}({args})'
cmd = textwrap.dedent(f'''
import _testcapi
def foo():
{call}
def bar():
foo()
bar()
''')
if support.verbose:
print(f' test call: {call}', flush=True)

self.check(func_name, cmd)

def test_pycfunction_noargs(self):
self.check_pycfunction('meth_noargs', '')

def test_pycfunction_o(self):
self.check_pycfunction('meth_o', '[]')

def test_pycfunction_varargs(self):
self.check_pycfunction('meth_varargs', '')

def test_pycfunction_varargs_keywords(self):
self.check_pycfunction('meth_varargs_keywords', '')

def test_pycfunction_fastcall(self):
self.check_pycfunction('meth_fastcall', '')

def test_pycfunction_fastcall_keywords(self):
self.check_pycfunction('meth_fastcall_keywords', '')
36 changes: 36 additions & 0 deletions Lib/test/test_gdb/test_cfunction_full.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""
Similar to test_cfunction but test "py-bt-full" command.
"""

import re

from .util import setup_module
from .test_cfunction import CFunctionTests


def setUpModule():
setup_module()


class CFunctionFullTests(CFunctionTests):
def check(self, func_name, cmd):
# Verify with "py-bt-full":
gdb_output = self.get_stack_trace(
cmd,
breakpoint=func_name,
cmds_after_breakpoint=['py-bt-full'],
# bpo-45207: Ignore 'Function "meth_varargs" not
# defined.' message in stderr.
ignore_stderr=True,
)

# bpo-46600: If the compiler inlines _null_to_none() in
# meth_varargs() (ex: clang -Og), _null_to_none() is the
# frame #1. Otherwise, meth_varargs() is the frame #1.
regex = r'#(1|2)'
regex += re.escape(f' <built-in method {func_name}')
self.assertRegex(gdb_output, regex)


# Delete the test case, otherwise it's executed twice
del CFunctionTests
20 changes: 10 additions & 10 deletions Lib/test/test_gdb/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import unittest
from test.support import python_is_optimized

from .util import run_gdb, setup_module, DebuggerTests
from .util import run_gdb, setup_module, DebuggerTests, SAMPLE_SCRIPT


def setUpModule():
Expand Down Expand Up @@ -32,7 +32,7 @@ def assertListing(self, expected, actual):

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

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

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

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

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

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

@unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
def test_up_at_top(self):
'Verify handling of "py-up" at the top of the stack'
bt = self.get_stack_trace(script=self.get_sample_script(),
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
cmds_after_breakpoint=['py-up'] * 5)
self.assertEndsWith(bt,
'Unable to find an older python frame\n')
Expand Down Expand Up @@ -150,15 +150,15 @@ def test_print_after_up(self):
@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
def test_printing_global(self):
bt = self.get_stack_trace(script=self.get_sample_script(),
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
cmds_after_breakpoint=['py-up', 'py-print __name__'])
self.assertMultilineMatches(bt,
r".*\nglobal '__name__' = '__main__'\n.*")

@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
def test_printing_builtin(self):
bt = self.get_stack_trace(script=self.get_sample_script(),
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
cmds_after_breakpoint=['py-up', 'py-print len'])
self.assertMultilineMatches(bt,
r".*\nbuiltin 'len' = <built-in method len of module object at remote 0x-?[0-9a-f]+>\n.*")
Expand All @@ -167,7 +167,7 @@ class PyLocalsTests(DebuggerTests):
@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
def test_basic_command(self):
bt = self.get_stack_trace(script=self.get_sample_script(),
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
cmds_after_breakpoint=['py-up', 'py-locals'])
self.assertMultilineMatches(bt,
r".*\nargs = \(1, 2, 3\)\n.*")
Expand All @@ -176,7 +176,7 @@ def test_basic_command(self):
@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
def test_locals_after_up(self):
bt = self.get_stack_trace(script=self.get_sample_script(),
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
cmds_after_breakpoint=['py-up', 'py-up', 'py-locals'])
self.assertMultilineMatches(bt,
r'''^.*
Expand Down
Loading
Loading