#809887: improve pdb feedback for breakpoint-related actions. Also add a functional test for these commands.
This commit is contained in:
parent
a074523f3a
commit
7410dd11ef
|
@ -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*.
|
||||||
|
|
30
Lib/bdb.py
30
Lib/bdb.py
|
@ -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):
|
||||||
|
|
117
Lib/pdb.py
117
Lib/pdb.py
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue