mirror of
https://github.com/python/cpython.git
synced 2026-01-06 07:22:09 +00:00
gh-106670: Allow Pdb to move between chained exceptions (#106676)
This commit is contained in:
parent
242bef459b
commit
f75cefd402
5 changed files with 526 additions and 20 deletions
|
|
@ -175,8 +175,8 @@ slightly different way:
|
|||
|
||||
.. function:: pm()
|
||||
|
||||
Enter post-mortem debugging of the traceback found in
|
||||
:data:`sys.last_traceback`.
|
||||
Enter post-mortem debugging of the exception found in
|
||||
:data:`sys.last_exc`.
|
||||
|
||||
|
||||
The ``run*`` functions and :func:`set_trace` are aliases for instantiating the
|
||||
|
|
@ -639,6 +639,55 @@ can be overridden by the local file.
|
|||
|
||||
Print the return value for the last return of the current function.
|
||||
|
||||
.. pdbcommand:: exceptions [excnumber]
|
||||
|
||||
List or jump between chained exceptions.
|
||||
|
||||
When using ``pdb.pm()`` or ``Pdb.post_mortem(...)`` with a chained exception
|
||||
instead of a traceback, it allows the user to move between the
|
||||
chained exceptions using ``exceptions`` command to list exceptions, and
|
||||
``exception <number>`` to switch to that exception.
|
||||
|
||||
|
||||
Example::
|
||||
|
||||
def out():
|
||||
try:
|
||||
middle()
|
||||
except Exception as e:
|
||||
raise ValueError("reraise middle() error") from e
|
||||
|
||||
def middle():
|
||||
try:
|
||||
return inner(0)
|
||||
except Exception as e:
|
||||
raise ValueError("Middle fail")
|
||||
|
||||
def inner(x):
|
||||
1 / x
|
||||
|
||||
out()
|
||||
|
||||
calling ``pdb.pm()`` will allow to move between exceptions::
|
||||
|
||||
> example.py(5)out()
|
||||
-> raise ValueError("reraise middle() error") from e
|
||||
|
||||
(Pdb) exceptions
|
||||
0 ZeroDivisionError('division by zero')
|
||||
1 ValueError('Middle fail')
|
||||
> 2 ValueError('reraise middle() error')
|
||||
|
||||
(Pdb) exceptions 0
|
||||
> example.py(16)inner()
|
||||
-> 1 / x
|
||||
|
||||
(Pdb) up
|
||||
> example.py(10)middle()
|
||||
-> return inner(0)
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
.. rubric:: Footnotes
|
||||
|
||||
.. [1] Whether a frame is considered to originate in a certain module
|
||||
|
|
|
|||
|
|
@ -158,6 +158,13 @@ pathlib
|
|||
:meth:`~pathlib.Path.is_dir`.
|
||||
(Contributed by Barney Gale in :gh:`77609` and :gh:`105793`.)
|
||||
|
||||
pdb
|
||||
---
|
||||
|
||||
* Add ability to move between chained exceptions during post mortem debugging in :func:`~pdb.pm` using
|
||||
the new ``exceptions [exc_number]`` command for Pdb. (Contributed by Matthias
|
||||
Bussonnier in :gh:`106676`.)
|
||||
|
||||
sqlite3
|
||||
-------
|
||||
|
||||
|
|
|
|||
142
Lib/pdb.py
142
Lib/pdb.py
|
|
@ -85,6 +85,7 @@
|
|||
import traceback
|
||||
import linecache
|
||||
|
||||
from contextlib import contextmanager
|
||||
from typing import Union
|
||||
|
||||
|
||||
|
|
@ -205,10 +206,15 @@ def namespace(self):
|
|||
# line_prefix = ': ' # Use this to get the old situation back
|
||||
line_prefix = '\n-> ' # Probably a better default
|
||||
|
||||
class Pdb(bdb.Bdb, cmd.Cmd):
|
||||
|
||||
|
||||
class Pdb(bdb.Bdb, cmd.Cmd):
|
||||
_previous_sigint_handler = None
|
||||
|
||||
# Limit the maximum depth of chained exceptions, we should be handling cycles,
|
||||
# but in case there are recursions, we stop at 999.
|
||||
MAX_CHAINED_EXCEPTION_DEPTH = 999
|
||||
|
||||
def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
|
||||
nosigint=False, readrc=True):
|
||||
bdb.Bdb.__init__(self, skip=skip)
|
||||
|
|
@ -256,6 +262,9 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
|
|||
self.commands_bnum = None # The breakpoint number for which we are
|
||||
# defining a list
|
||||
|
||||
self._chained_exceptions = tuple()
|
||||
self._chained_exception_index = 0
|
||||
|
||||
def sigint_handler(self, signum, frame):
|
||||
if self.allow_kbdint:
|
||||
raise KeyboardInterrupt
|
||||
|
|
@ -414,7 +423,64 @@ def preloop(self):
|
|||
self.message('display %s: %r [old: %r]' %
|
||||
(expr, newvalue, oldvalue))
|
||||
|
||||
def interaction(self, frame, traceback):
|
||||
def _get_tb_and_exceptions(self, tb_or_exc):
|
||||
"""
|
||||
Given a tracecack or an exception, return a tuple of chained exceptions
|
||||
and current traceback to inspect.
|
||||
|
||||
This will deal with selecting the right ``__cause__`` or ``__context__``
|
||||
as well as handling cycles, and return a flattened list of exceptions we
|
||||
can jump to with do_exceptions.
|
||||
|
||||
"""
|
||||
_exceptions = []
|
||||
if isinstance(tb_or_exc, BaseException):
|
||||
traceback, current = tb_or_exc.__traceback__, tb_or_exc
|
||||
|
||||
while current is not None:
|
||||
if current in _exceptions:
|
||||
break
|
||||
_exceptions.append(current)
|
||||
if current.__cause__ is not None:
|
||||
current = current.__cause__
|
||||
elif (
|
||||
current.__context__ is not None and not current.__suppress_context__
|
||||
):
|
||||
current = current.__context__
|
||||
|
||||
if len(_exceptions) >= self.MAX_CHAINED_EXCEPTION_DEPTH:
|
||||
self.message(
|
||||
f"More than {self.MAX_CHAINED_EXCEPTION_DEPTH}"
|
||||
" chained exceptions found, not all exceptions"
|
||||
"will be browsable with `exceptions`."
|
||||
)
|
||||
break
|
||||
else:
|
||||
traceback = tb_or_exc
|
||||
return tuple(reversed(_exceptions)), traceback
|
||||
|
||||
@contextmanager
|
||||
def _hold_exceptions(self, exceptions):
|
||||
"""
|
||||
Context manager to ensure proper cleaning of exceptions references
|
||||
|
||||
When given a chained exception instead of a traceback,
|
||||
pdb may hold references to many objects which may leak memory.
|
||||
|
||||
We use this context manager to make sure everything is properly cleaned
|
||||
|
||||
"""
|
||||
try:
|
||||
self._chained_exceptions = exceptions
|
||||
self._chained_exception_index = len(exceptions) - 1
|
||||
yield
|
||||
finally:
|
||||
# we can't put those in forget as otherwise they would
|
||||
# be cleared on exception change
|
||||
self._chained_exceptions = tuple()
|
||||
self._chained_exception_index = 0
|
||||
|
||||
def interaction(self, frame, tb_or_exc):
|
||||
# Restore the previous signal handler at the Pdb prompt.
|
||||
if Pdb._previous_sigint_handler:
|
||||
try:
|
||||
|
|
@ -423,14 +489,17 @@ def interaction(self, frame, traceback):
|
|||
pass
|
||||
else:
|
||||
Pdb._previous_sigint_handler = None
|
||||
if self.setup(frame, traceback):
|
||||
# no interaction desired at this time (happens if .pdbrc contains
|
||||
# a command like "continue")
|
||||
|
||||
_chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc)
|
||||
with self._hold_exceptions(_chained_exceptions):
|
||||
if self.setup(frame, tb):
|
||||
# no interaction desired at this time (happens if .pdbrc contains
|
||||
# a command like "continue")
|
||||
self.forget()
|
||||
return
|
||||
self.print_stack_entry(self.stack[self.curindex])
|
||||
self._cmdloop()
|
||||
self.forget()
|
||||
return
|
||||
self.print_stack_entry(self.stack[self.curindex])
|
||||
self._cmdloop()
|
||||
self.forget()
|
||||
|
||||
def displayhook(self, obj):
|
||||
"""Custom displayhook for the exec in default(), which prevents
|
||||
|
|
@ -1073,6 +1142,44 @@ def _select_frame(self, number):
|
|||
self.print_stack_entry(self.stack[self.curindex])
|
||||
self.lineno = None
|
||||
|
||||
def do_exceptions(self, arg):
|
||||
"""exceptions [number]
|
||||
|
||||
List or change current exception in an exception chain.
|
||||
|
||||
Without arguments, list all the current exception in the exception
|
||||
chain. Exceptions will be numbered, with the current exception indicated
|
||||
with an arrow.
|
||||
|
||||
If given an integer as argument, switch to the exception at that index.
|
||||
"""
|
||||
if not self._chained_exceptions:
|
||||
self.message(
|
||||
"Did not find chained exceptions. To move between"
|
||||
" exceptions, pdb/post_mortem must be given an exception"
|
||||
" object rather than a traceback."
|
||||
)
|
||||
return
|
||||
if not arg:
|
||||
for ix, exc in enumerate(self._chained_exceptions):
|
||||
prompt = ">" if ix == self._chained_exception_index else " "
|
||||
rep = repr(exc)
|
||||
if len(rep) > 80:
|
||||
rep = rep[:77] + "..."
|
||||
self.message(f"{prompt} {ix:>3} {rep}")
|
||||
else:
|
||||
try:
|
||||
number = int(arg)
|
||||
except ValueError:
|
||||
self.error("Argument must be an integer")
|
||||
return
|
||||
if 0 <= number < len(self._chained_exceptions):
|
||||
self._chained_exception_index = number
|
||||
self.setup(None, self._chained_exceptions[number].__traceback__)
|
||||
self.print_stack_entry(self.stack[self.curindex])
|
||||
else:
|
||||
self.error("No exception with that number")
|
||||
|
||||
def do_up(self, arg):
|
||||
"""u(p) [count]
|
||||
|
||||
|
|
@ -1890,11 +1997,15 @@ def set_trace(*, header=None):
|
|||
# Post-Mortem interface
|
||||
|
||||
def post_mortem(t=None):
|
||||
"""Enter post-mortem debugging of the given *traceback* object.
|
||||
"""Enter post-mortem debugging of the given *traceback*, or *exception*
|
||||
object.
|
||||
|
||||
If no traceback is given, it uses the one of the exception that is
|
||||
currently being handled (an exception must be being handled if the
|
||||
default is to be used).
|
||||
|
||||
If `t` is an exception object, the `exceptions` command makes it possible to
|
||||
list and inspect its chained exceptions (if any).
|
||||
"""
|
||||
# handling the default
|
||||
if t is None:
|
||||
|
|
@ -1911,12 +2022,8 @@ def post_mortem(t=None):
|
|||
p.interaction(None, t)
|
||||
|
||||
def pm():
|
||||
"""Enter post-mortem debugging of the traceback found in sys.last_traceback."""
|
||||
if hasattr(sys, 'last_exc'):
|
||||
tb = sys.last_exc.__traceback__
|
||||
else:
|
||||
tb = sys.last_traceback
|
||||
post_mortem(tb)
|
||||
"""Enter post-mortem debugging of the traceback found in sys.last_exc."""
|
||||
post_mortem(sys.last_exc)
|
||||
|
||||
|
||||
# Main program for testing
|
||||
|
|
@ -1996,8 +2103,7 @@ def main():
|
|||
traceback.print_exc()
|
||||
print("Uncaught exception. Entering post mortem debugging")
|
||||
print("Running 'cont' or 'step' will restart the program")
|
||||
t = e.__traceback__
|
||||
pdb.interaction(None, t)
|
||||
pdb.interaction(None, e)
|
||||
print("Post mortem debugger finished. The " + target +
|
||||
" will be restarted")
|
||||
|
||||
|
|
|
|||
|
|
@ -826,6 +826,349 @@ def test_convenience_variables():
|
|||
(Pdb) continue
|
||||
"""
|
||||
|
||||
|
||||
def test_post_mortem_chained():
|
||||
"""Test post mortem traceback debugging of chained exception
|
||||
|
||||
>>> def test_function_2():
|
||||
... try:
|
||||
... 1/0
|
||||
... finally:
|
||||
... print('Exception!')
|
||||
|
||||
>>> def test_function_reraise():
|
||||
... try:
|
||||
... test_function_2()
|
||||
... except ZeroDivisionError as e:
|
||||
... raise ZeroDivisionError('reraised') from e
|
||||
|
||||
>>> def test_function():
|
||||
... import pdb;
|
||||
... instance = pdb.Pdb(nosigint=True, readrc=False)
|
||||
... try:
|
||||
... test_function_reraise()
|
||||
... except Exception as e:
|
||||
... # same as pdb.post_mortem(e), but with custom pdb instance.
|
||||
... instance.reset()
|
||||
... instance.interaction(None, e)
|
||||
|
||||
>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
||||
... 'exceptions',
|
||||
... 'exceptions 0',
|
||||
... 'up',
|
||||
... 'down',
|
||||
... 'exceptions 1',
|
||||
... 'up',
|
||||
... 'down',
|
||||
... 'exceptions -1',
|
||||
... 'exceptions 3',
|
||||
... 'up',
|
||||
... 'exit',
|
||||
... ]):
|
||||
... try:
|
||||
... test_function()
|
||||
... except ZeroDivisionError:
|
||||
... print('Correctly reraised.')
|
||||
Exception!
|
||||
> <doctest test.test_pdb.test_post_mortem_chained[1]>(5)test_function_reraise()
|
||||
-> raise ZeroDivisionError('reraised') from e
|
||||
(Pdb) exceptions
|
||||
0 ZeroDivisionError('division by zero')
|
||||
> 1 ZeroDivisionError('reraised')
|
||||
(Pdb) exceptions 0
|
||||
> <doctest test.test_pdb.test_post_mortem_chained[0]>(3)test_function_2()
|
||||
-> 1/0
|
||||
(Pdb) up
|
||||
> <doctest test.test_pdb.test_post_mortem_chained[1]>(3)test_function_reraise()
|
||||
-> test_function_2()
|
||||
(Pdb) down
|
||||
> <doctest test.test_pdb.test_post_mortem_chained[0]>(3)test_function_2()
|
||||
-> 1/0
|
||||
(Pdb) exceptions 1
|
||||
> <doctest test.test_pdb.test_post_mortem_chained[1]>(5)test_function_reraise()
|
||||
-> raise ZeroDivisionError('reraised') from e
|
||||
(Pdb) up
|
||||
> <doctest test.test_pdb.test_post_mortem_chained[2]>(5)test_function()
|
||||
-> test_function_reraise()
|
||||
(Pdb) down
|
||||
> <doctest test.test_pdb.test_post_mortem_chained[1]>(5)test_function_reraise()
|
||||
-> raise ZeroDivisionError('reraised') from e
|
||||
(Pdb) exceptions -1
|
||||
*** No exception with that number
|
||||
(Pdb) exceptions 3
|
||||
*** No exception with that number
|
||||
(Pdb) up
|
||||
> <doctest test.test_pdb.test_post_mortem_chained[2]>(5)test_function()
|
||||
-> test_function_reraise()
|
||||
(Pdb) exit
|
||||
"""
|
||||
|
||||
|
||||
def test_post_mortem_cause_no_context():
|
||||
"""Test post mortem traceback debugging of chained exception
|
||||
|
||||
>>> def main():
|
||||
... try:
|
||||
... raise ValueError('Context Not Shown')
|
||||
... except Exception as e1:
|
||||
... raise ValueError("With Cause") from TypeError('The Cause')
|
||||
|
||||
>>> def test_function():
|
||||
... import pdb;
|
||||
... instance = pdb.Pdb(nosigint=True, readrc=False)
|
||||
... try:
|
||||
... main()
|
||||
... except Exception as e:
|
||||
... # same as pdb.post_mortem(e), but with custom pdb instance.
|
||||
... instance.reset()
|
||||
... instance.interaction(None, e)
|
||||
|
||||
>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
||||
... 'exceptions',
|
||||
... 'exceptions 1',
|
||||
... 'up',
|
||||
... 'down',
|
||||
... 'exit',
|
||||
... ]):
|
||||
... try:
|
||||
... test_function()
|
||||
... except ValueError:
|
||||
... print('Ok.')
|
||||
> <doctest test.test_pdb.test_post_mortem_cause_no_context[0]>(5)main()
|
||||
-> raise ValueError("With Cause") from TypeError('The Cause')
|
||||
(Pdb) exceptions
|
||||
0 TypeError('The Cause')
|
||||
> 1 ValueError('With Cause')
|
||||
(Pdb) exceptions 1
|
||||
> <doctest test.test_pdb.test_post_mortem_cause_no_context[0]>(5)main()
|
||||
-> raise ValueError("With Cause") from TypeError('The Cause')
|
||||
(Pdb) up
|
||||
> <doctest test.test_pdb.test_post_mortem_cause_no_context[1]>(5)test_function()
|
||||
-> main()
|
||||
(Pdb) down
|
||||
> <doctest test.test_pdb.test_post_mortem_cause_no_context[0]>(5)main()
|
||||
-> raise ValueError("With Cause") from TypeError('The Cause')
|
||||
(Pdb) exit"""
|
||||
|
||||
|
||||
def test_post_mortem_context_of_the_cause():
|
||||
"""Test post mortem traceback debugging of chained exception
|
||||
|
||||
|
||||
>>> def main():
|
||||
... try:
|
||||
... raise TypeError('Context of the cause')
|
||||
... except Exception as e1:
|
||||
... try:
|
||||
... raise ValueError('Root Cause')
|
||||
... except Exception as e2:
|
||||
... ex = e2
|
||||
... raise ValueError("With Cause, and cause has context") from ex
|
||||
|
||||
>>> def test_function():
|
||||
... import pdb;
|
||||
... instance = pdb.Pdb(nosigint=True, readrc=False)
|
||||
... try:
|
||||
... main()
|
||||
... except Exception as e:
|
||||
... # same as pdb.post_mortem(e), but with custom pdb instance.
|
||||
... instance.reset()
|
||||
... instance.interaction(None, e)
|
||||
|
||||
>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
||||
... 'exceptions',
|
||||
... 'exceptions 2',
|
||||
... 'up',
|
||||
... 'down',
|
||||
... 'exceptions 3',
|
||||
... 'up',
|
||||
... 'down',
|
||||
... 'exceptions 4',
|
||||
... 'up',
|
||||
... 'down',
|
||||
... 'exit',
|
||||
... ]):
|
||||
... try:
|
||||
... test_function()
|
||||
... except ValueError:
|
||||
... print('Correctly reraised.')
|
||||
> <doctest test.test_pdb.test_post_mortem_context_of_the_cause[0]>(9)main()
|
||||
-> raise ValueError("With Cause, and cause has context") from ex
|
||||
(Pdb) exceptions
|
||||
0 TypeError('Context of the cause')
|
||||
1 ValueError('Root Cause')
|
||||
> 2 ValueError('With Cause, and cause has context')
|
||||
(Pdb) exceptions 2
|
||||
> <doctest test.test_pdb.test_post_mortem_context_of_the_cause[0]>(9)main()
|
||||
-> raise ValueError("With Cause, and cause has context") from ex
|
||||
(Pdb) up
|
||||
> <doctest test.test_pdb.test_post_mortem_context_of_the_cause[1]>(5)test_function()
|
||||
-> main()
|
||||
(Pdb) down
|
||||
> <doctest test.test_pdb.test_post_mortem_context_of_the_cause[0]>(9)main()
|
||||
-> raise ValueError("With Cause, and cause has context") from ex
|
||||
(Pdb) exceptions 3
|
||||
*** No exception with that number
|
||||
(Pdb) up
|
||||
> <doctest test.test_pdb.test_post_mortem_context_of_the_cause[1]>(5)test_function()
|
||||
-> main()
|
||||
(Pdb) down
|
||||
> <doctest test.test_pdb.test_post_mortem_context_of_the_cause[0]>(9)main()
|
||||
-> raise ValueError("With Cause, and cause has context") from ex
|
||||
(Pdb) exceptions 4
|
||||
*** No exception with that number
|
||||
(Pdb) up
|
||||
> <doctest test.test_pdb.test_post_mortem_context_of_the_cause[1]>(5)test_function()
|
||||
-> main()
|
||||
(Pdb) down
|
||||
> <doctest test.test_pdb.test_post_mortem_context_of_the_cause[0]>(9)main()
|
||||
-> raise ValueError("With Cause, and cause has context") from ex
|
||||
(Pdb) exit
|
||||
"""
|
||||
|
||||
|
||||
def test_post_mortem_from_none():
|
||||
"""Test post mortem traceback debugging of chained exception
|
||||
|
||||
In particular that cause from None (which sets __supress_context__ to True)
|
||||
does not show context.
|
||||
|
||||
|
||||
>>> def main():
|
||||
... try:
|
||||
... raise TypeError('Context of the cause')
|
||||
... except Exception as e1:
|
||||
... raise ValueError("With Cause, and cause has context") from None
|
||||
|
||||
>>> def test_function():
|
||||
... import pdb;
|
||||
... instance = pdb.Pdb(nosigint=True, readrc=False)
|
||||
... try:
|
||||
... main()
|
||||
... except Exception as e:
|
||||
... # same as pdb.post_mortem(e), but with custom pdb instance.
|
||||
... instance.reset()
|
||||
... instance.interaction(None, e)
|
||||
|
||||
>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
||||
... 'exceptions',
|
||||
... 'exit',
|
||||
... ]):
|
||||
... try:
|
||||
... test_function()
|
||||
... except ValueError:
|
||||
... print('Correctly reraised.')
|
||||
> <doctest test.test_pdb.test_post_mortem_from_none[0]>(5)main()
|
||||
-> raise ValueError("With Cause, and cause has context") from None
|
||||
(Pdb) exceptions
|
||||
> 0 ValueError('With Cause, and cause has context')
|
||||
(Pdb) exit
|
||||
"""
|
||||
|
||||
|
||||
def test_post_mortem_complex():
|
||||
"""Test post mortem traceback debugging of chained exception
|
||||
|
||||
Test with simple and complex cycles, exception groups,...
|
||||
|
||||
>>> def make_ex_with_stack(type_, *content, from_=None):
|
||||
... try:
|
||||
... raise type_(*content) from from_
|
||||
... except Exception as out:
|
||||
... return out
|
||||
...
|
||||
|
||||
>>> def cycle():
|
||||
... try:
|
||||
... raise ValueError("Cycle Leaf")
|
||||
... except Exception as e:
|
||||
... raise e from e
|
||||
...
|
||||
|
||||
>>> def tri_cycle():
|
||||
... a = make_ex_with_stack(ValueError, "Cycle1")
|
||||
... b = make_ex_with_stack(ValueError, "Cycle2")
|
||||
... c = make_ex_with_stack(ValueError, "Cycle3")
|
||||
...
|
||||
... a.__cause__ = b
|
||||
... b.__cause__ = c
|
||||
...
|
||||
... raise c from a
|
||||
...
|
||||
|
||||
>>> def cause():
|
||||
... try:
|
||||
... raise ValueError("Cause Leaf")
|
||||
... except Exception as e:
|
||||
... raise e
|
||||
...
|
||||
|
||||
>>> def context(n=10):
|
||||
... try:
|
||||
... raise ValueError(f"Context Leaf {n}")
|
||||
... except Exception as e:
|
||||
... if n == 0:
|
||||
... raise ValueError(f"With Context {n}") from e
|
||||
... else:
|
||||
... context(n - 1)
|
||||
...
|
||||
|
||||
>>> def main():
|
||||
... try:
|
||||
... cycle()
|
||||
... except Exception as e1:
|
||||
... try:
|
||||
... tri_cycle()
|
||||
... except Exception as e2:
|
||||
... ex = e2
|
||||
... raise ValueError("With Context and With Cause") from ex
|
||||
|
||||
|
||||
>>> def test_function():
|
||||
... import pdb;
|
||||
... instance = pdb.Pdb(nosigint=True, readrc=False)
|
||||
... try:
|
||||
... main()
|
||||
... except Exception as e:
|
||||
... # same as pdb.post_mortem(e), but with custom pdb instance.
|
||||
... instance.reset()
|
||||
... instance.interaction(None, e)
|
||||
|
||||
>>> with PdbTestInput( # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
||||
... ["exceptions",
|
||||
... "exceptions 0",
|
||||
... "exceptions 1",
|
||||
... "exceptions 2",
|
||||
... "exceptions 3",
|
||||
... "exit"],
|
||||
... ):
|
||||
... try:
|
||||
... test_function()
|
||||
... except ValueError:
|
||||
... print('Correctly reraised.')
|
||||
> <doctest test.test_pdb.test_post_mortem_complex[5]>(9)main()
|
||||
-> raise ValueError("With Context and With Cause") from ex
|
||||
(Pdb) exceptions
|
||||
0 ValueError('Cycle2')
|
||||
1 ValueError('Cycle1')
|
||||
2 ValueError('Cycle3')
|
||||
> 3 ValueError('With Context and With Cause')
|
||||
(Pdb) exceptions 0
|
||||
> <doctest test.test_pdb.test_post_mortem_complex[0]>(3)make_ex_with_stack()
|
||||
-> raise type_(*content) from from_
|
||||
(Pdb) exceptions 1
|
||||
> <doctest test.test_pdb.test_post_mortem_complex[0]>(3)make_ex_with_stack()
|
||||
-> raise type_(*content) from from_
|
||||
(Pdb) exceptions 2
|
||||
> <doctest test.test_pdb.test_post_mortem_complex[0]>(3)make_ex_with_stack()
|
||||
-> raise type_(*content) from from_
|
||||
(Pdb) exceptions 3
|
||||
> <doctest test.test_pdb.test_post_mortem_complex[5]>(9)main()
|
||||
-> raise ValueError("With Context and With Cause") from ex
|
||||
(Pdb) exit
|
||||
"""
|
||||
|
||||
|
||||
def test_post_mortem():
|
||||
"""Test post mortem traceback debugging.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Add the new ``exceptions`` command to the Pdb debugger. It makes it possible to move between chained exceptions when using post mortem debugging.
|
||||
Loading…
Add table
Add a link
Reference in a new issue