#809887: improve pdb feedback for breakpoint-related actions. Also add a functional test for these commands.
diff --git a/Doc/library/bdb.rst b/Doc/library/bdb.rst
index 3d7e41b..c35e813 100644
--- a/Doc/library/bdb.rst
+++ b/Doc/library/bdb.rst
@@ -267,6 +267,15 @@
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)
Check if there is a breakpoint for *lineno* of *filename*.
diff --git a/Lib/bdb.py b/Lib/bdb.py
index 460f0d4..ffeca72 100644
--- a/Lib/bdb.py
+++ b/Lib/bdb.py
@@ -270,15 +270,9 @@
def clear_bpbynumber(self, arg):
try:
- number = int(arg)
- except:
- return 'Non-numeric breakpoint number (%s)' % arg
- try:
- bp = Breakpoint.bpbynumber[number]
- except IndexError:
- return 'Breakpoint number (%d) out of range' % number
- if not bp:
- return 'Breakpoint (%d) already deleted' % number
+ bp = self.get_bpbynumber(arg)
+ except ValueError as err:
+ return str(err)
self.clear_break(bp.file, bp.line)
def clear_all_file_breaks(self, filename):
@@ -299,6 +293,21 @@
bp.deleteMe()
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):
filename = self.canonic(filename)
return filename in self.breaks and \
@@ -510,6 +519,9 @@
print(('\tbreakpoint already hit %d time%s' %
(self.hits, ss)), file=out)
+ def __str__(self):
+ return 'breakpoint %s at %s:%s' % (self.number, self.file, self.line)
+
# -----------end of Breakpoint class----------
def checkfuncname(b, frame):
diff --git a/Lib/pdb.py b/Lib/pdb.py
index d80a29b..1e8821a 100755
--- a/Lib/pdb.py
+++ b/Lib/pdb.py
@@ -552,12 +552,12 @@
Those commands will be executed whenever the breakpoint causes
the program to stop execution."""
if not arg:
- bnum = len(bdb.Breakpoint.bpbynumber)-1
+ bnum = len(bdb.Breakpoint.bpbynumber) - 1
else:
try:
bnum = int(arg)
except:
- print("Usage : commands [bnum]\n ...\n end",
+ print("Usage: commands [bnum]\n ...\n end",
file=self.stdout)
return
self.commands_bnum = bnum
@@ -634,10 +634,9 @@
# last thing to try
(ok, filename, ln) = self.lineinfo(arg)
if not ok:
- print('*** The specified object', end=' ', file=self.stdout)
- print(repr(arg), end=' ', file=self.stdout)
- print('is not a function', file=self.stdout)
- print('or was not found along sys.path.', file=self.stdout)
+ print('*** The specified object %r is not a function '
+ 'or was not found along sys.path.' % arg,
+ file=self.stdout)
return
funcname = ok # ok contains a function name
lineno = int(ln)
@@ -726,81 +725,56 @@
args = arg.split()
for i in args:
try:
- i = int(i)
- except ValueError:
- 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
-
- bp = bdb.Breakpoint.bpbynumber[i]
- if bp:
+ bp = self.get_bpbynumber(i)
+ except ValueError as err:
+ print('***', err, file=self.stdout)
+ else:
bp.enable()
+ print('Enabled %s' % bp, file=self.stdout)
def do_disable(self, arg):
args = arg.split()
for i in args:
try:
- i = int(i)
- except ValueError:
- 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
-
- bp = bdb.Breakpoint.bpbynumber[i]
- if bp:
+ bp = self.get_bpbynumber(i)
+ except ValueError as err:
+ print('***', err, file=self.stdout)
+ else:
bp.disable()
+ print('Disabled %s' % bp, file=self.stdout)
def do_condition(self, arg):
# arg is breakpoint number and condition
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:
cond = args[1]
- except:
+ except IndexError:
cond = None
try:
- bp = bdb.Breakpoint.bpbynumber[bpnum]
- except IndexError:
- print('Breakpoint index %r is not valid' % args[0],
- file=self.stdout)
- return
- if bp:
+ bp = self.get_bpbynumber(args[0].strip())
+ except ValueError as err:
+ print('***', err, file=self.stdout)
+ else:
bp.cond = cond
if not cond:
- print('Breakpoint', bpnum, end=' ', file=self.stdout)
- print('is now unconditional.', file=self.stdout)
+ print('Breakpoint %d is now unconditional.' % bp.number,
+ 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."""
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:
count = int(args[1].strip())
except:
count = 0
try:
- bp = bdb.Breakpoint.bpbynumber[bpnum]
- except IndexError:
- print('Breakpoint index %r is not valid' % args[0],
- file=self.stdout)
- return
- if bp:
+ bp = self.get_bpbynumber(args[0].strip())
+ except ValueError as err:
+ print('***', err, file=self.stdout)
+ else:
bp.ignore = count
if count > 0:
reply = 'Will ignore next '
@@ -808,10 +782,10 @@
reply = reply + '%d crossings' % count
else:
reply = reply + '1 crossing'
- print(reply + ' of breakpoint %d.' % bpnum, file=self.stdout)
+ print(reply + ' of breakpoint %d.' % bp.number, file=self.stdout)
else:
- print('Will stop next time breakpoint', end=' ', file=self.stdout)
- print(bpnum, 'is reached.', file=self.stdout)
+ print('Will stop next time breakpoint %d is reached.'
+ % bp.number, file=self.stdout)
def do_clear(self, arg):
"""Three possibilities, tried in this order:
@@ -825,7 +799,10 @@
reply = 'no'
reply = reply.strip().lower()
if reply in ('y', 'yes'):
+ bplist = [bp for bp in bdb.Breakpoint.bpbynumber if bp]
self.clear_all_breaks()
+ for bp in bplist:
+ print('Deleted %s' % bp, file=self.stdout)
return
if ':' in arg:
# Make sure it works for "clear C:\foo\bar.py:12"
@@ -837,25 +814,23 @@
except ValueError:
err = "Invalid line number (%s)" % arg
else:
+ bplist = self.get_breaks(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
numberlist = arg.split()
for i in numberlist:
try:
- i = int(i)
- except ValueError:
- 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:
+ bp = self.get_bpbynumber(i)
+ except ValueError as err:
print('***', err, file=self.stdout)
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'
def do_where(self, arg):
diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py
index 00ff4b7..1e3cecd 100644
--- a/Lib/test/test_pdb.py
+++ b/Lib/test/test_pdb.py
@@ -134,7 +134,7 @@
... print(3)
... print(4)
- >>> with PdbTestInput([
+ >>> with PdbTestInput([ # doctest: +ELLIPSIS
... 'next',
... 'break 7',
... 'continue',
@@ -149,7 +149,7 @@
> <doctest test.test_pdb.test_pdb_continue_in_bottomframe[0]>(5)test_function()
-> print(1)
(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
1
2
@@ -164,6 +164,84 @@
"""
+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):
"""Run pdb.method(arg)."""
import pdb; getattr(pdb, method)(arg)
diff --git a/Misc/NEWS b/Misc/NEWS
index dbe1b8a..65346fc 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -475,6 +475,9 @@
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
in the top-level debugged frame.