Skip to content

Commit

Permalink
[3.11] gh-109972: Split test_gdb.py into test_gdb package (#109977) (… (
Browse files Browse the repository at this point in the history
#110343)

[3.12] gh-109972: Split test_gdb.py into test_gdb package (#109977) (#110339)

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().

(cherry picked from commit 8f324b7)
(cherry picked from commit e7a61d3)
  • Loading branch information
vstinner authored Oct 4, 2023
1 parent 5039db7 commit 6c98c73
Show file tree
Hide file tree
Showing 11 changed files with 1,124 additions and 1,067 deletions.
1 change: 1 addition & 0 deletions Lib/test/libregrtest/runtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ def set_env_changed(self):
"test_asyncio",
"test_concurrent_futures",
"test_future_stmt",
"test_gdb",
"test_multiprocessing_fork",
"test_multiprocessing_forkserver",
"test_multiprocessing_spawn",
Expand Down
1,066 changes: 0 additions & 1,066 deletions Lib/test/test_gdb.py

This file was deleted.

10 changes: 10 additions & 0 deletions Lib/test/test_gdb/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Verify that gdb can pretty-print the various PyObject* types
#
# The code for testing gdb was adapted from similar work in Unladen Swallow's
# Lib/test/test_jit_gdb.py

import os
from test.support import load_package_tests

def load_tests(*args):
return load_package_tests(os.path.dirname(__file__), *args)
2 changes: 1 addition & 1 deletion Lib/test/gdb_sample.py → Lib/test/test_gdb/gdb_sample.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Sample script for use by test_gdb.py
# Sample script for use by test_gdb

def foo(a, b, c):
bar(a=a, b=b, c=c)
Expand Down
134 changes: 134 additions & 0 deletions Lib/test/test_gdb/test_backtrace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import textwrap
import unittest
from test import support
from test.support import python_is_optimized

from .util import setup_module, DebuggerTests, CET_PROTECTION


def setUpModule():
setup_module()


class PyBtTests(DebuggerTests):
@unittest.skipIf(python_is_optimized(),
"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(),
cmds_after_breakpoint=['py-bt'])
self.assertMultilineMatches(bt,
r'''^.*
Traceback \(most recent call first\):
<built-in method id of module object .*>
File ".*gdb_sample.py", line 10, in baz
id\(42\)
File ".*gdb_sample.py", line 7, in bar
baz\(a, b, c\)
File ".*gdb_sample.py", line 4, in foo
bar\(a=a, b=b, c=c\)
File ".*gdb_sample.py", line 12, in <module>
foo\(1, 2, 3\)
''')

@unittest.skipIf(python_is_optimized(),
"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(),
cmds_after_breakpoint=['py-bt-full'])
self.assertMultilineMatches(bt,
r'''^.*
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
baz\(a, b, c\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 4, in foo \(a=1, b=2, c=3\)
bar\(a=a, b=b, c=c\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 12, in <module> \(\)
foo\(1, 2, 3\)
''')

@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
@support.requires_resource('cpu')
def test_threads(self):
'Verify that "py-bt" indicates threads that are waiting for the GIL'
cmd = '''
from threading import Thread
class TestThread(Thread):
# These threads would run forever, but we'll interrupt things with the
# debugger
def run(self):
i = 0
while 1:
i += 1
t = {}
for i in range(4):
t[i] = TestThread()
t[i].start()
# Trigger a breakpoint on the main thread
id(42)
'''
# Verify with "py-bt":
gdb_output = self.get_stack_trace(cmd,
cmds_after_breakpoint=['thread apply all py-bt'])
self.assertIn('Waiting for the GIL', gdb_output)

# Verify with "py-bt-full":
gdb_output = self.get_stack_trace(cmd,
cmds_after_breakpoint=['thread apply all py-bt-full'])
self.assertIn('Waiting for the GIL', gdb_output)

@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
# 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
def test_gc(self):
'Verify that "py-bt" indicates if a thread is garbage-collecting'
cmd = ('from gc import collect\n'
'id(42)\n'
'def foo():\n'
' collect()\n'
'def bar():\n'
' foo()\n'
'bar()\n')
# Verify with "py-bt":
gdb_output = self.get_stack_trace(cmd,
cmds_after_breakpoint=['break update_refs', 'continue', 'py-bt'],
)
self.assertIn('Garbage-collecting', gdb_output)

# Verify with "py-bt-full":
gdb_output = self.get_stack_trace(cmd,
cmds_after_breakpoint=['break update_refs', 'continue', 'py-bt-full'],
)
self.assertIn('Garbage-collecting', gdb_output)

@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
def test_wrapper_call(self):
cmd = textwrap.dedent('''
class MyList(list):
def __init__(self):
super(*[]).__init__() # wrapper_call()
id("first break point")
l = MyList()
''')
cmds_after_breakpoint = ['break wrapper_call', 'continue']
if CET_PROTECTION:
# bpo-32962: same case as in get_stack_trace():
# we need an additional 'next' command in order to read
# arguments of the innermost function of the call stack.
cmds_after_breakpoint.append('next')
cmds_after_breakpoint.append('py-bt')

# Verify with "py-bt":
gdb_output = self.get_stack_trace(cmd,
cmds_after_breakpoint=cmds_after_breakpoint)
self.assertRegex(gdb_output,
r"<method-wrapper u?'__init__' of MyList object at ")
83 changes: 83 additions & 0 deletions Lib/test/test_gdb/test_cfunction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import re
import textwrap
import unittest
from test import support
from test.support import python_is_optimized

from .util import setup_module, DebuggerTests


def setUpModule():
setup_module()


@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
@support.requires_resource('cpu')
class CFunctionTests(DebuggerTests):
# 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
#
# gdb will also generate many erroneous errors such as:
# Function "meth_varargs" not defined.
# 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):
'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)'
# 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()',
):
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)
Loading

0 comments on commit 6c98c73

Please sign in to comment.