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:
parent
cdf66a9a7c
commit
0a9c3e91dc
@ -368,9 +368,18 @@ by the local file.
|
|||||||
list 11 lines around at that line. With two arguments, list the given range;
|
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.
|
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
|
.. 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
|
.. versionadded:: 3.2
|
||||||
|
|
||||||
|
68
Lib/pdb.py
68
Lib/pdb.py
@ -70,8 +70,10 @@ import sys
|
|||||||
import linecache
|
import linecache
|
||||||
import cmd
|
import cmd
|
||||||
import bdb
|
import bdb
|
||||||
|
import dis
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import code
|
||||||
import pprint
|
import pprint
|
||||||
import traceback
|
import traceback
|
||||||
import inspect
|
import inspect
|
||||||
@ -107,14 +109,22 @@ def find_function(funcname, filename):
|
|||||||
|
|
||||||
def getsourcelines(obj):
|
def getsourcelines(obj):
|
||||||
lines, lineno = inspect.findsource(obj)
|
lines, lineno = inspect.findsource(obj)
|
||||||
if inspect.isframe(obj) and lineno == 0 and \
|
if inspect.isframe(obj) and obj.f_globals is obj.f_locals:
|
||||||
obj.f_globals is obj.f_locals:
|
|
||||||
# must be a module frame: do not try to cut a block out of it
|
# 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):
|
elif inspect.ismodule(obj):
|
||||||
return lines, 0
|
return lines, 1
|
||||||
return inspect.getblock(lines[lineno:]), lineno+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
|
# Interaction prompt line will separate file and call info from code
|
||||||
# text using value of line_prefix string. A newline and arrow may
|
# 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
|
# 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.aliases = {}
|
||||||
self.mainpyfile = ''
|
self.mainpyfile = ''
|
||||||
self._wait_for_mainpyfile = 0
|
self._wait_for_mainpyfile = 0
|
||||||
|
self.tb_lineno = {}
|
||||||
# Try to load readline if it exists
|
# Try to load readline if it exists
|
||||||
try:
|
try:
|
||||||
import readline
|
import readline
|
||||||
@ -179,10 +190,18 @@ class Pdb(bdb.Bdb, cmd.Cmd):
|
|||||||
self.stack = []
|
self.stack = []
|
||||||
self.curindex = 0
|
self.curindex = 0
|
||||||
self.curframe = None
|
self.curframe = None
|
||||||
|
self.tb_lineno.clear()
|
||||||
|
|
||||||
def setup(self, f, t):
|
def setup(self, f, tb):
|
||||||
self.forget()
|
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]
|
self.curframe = self.stack[self.curindex][0]
|
||||||
# The f_locals dictionary is updated from the actual frame
|
# The f_locals dictionary is updated from the actual frame
|
||||||
# locals whenever the .f_locals accessor is called, so we
|
# 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):
|
def do_list(self, arg):
|
||||||
"""l(ist) [first [,last] | .]
|
"""l(ist) [first [,last] | .]
|
||||||
List source code for the current file.
|
|
||||||
Without arguments, list 11 lines around the current line
|
List source code for the current file. Without arguments,
|
||||||
or continue the previous listing.
|
list 11 lines around the current line or continue the previous
|
||||||
With . as argument, list 11 lines around the current line.
|
listing. With . as argument, list 11 lines around the current
|
||||||
With one argument, list 11 lines starting at that line.
|
line. With one argument, list 11 lines starting at that line.
|
||||||
With two arguments, list the given range;
|
With two arguments, list the given range; if the second
|
||||||
if the second argument is less than the first, it is a count.
|
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'
|
self.lastcmd = 'list'
|
||||||
last = None
|
last = None
|
||||||
@ -1039,10 +1063,9 @@ class Pdb(bdb.Bdb, cmd.Cmd):
|
|||||||
filename = self.curframe.f_code.co_filename
|
filename = self.curframe.f_code.co_filename
|
||||||
breaklist = self.get_file_breaks(filename)
|
breaklist = self.get_file_breaks(filename)
|
||||||
try:
|
try:
|
||||||
# XXX add tb_lineno feature
|
|
||||||
lines = linecache.getlines(filename, self.curframe.f_globals)
|
lines = linecache.getlines(filename, self.curframe.f_globals)
|
||||||
self._print_lines(lines[first-1:last], first, breaklist,
|
self._print_lines(lines[first-1:last], first, breaklist,
|
||||||
self.curframe.f_lineno, -1)
|
self.curframe)
|
||||||
self.lineno = min(last, len(lines))
|
self.lineno = min(last, len(lines))
|
||||||
if len(lines) < last:
|
if len(lines) < last:
|
||||||
self.message('[EOF]')
|
self.message('[EOF]')
|
||||||
@ -1061,7 +1084,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
|
|||||||
except IOError as err:
|
except IOError as err:
|
||||||
self.error(err)
|
self.error(err)
|
||||||
return
|
return
|
||||||
self._print_lines(lines, lineno, breaklist, self.curframe.f_lineno, -1)
|
self._print_lines(lines, lineno, breaklist, self.curframe)
|
||||||
do_ll = do_longlist
|
do_ll = do_longlist
|
||||||
|
|
||||||
def do_source(self, arg):
|
def do_source(self, arg):
|
||||||
@ -1077,10 +1100,15 @@ class Pdb(bdb.Bdb, cmd.Cmd):
|
|||||||
except (IOError, TypeError) as err:
|
except (IOError, TypeError) as err:
|
||||||
self.error(err)
|
self.error(err)
|
||||||
return
|
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."""
|
"""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):
|
for lineno, line in enumerate(lines, start):
|
||||||
s = str(lineno).rjust(3)
|
s = str(lineno).rjust(3)
|
||||||
if len(s) < 4:
|
if len(s) < 4:
|
||||||
@ -1089,9 +1117,9 @@ class Pdb(bdb.Bdb, cmd.Cmd):
|
|||||||
s += 'B'
|
s += 'B'
|
||||||
else:
|
else:
|
||||||
s += ' '
|
s += ' '
|
||||||
if lineno == current:
|
if lineno == current_lineno:
|
||||||
s += '->'
|
s += '->'
|
||||||
elif lineno == special:
|
elif lineno == exc_lineno:
|
||||||
s += '>>'
|
s += '>>'
|
||||||
self.message(s + '\t' + line.rstrip())
|
self.message(s + '\t' + line.rstrip())
|
||||||
|
|
||||||
|
@ -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():
|
def test_pdb_skip_modules():
|
||||||
"""This illustrates the simple case of module skipping.
|
"""This illustrates the simple case of module skipping.
|
||||||
|
|
||||||
|
@ -475,6 +475,10 @@ C-API
|
|||||||
Library
|
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
|
- The pdb command "source" has been added. It displays the source
|
||||||
code for a given object, if possible.
|
code for a given object, if possible.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user