Show the traceback line numbers as well as the current line numbers if an exception is being debugged. Courtesy of pdb++ by Antonio Cuni. Also document -> and >> markers for "list".

This commit is contained in:
Georg Brandl 2010-07-30 18:46:38 +00:00
parent cdf66a9a7c
commit 0a9c3e91dc
4 changed files with 124 additions and 21 deletions

View File

@ -368,9 +368,18 @@ by the local file.
list 11 lines around at that line. With two arguments, list the given range;
if the second argument is less than the first, it is interpreted as a count.
The current line in the current frame is indicated by ``->``. If an
exception is being debugged, the line where the exception was originally
raised or propagated is indicated by ``>>``, if it differs from the current
line.
.. versionadded:: 3.2
The ``>>`` marker.
.. pdbcommand:: ll | longlist
List all source code for the current function or frame.
List all source code for the current function or frame. Interesting lines
are marked as for :pdbcmd:`list`.
.. versionadded:: 3.2

View File

@ -70,8 +70,10 @@ import sys
import linecache
import cmd
import bdb
import dis
import os
import re
import code
import pprint
import traceback
import inspect
@ -107,14 +109,22 @@ def find_function(funcname, filename):
def getsourcelines(obj):
lines, lineno = inspect.findsource(obj)
if inspect.isframe(obj) and lineno == 0 and \
obj.f_globals is obj.f_locals:
if inspect.isframe(obj) and obj.f_globals is obj.f_locals:
# must be a module frame: do not try to cut a block out of it
return lines, 0
return lines, 1
elif inspect.ismodule(obj):
return lines, 0
return lines, 1
return inspect.getblock(lines[lineno:]), lineno+1
def lasti2lineno(code, lasti):
linestarts = list(dis.findlinestarts(code))
linestarts.reverse()
for i, lineno in linestarts:
if lasti >= i:
return lineno
return 0
# Interaction prompt line will separate file and call info from code
# text using value of line_prefix string. A newline and arrow may
# be to your liking. You can set it once pdb is imported using the
@ -133,6 +143,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
self.aliases = {}
self.mainpyfile = ''
self._wait_for_mainpyfile = 0
self.tb_lineno = {}
# Try to load readline if it exists
try:
import readline
@ -179,10 +190,18 @@ class Pdb(bdb.Bdb, cmd.Cmd):
self.stack = []
self.curindex = 0
self.curframe = None
self.tb_lineno.clear()
def setup(self, f, t):
def setup(self, f, tb):
self.forget()
self.stack, self.curindex = self.get_stack(f, t)
self.stack, self.curindex = self.get_stack(f, tb)
while tb:
# when setting up post-mortem debugging with a traceback, save all
# the original line numbers to be displayed along the current line
# numbers (which can be different, e.g. due to finally clauses)
lineno = lasti2lineno(tb.tb_frame.f_code, tb.tb_lasti)
self.tb_lineno[tb.tb_frame] = lineno
tb = tb.tb_next
self.curframe = self.stack[self.curindex][0]
# The f_locals dictionary is updated from the actual frame
# locals whenever the .f_locals accessor is called, so we
@ -1005,13 +1024,18 @@ class Pdb(bdb.Bdb, cmd.Cmd):
def do_list(self, arg):
"""l(ist) [first [,last] | .]
List source code for the current file.
Without arguments, list 11 lines around the current line
or continue the previous listing.
With . as argument, list 11 lines around the current line.
With one argument, list 11 lines starting at that line.
With two arguments, list the given range;
if the second argument is less than the first, it is a count.
List source code for the current file. Without arguments,
list 11 lines around the current line or continue the previous
listing. With . as argument, list 11 lines around the current
line. With one argument, list 11 lines starting at that line.
With two arguments, list the given range; if the second
argument is less than the first, it is a count.
The current line in the current frame is indicated by "->".
If an exception is being debugged, the line where the
exception was originally raised or propagated is indicated by
">>", if it differs from the current line.
"""
self.lastcmd = 'list'
last = None
@ -1039,10 +1063,9 @@ class Pdb(bdb.Bdb, cmd.Cmd):
filename = self.curframe.f_code.co_filename
breaklist = self.get_file_breaks(filename)
try:
# XXX add tb_lineno feature
lines = linecache.getlines(filename, self.curframe.f_globals)
self._print_lines(lines[first-1:last], first, breaklist,
self.curframe.f_lineno, -1)
self.curframe)
self.lineno = min(last, len(lines))
if len(lines) < last:
self.message('[EOF]')
@ -1061,7 +1084,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
except IOError as err:
self.error(err)
return
self._print_lines(lines, lineno, breaklist, self.curframe.f_lineno, -1)
self._print_lines(lines, lineno, breaklist, self.curframe)
do_ll = do_longlist
def do_source(self, arg):
@ -1077,10 +1100,15 @@ class Pdb(bdb.Bdb, cmd.Cmd):
except (IOError, TypeError) as err:
self.error(err)
return
self._print_lines(lines, lineno, [], -1, -1)
self._print_lines(lines, lineno)
def _print_lines(self, lines, start, breaks, current, special):
def _print_lines(self, lines, start, breaks=(), frame=None):
"""Print a range of lines."""
if frame:
current_lineno = frame.f_lineno
exc_lineno = self.tb_lineno.get(frame, -1)
else:
current_lineno = exc_lineno = -1
for lineno, line in enumerate(lines, start):
s = str(lineno).rjust(3)
if len(s) < 4:
@ -1089,9 +1117,9 @@ class Pdb(bdb.Bdb, cmd.Cmd):
s += 'B'
else:
s += ' '
if lineno == current:
if lineno == current_lineno:
s += '->'
elif lineno == special:
elif lineno == exc_lineno:
s += '>>'
self.message(s + '\t' + line.rstrip())

View File

@ -359,6 +359,68 @@ def test_list_commands():
"""
def test_post_mortem():
"""Test post mortem traceback debugging.
>>> def test_function_2():
... try:
... 1/0
... finally:
... print('Exception!')
>>> def test_function():
... import pdb; pdb.Pdb().set_trace()
... test_function_2()
... print('Not reached.')
>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
... 'next', # step over exception-raising call
... 'bt', # get a backtrace
... 'list', # list code of test_function()
... 'down', # step into test_function_2()
... 'list', # list code of test_function_2()
... 'continue',
... ]):
... try:
... test_function()
... except ZeroDivisionError:
... print('Correctly reraised.')
> <doctest test.test_pdb.test_post_mortem[1]>(3)test_function()
-> test_function_2()
(Pdb) next
Exception!
ZeroDivisionError: division by zero
> <doctest test.test_pdb.test_post_mortem[1]>(3)test_function()
-> test_function_2()
(Pdb) bt
...
<doctest test.test_pdb.test_post_mortem[2]>(10)<module>()
-> test_function()
> <doctest test.test_pdb.test_post_mortem[1]>(3)test_function()
-> test_function_2()
<doctest test.test_pdb.test_post_mortem[0]>(3)test_function_2()
-> 1/0
(Pdb) list
1 def test_function():
2 import pdb; pdb.Pdb().set_trace()
3 -> test_function_2()
4 print('Not reached.')
[EOF]
(Pdb) down
> <doctest test.test_pdb.test_post_mortem[0]>(3)test_function_2()
-> 1/0
(Pdb) list
1 def test_function_2():
2 try:
3 >> 1/0
4 finally:
5 -> print('Exception!')
[EOF]
(Pdb) continue
Correctly reraised.
"""
def test_pdb_skip_modules():
"""This illustrates the simple case of module skipping.

View File

@ -475,6 +475,10 @@ C-API
Library
-------
- For traceback debugging, the pdb listing now also shows the locations
where the exception was originally (re)raised, if it differs from the
last line executed (e.g. in case of finally clauses).
- The pdb command "source" has been added. It displays the source
code for a given object, if possible.