Skip to content

Commit

Permalink
Exception chains can be navigated when dropped into Pdb in Python 3.1…
Browse files Browse the repository at this point in the history
…3+ (#12708)

Closes #12707
  • Loading branch information
AWhetter authored Sep 7, 2024
1 parent 57cccf7 commit 3cc88d9
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 11 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Aron Coyle
Aron Curzon
Arthur Richard
Ashish Kurmi
Ashley Whetter
Aviral Verma
Aviv Palivoda
Babak Keyvani
Expand Down
1 change: 1 addition & 0 deletions changelog/12707.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Exception chains can be navigated when dropped into Pdb in Python 3.13+.
34 changes: 24 additions & 10 deletions src/_pytest/debugging.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,8 @@ def pytest_exception_interact(
_enter_pdb(node, call.excinfo, report)

def pytest_internalerror(self, excinfo: ExceptionInfo[BaseException]) -> None:
tb = _postmortem_traceback(excinfo)
post_mortem(tb)
exc_or_tb = _postmortem_exc_or_tb(excinfo)
post_mortem(exc_or_tb)


class PdbTrace:
Expand Down Expand Up @@ -354,32 +354,46 @@ def _enter_pdb(
tw.sep(">", "traceback")
rep.toterminal(tw)
tw.sep(">", "entering PDB")
tb = _postmortem_traceback(excinfo)
tb_or_exc = _postmortem_exc_or_tb(excinfo)
rep._pdbshown = True # type: ignore[attr-defined]
post_mortem(tb)
post_mortem(tb_or_exc)
return rep


def _postmortem_traceback(excinfo: ExceptionInfo[BaseException]) -> types.TracebackType:
def _postmortem_exc_or_tb(
excinfo: ExceptionInfo[BaseException],
) -> types.TracebackType | BaseException:
from doctest import UnexpectedException

get_exc = sys.version_info >= (3, 13)
if isinstance(excinfo.value, UnexpectedException):
# A doctest.UnexpectedException is not useful for post_mortem.
# Use the underlying exception instead:
return excinfo.value.exc_info[2]
underlying_exc = excinfo.value
if get_exc:
return underlying_exc.exc_info[1]

return underlying_exc.exc_info[2]
elif isinstance(excinfo.value, ConftestImportFailure):
# A config.ConftestImportFailure is not useful for post_mortem.
# Use the underlying exception instead:
assert excinfo.value.cause.__traceback__ is not None
return excinfo.value.cause.__traceback__
cause = excinfo.value.cause
if get_exc:
return cause

assert cause.__traceback__ is not None
return cause.__traceback__
else:
assert excinfo._excinfo is not None
if get_exc:
return excinfo._excinfo[1]

return excinfo._excinfo[2]


def post_mortem(t: types.TracebackType) -> None:
def post_mortem(tb_or_exc: types.TracebackType | BaseException) -> None:
p = pytestPDB._init_pdb("post_mortem")
p.reset()
p.interaction(None, t)
p.interaction(None, tb_or_exc)
if p.quitting:
outcomes.exit("Quitting debugger")
38 changes: 37 additions & 1 deletion testing/test_debugging.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,10 @@ def test_func():
)
assert rep.failed
assert len(pdblist) == 1
tb = _pytest._code.Traceback(pdblist[0][0])
if sys.version_info < (3, 13):
tb = _pytest._code.Traceback(pdblist[0][0])
else:
tb = _pytest._code.Traceback(pdblist[0][0].__traceback__)
assert tb[-1].name == "test_func"

def test_pdb_on_xfail(self, pytester: Pytester, pdblist) -> None:
Expand Down Expand Up @@ -921,6 +924,39 @@ def test_foo():
child.expect("custom set_trace>")
self.flush(child)

@pytest.mark.skipif(
sys.version_info < (3, 13),
reason="Navigating exception chains was introduced in 3.13",
)
def test_pdb_exception_chain_navigation(self, pytester: Pytester) -> None:
p1 = pytester.makepyfile(
"""
def inner_raise():
is_inner = True
raise RuntimeError("Woops")
def outer_raise():
is_inner = False
try:
inner_raise()
except RuntimeError:
raise RuntimeError("Woopsie")
def test_1():
outer_raise()
assert True
"""
)
child = pytester.spawn_pytest(f"--pdb {p1}")
child.expect("Pdb")
child.sendline("is_inner")
child.expect_exact("False")
child.sendline("exceptions 0")
child.sendline("is_inner")
child.expect_exact("True")
child.sendeof()
self.flush(child)


class TestDebuggingBreakpoints:
@pytest.mark.parametrize("arg", ["--pdb", ""])
Expand Down

0 comments on commit 3cc88d9

Please sign in to comment.