diff options
-rwxr-xr-x | Lib/pdb.py | 29 | ||||
-rw-r--r-- | Lib/test/test_pdb.py | 109 |
2 files changed, 108 insertions, 30 deletions
@@ -494,6 +494,8 @@ class Pdb(bdb.Bdb, cmd.Cmd): Pdb._previous_sigint_handler = None _chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc) + if isinstance(tb_or_exc, BaseException): + assert tb is not None, "main exception must have a traceback" with self._hold_exceptions(_chained_exceptions): if self.setup(frame, tb): # no interaction desired at this time (happens if .pdbrc contains @@ -1169,7 +1171,12 @@ class Pdb(bdb.Bdb, cmd.Cmd): rep = repr(exc) if len(rep) > 80: rep = rep[:77] + "..." - self.message(f"{prompt} {ix:>3} {rep}") + indicator = ( + " -" + if self._chained_exceptions[ix].__traceback__ is None + else f"{ix:>3}" + ) + self.message(f"{prompt} {indicator} {rep}") else: try: number = int(arg) @@ -1177,6 +1184,10 @@ class Pdb(bdb.Bdb, cmd.Cmd): self.error("Argument must be an integer") return if 0 <= number < len(self._chained_exceptions): + if self._chained_exceptions[number].__traceback__ is None: + self.error("This exception does not have a traceback, cannot jump to it") + return + self._chained_exception_index = number self.setup(None, self._chained_exceptions[number].__traceback__) self.print_stack_entry(self.stack[self.curindex]) @@ -2010,19 +2021,27 @@ def post_mortem(t=None): If `t` is an exception object, the `exceptions` command makes it possible to list and inspect its chained exceptions (if any). """ + return _post_mortem(t, Pdb()) + + +def _post_mortem(t, pdb_instance): + """ + Private version of post_mortem, which allow to pass a pdb instance + for testing purposes. + """ # handling the default if t is None: exc = sys.exception() if exc is not None: t = exc.__traceback__ - if t is None: + if t is None or (isinstance(t, BaseException) and t.__traceback__ is None): raise ValueError("A valid traceback must be passed if no " "exception is being handled") - p = Pdb() - p.reset() - p.interaction(None, t) + pdb_instance.reset() + pdb_instance.interaction(None, t) + def pm(): """Enter post-mortem debugging of the traceback found in sys.last_exc.""" diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index a9edd1a..f6bed84 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -848,9 +848,7 @@ def test_post_mortem_chained(): ... 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) + ... pdb._post_mortem(e, instance) >>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE ... 'exceptions', @@ -907,11 +905,18 @@ def test_post_mortem_chained(): def test_post_mortem_cause_no_context(): """Test post mortem traceback debugging of chained exception + >>> def make_exc_with_stack(type_, *content, from_=None): + ... try: + ... raise type_(*content) from from_ + ... except Exception as out: + ... return out + ... + >>> def main(): ... try: ... raise ValueError('Context Not Shown') ... except Exception as e1: - ... raise ValueError("With Cause") from TypeError('The Cause') + ... raise ValueError("With Cause") from make_exc_with_stack(TypeError,'The Cause') >>> def test_function(): ... import pdb; @@ -919,12 +924,11 @@ def test_post_mortem_cause_no_context(): ... try: ... main() ... except Exception as e: - ... # same as pdb.post_mortem(e), but with custom pdb instance. - ... instance.reset() - ... instance.interaction(None, e) + ... pdb._post_mortem(e, instance) >>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE ... 'exceptions', + ... 'exceptions 0', ... 'exceptions 1', ... 'up', ... 'down', @@ -934,20 +938,23 @@ def test_post_mortem_cause_no_context(): ... 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') + > <doctest test.test_pdb.test_post_mortem_cause_no_context[1]>(5)main() + -> raise ValueError("With Cause") from make_exc_with_stack(TypeError,'The Cause') (Pdb) exceptions - 0 TypeError('The Cause') - > 1 ValueError('With Cause') + 0 TypeError('The Cause') + > 1 ValueError('With Cause') + (Pdb) exceptions 0 + > <doctest test.test_pdb.test_post_mortem_cause_no_context[0]>(3)make_exc_with_stack() + -> raise type_(*content) from from_ (Pdb) exceptions 1 - > <doctest test.test_pdb.test_post_mortem_cause_no_context[0]>(5)main() - -> raise ValueError("With Cause") from TypeError('The Cause') + > <doctest test.test_pdb.test_post_mortem_cause_no_context[1]>(5)main() + -> raise ValueError("With Cause") from make_exc_with_stack(TypeError,'The Cause') (Pdb) up - > <doctest test.test_pdb.test_post_mortem_cause_no_context[1]>(5)test_function() + > <doctest test.test_pdb.test_post_mortem_cause_no_context[2]>(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') + > <doctest test.test_pdb.test_post_mortem_cause_no_context[1]>(5)main() + -> raise ValueError("With Cause") from make_exc_with_stack(TypeError,'The Cause') (Pdb) exit""" @@ -971,9 +978,7 @@ def test_post_mortem_context_of_the_cause(): ... try: ... main() ... except Exception as e: - ... # same as pdb.post_mortem(e), but with custom pdb instance. - ... instance.reset() - ... instance.interaction(None, e) + ... pdb._post_mortem(e, instance) >>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE ... 'exceptions', @@ -1046,9 +1051,7 @@ def test_post_mortem_from_none(): ... try: ... main() ... except Exception as e: - ... # same as pdb.post_mortem(e), but with custom pdb instance. - ... instance.reset() - ... instance.interaction(None, e) + ... pdb._post_mortem(e, instance) >>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE ... 'exceptions', @@ -1066,6 +1069,64 @@ def test_post_mortem_from_none(): """ +def test_post_mortem_from_no_stack(): + """Test post mortem traceback debugging of chained exception + + especially when one exception has no stack. + + >>> def main(): + ... raise Exception() from Exception() + + + >>> def test_function(): + ... import pdb; + ... instance = pdb.Pdb(nosigint=True, readrc=False) + ... try: + ... main() + ... except Exception as e: + ... pdb._post_mortem(e, instance) + + >>> with PdbTestInput( # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE + ... ["exceptions", + ... "exceptions 0", + ... "exit"], + ... ): + ... try: + ... test_function() + ... except ValueError: + ... print('Correctly reraised.') + > <doctest test.test_pdb.test_post_mortem_from_no_stack[0]>(2)main() + -> raise Exception() from Exception() + (Pdb) exceptions + - Exception() + > 1 Exception() + (Pdb) exceptions 0 + *** This exception does not have a traceback, cannot jump to it + (Pdb) exit + """ + + +def test_post_mortem_single_no_stack(): + """Test post mortem called when origin exception has no stack + + + >>> def test_function(): + ... import pdb; + ... instance = pdb.Pdb(nosigint=True, readrc=False) + ... import sys + ... sys.last_exc = Exception() + ... pdb._post_mortem(sys.last_exc, instance) + + >>> with PdbTestInput( # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE + ... [] + ... ): + ... try: + ... test_function() + ... except ValueError as e: + ... print(e) + A valid traceback must be passed if no exception is being handled + """ + def test_post_mortem_complex(): """Test post mortem traceback debugging of chained exception @@ -1130,9 +1191,7 @@ def test_post_mortem_complex(): ... try: ... main() ... except Exception as e: - ... # same as pdb.post_mortem(e), but with custom pdb instance. - ... instance.reset() - ... instance.interaction(None, e) + ... pdb._post_mortem(e, instance) >>> with PdbTestInput( # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE ... ["exceptions", |