#809887: improve pdb feedback for breakpoint-related actions. Also add a functional test for these commands.

This commit is contained in:
Georg Brandl 2010-07-30 12:01:20 +00:00
parent a074523f3a
commit 7410dd11ef
5 changed files with 159 additions and 82 deletions

View File

@ -267,6 +267,15 @@ The :mod:`bdb` module also defines two classes:
Delete all existing breakpoints. Delete all existing breakpoints.
.. method:: get_bpbynumber(arg)
Return a breakpoint specified by the given number. If *arg* is a string,
it will be converted to a number. If *arg* is a non-numeric string, if
the given breakpoint never existed or has been deleted, a
:exc:`ValueError` is raised.
.. versionadded:: 3.2
.. method:: get_break(filename, lineno) .. method:: get_break(filename, lineno)
Check if there is a breakpoint for *lineno* of *filename*. Check if there is a breakpoint for *lineno* of *filename*.

View File

@ -270,15 +270,9 @@ class Bdb:
def clear_bpbynumber(self, arg): def clear_bpbynumber(self, arg):
try: try:
number = int(arg) bp = self.get_bpbynumber(arg)
except: except ValueError as err:
return 'Non-numeric breakpoint number (%s)' % arg return str(err)
try:
bp = Breakpoint.bpbynumber[number]
except IndexError:
return 'Breakpoint number (%d) out of range' % number
if not bp:
return 'Breakpoint (%d) already deleted' % number
self.clear_break(bp.file, bp.line) self.clear_break(bp.file, bp.line)
def clear_all_file_breaks(self, filename): def clear_all_file_breaks(self, filename):
@ -299,6 +293,21 @@ class Bdb:
bp.deleteMe() bp.deleteMe()
self.breaks = {} self.breaks = {}
def get_bpbynumber(self, arg):
if not arg:
raise ValueError('Breakpoint number expected')
try:
number = int(arg)
except ValueError:
raise ValueError('Non-numeric breakpoint number %s' % arg)
try:
bp = Breakpoint.bpbynumber[number]
except IndexError:
raise ValueError('Breakpoint number %d out of range' % number)
if bp is None:
raise ValueError('Breakpoint %d already deleted' % number)
return bp
def get_break(self, filename, lineno): def get_break(self, filename, lineno):
filename = self.canonic(filename) filename = self.canonic(filename)
return filename in self.breaks and \ return filename in self.breaks and \
@ -510,6 +519,9 @@ class Breakpoint:
print(('\tbreakpoint already hit %d time%s' % print(('\tbreakpoint already hit %d time%s' %
(self.hits, ss)), file=out) (self.hits, ss)), file=out)
def __str__(self):
return 'breakpoint %s at %s:%s' % (self.number, self.file, self.line)
# -----------end of Breakpoint class---------- # -----------end of Breakpoint class----------
def checkfuncname(b, frame): def checkfuncname(b, frame):

View File

@ -552,12 +552,12 @@ class Pdb(bdb.Bdb, cmd.Cmd):
Those commands will be executed whenever the breakpoint causes Those commands will be executed whenever the breakpoint causes
the program to stop execution.""" the program to stop execution."""
if not arg: if not arg:
bnum = len(bdb.Breakpoint.bpbynumber)-1 bnum = len(bdb.Breakpoint.bpbynumber) - 1
else: else:
try: try:
bnum = int(arg) bnum = int(arg)
except: except:
print("Usage : commands [bnum]\n ...\n end", print("Usage: commands [bnum]\n ...\n end",
file=self.stdout) file=self.stdout)
return return
self.commands_bnum = bnum self.commands_bnum = bnum
@ -634,10 +634,9 @@ class Pdb(bdb.Bdb, cmd.Cmd):
# last thing to try # last thing to try
(ok, filename, ln) = self.lineinfo(arg) (ok, filename, ln) = self.lineinfo(arg)
if not ok: if not ok:
print('*** The specified object', end=' ', file=self.stdout) print('*** The specified object %r is not a function '
print(repr(arg), end=' ', file=self.stdout) 'or was not found along sys.path.' % arg,
print('is not a function', file=self.stdout) file=self.stdout)
print('or was not found along sys.path.', file=self.stdout)
return return
funcname = ok # ok contains a function name funcname = ok # ok contains a function name
lineno = int(ln) lineno = int(ln)
@ -726,81 +725,56 @@ class Pdb(bdb.Bdb, cmd.Cmd):
args = arg.split() args = arg.split()
for i in args: for i in args:
try: try:
i = int(i) bp = self.get_bpbynumber(i)
except ValueError: except ValueError as err:
print('Breakpoint index %r is not a number' % i, file=self.stdout) print('***', err, file=self.stdout)
continue else:
if not (0 <= i < len(bdb.Breakpoint.bpbynumber)):
print('No breakpoint numbered', i, file=self.stdout)
continue
bp = bdb.Breakpoint.bpbynumber[i]
if bp:
bp.enable() bp.enable()
print('Enabled %s' % bp, file=self.stdout)
def do_disable(self, arg): def do_disable(self, arg):
args = arg.split() args = arg.split()
for i in args: for i in args:
try: try:
i = int(i) bp = self.get_bpbynumber(i)
except ValueError: except ValueError as err:
print('Breakpoint index %r is not a number' % i, file=self.stdout) print('***', err, file=self.stdout)
continue else:
if not (0 <= i < len(bdb.Breakpoint.bpbynumber)):
print('No breakpoint numbered', i, file=self.stdout)
continue
bp = bdb.Breakpoint.bpbynumber[i]
if bp:
bp.disable() bp.disable()
print('Disabled %s' % bp, file=self.stdout)
def do_condition(self, arg): def do_condition(self, arg):
# arg is breakpoint number and condition # arg is breakpoint number and condition
args = arg.split(' ', 1) args = arg.split(' ', 1)
try:
bpnum = int(args[0].strip())
except ValueError:
# something went wrong
print('Breakpoint index %r is not a number' % args[0], file=self.stdout)
return
try: try:
cond = args[1] cond = args[1]
except: except IndexError:
cond = None cond = None
try: try:
bp = bdb.Breakpoint.bpbynumber[bpnum] bp = self.get_bpbynumber(args[0].strip())
except IndexError: except ValueError as err:
print('Breakpoint index %r is not valid' % args[0], print('***', err, file=self.stdout)
file=self.stdout) else:
return
if bp:
bp.cond = cond bp.cond = cond
if not cond: if not cond:
print('Breakpoint', bpnum, end=' ', file=self.stdout) print('Breakpoint %d is now unconditional.' % bp.number,
print('is now unconditional.', file=self.stdout) file=self.stdout)
else:
print('New condition set for breakpoint %d.' % bp.number,
file=self.stdout)
def do_ignore(self,arg): def do_ignore(self, arg):
"""arg is bp number followed by ignore count.""" """arg is bp number followed by ignore count."""
args = arg.split() args = arg.split()
try:
bpnum = int(args[0].strip())
except ValueError:
# something went wrong
print('Breakpoint index %r is not a number' % args[0], file=self.stdout)
return
try: try:
count = int(args[1].strip()) count = int(args[1].strip())
except: except:
count = 0 count = 0
try: try:
bp = bdb.Breakpoint.bpbynumber[bpnum] bp = self.get_bpbynumber(args[0].strip())
except IndexError: except ValueError as err:
print('Breakpoint index %r is not valid' % args[0], print('***', err, file=self.stdout)
file=self.stdout) else:
return
if bp:
bp.ignore = count bp.ignore = count
if count > 0: if count > 0:
reply = 'Will ignore next ' reply = 'Will ignore next '
@ -808,10 +782,10 @@ class Pdb(bdb.Bdb, cmd.Cmd):
reply = reply + '%d crossings' % count reply = reply + '%d crossings' % count
else: else:
reply = reply + '1 crossing' reply = reply + '1 crossing'
print(reply + ' of breakpoint %d.' % bpnum, file=self.stdout) print(reply + ' of breakpoint %d.' % bp.number, file=self.stdout)
else: else:
print('Will stop next time breakpoint', end=' ', file=self.stdout) print('Will stop next time breakpoint %d is reached.'
print(bpnum, 'is reached.', file=self.stdout) % bp.number, file=self.stdout)
def do_clear(self, arg): def do_clear(self, arg):
"""Three possibilities, tried in this order: """Three possibilities, tried in this order:
@ -825,7 +799,10 @@ class Pdb(bdb.Bdb, cmd.Cmd):
reply = 'no' reply = 'no'
reply = reply.strip().lower() reply = reply.strip().lower()
if reply in ('y', 'yes'): if reply in ('y', 'yes'):
bplist = [bp for bp in bdb.Breakpoint.bpbynumber if bp]
self.clear_all_breaks() self.clear_all_breaks()
for bp in bplist:
print('Deleted %s' % bp, file=self.stdout)
return return
if ':' in arg: if ':' in arg:
# Make sure it works for "clear C:\foo\bar.py:12" # Make sure it works for "clear C:\foo\bar.py:12"
@ -837,25 +814,23 @@ class Pdb(bdb.Bdb, cmd.Cmd):
except ValueError: except ValueError:
err = "Invalid line number (%s)" % arg err = "Invalid line number (%s)" % arg
else: else:
bplist = self.get_breaks(filename, lineno)
err = self.clear_break(filename, lineno) err = self.clear_break(filename, lineno)
if err: print('***', err, file=self.stdout) if err:
print('***', err, file=self.stdout)
else:
for bp in bplist:
print('Deleted %s' % bp, file=self.stdout)
return return
numberlist = arg.split() numberlist = arg.split()
for i in numberlist: for i in numberlist:
try: try:
i = int(i) bp = self.get_bpbynumber(i)
except ValueError: except ValueError as err:
print('Breakpoint index %r is not a number' % i, file=self.stdout)
continue
if not (0 <= i < len(bdb.Breakpoint.bpbynumber)):
print('No breakpoint numbered', i, file=self.stdout)
continue
err = self.clear_bpbynumber(i)
if err:
print('***', err, file=self.stdout) print('***', err, file=self.stdout)
else: else:
print('Deleted breakpoint', i, file=self.stdout) self.clear_break(bp.file, bp.line)
print('Deleted %s' % bp, file=self.stdout)
do_cl = do_clear # 'c' is already an abbreviation for 'continue' do_cl = do_clear # 'c' is already an abbreviation for 'continue'
def do_where(self, arg): def do_where(self, arg):

View File

@ -134,7 +134,7 @@ def test_pdb_continue_in_bottomframe():
... print(3) ... print(3)
... print(4) ... print(4)
>>> with PdbTestInput([ >>> with PdbTestInput([ # doctest: +ELLIPSIS
... 'next', ... 'next',
... 'break 7', ... 'break 7',
... 'continue', ... 'continue',
@ -149,7 +149,7 @@ def test_pdb_continue_in_bottomframe():
> <doctest test.test_pdb.test_pdb_continue_in_bottomframe[0]>(5)test_function() > <doctest test.test_pdb.test_pdb_continue_in_bottomframe[0]>(5)test_function()
-> print(1) -> print(1)
(Pdb) break 7 (Pdb) break 7
Breakpoint 1 at <doctest test.test_pdb.test_pdb_continue_in_bottomframe[0]>:7 Breakpoint ... at <doctest test.test_pdb.test_pdb_continue_in_bottomframe[0]>:7
(Pdb) continue (Pdb) continue
1 1
2 2
@ -164,6 +164,84 @@ def test_pdb_continue_in_bottomframe():
""" """
def test_pdb_breakpoints():
"""Test handling of breakpoints.
>>> def test_function():
... import pdb; pdb.Pdb().set_trace()
... print(1)
... print(2)
... print(3)
... print(4)
First, need to clear bdb state that might be left over from previous tests.
Otherwise, the new breakpoints might get assigned different numbers.
>>> from bdb import Breakpoint
>>> Breakpoint.next = 1
>>> Breakpoint.bplist = {}
>>> Breakpoint.bpbynumber = [None]
Now test the breakpoint commands. NORMALIZE_WHITESPACE is needed because
the breakpoint list outputs a tab for the "stop only" and "ignore next"
lines, which we don't want to put in here.
>>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE
... 'break 3',
... 'disable 1',
... 'ignore 1 10',
... 'condition 1 1 < 2',
... 'break 4',
... 'break',
... 'condition 1',
... 'enable 1',
... 'clear 1',
... 'commands 2',
... 'print 42',
... 'end',
... 'continue', # will stop at breakpoint 2
... 'continue',
... ]):
... test_function()
> <doctest test.test_pdb.test_pdb_breakpoints[0]>(3)test_function()
-> print(1)
(Pdb) break 3
Breakpoint 1 at <doctest test.test_pdb.test_pdb_breakpoints[0]>:3
(Pdb) disable 1
Disabled breakpoint 1 at <doctest test.test_pdb.test_pdb_breakpoints[0]>:3
(Pdb) ignore 1 10
Will ignore next 10 crossings of breakpoint 1.
(Pdb) condition 1 1 < 2
New condition set for breakpoint 1.
(Pdb) break 4
Breakpoint 2 at <doctest test.test_pdb.test_pdb_breakpoints[0]>:4
(Pdb) break
Num Type Disp Enb Where
1 breakpoint keep no at <doctest test.test_pdb.test_pdb_breakpoints[0]>:3
stop only if 1 < 2
ignore next 10 hits
2 breakpoint keep yes at <doctest test.test_pdb.test_pdb_breakpoints[0]>:4
(Pdb) condition 1
Breakpoint 1 is now unconditional.
(Pdb) enable 1
Enabled breakpoint 1 at <doctest test.test_pdb.test_pdb_breakpoints[0]>:3
(Pdb) clear 1
Deleted breakpoint 1 at <doctest test.test_pdb.test_pdb_breakpoints[0]>:3
(Pdb) commands 2
(com) print 42
(com) end
(Pdb) continue
1
42
> <doctest test.test_pdb.test_pdb_breakpoints[0]>(4)test_function()
-> print(2)
(Pdb) continue
2
3
4
"""
def pdb_invoke(method, arg): def pdb_invoke(method, arg):
"""Run pdb.method(arg).""" """Run pdb.method(arg)."""
import pdb; getattr(pdb, method)(arg) import pdb; getattr(pdb, method)(arg)

View File

@ -475,6 +475,9 @@ C-API
Library Library
------- -------
- Issue #809887: Make the output of pdb's breakpoint deletions more
consistent; emit a message when a breakpoint is enabled or disabled.
- Issue #5294: Fix the behavior of pdb's "continue" command when called - Issue #5294: Fix the behavior of pdb's "continue" command when called
in the top-level debugged frame. in the top-level debugged frame.