From 7410dd11ef4d6f6953f690d304158cd53b357cb2 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Fri, 30 Jul 2010 12:01:20 +0000 Subject: #809887: improve pdb feedback for breakpoint-related actions. Also add a functional test for these commands. --- Doc/library/bdb.rst | 9 ++++ Lib/bdb.py | 30 +++++++++---- Lib/pdb.py | 117 ++++++++++++++++++++------------------------------- Lib/test/test_pdb.py | 82 +++++++++++++++++++++++++++++++++++- Misc/NEWS | 3 ++ 5 files changed, 159 insertions(+), 82 deletions(-) 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 @@ The :mod:`bdb` module also defines two classes: 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 @@ class Bdb: 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 @@ class Bdb: 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 @@ class Breakpoint: 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 @@ class Pdb(bdb.Bdb, cmd.Cmd): 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 @@ class Pdb(bdb.Bdb, cmd.Cmd): # 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 @@ class Pdb(bdb.Bdb, cmd.Cmd): 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 @@ class Pdb(bdb.Bdb, cmd.Cmd): 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 @@ class Pdb(bdb.Bdb, cmd.Cmd): 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 @@ class Pdb(bdb.Bdb, cmd.Cmd): 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 @@ def test_pdb_continue_in_bottomframe(): ... print(3) ... print(4) - >>> with PdbTestInput([ + >>> with PdbTestInput([ # doctest: +ELLIPSIS ... 'next', ... 'break 7', ... 'continue', @@ -149,7 +149,7 @@ def test_pdb_continue_in_bottomframe(): > (5)test_function() -> print(1) (Pdb) break 7 - Breakpoint 1 at :7 + Breakpoint ... at :7 (Pdb) continue 1 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() + > (3)test_function() + -> print(1) + (Pdb) break 3 + Breakpoint 1 at :3 + (Pdb) disable 1 + Disabled breakpoint 1 at :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 :4 + (Pdb) break + Num Type Disp Enb Where + 1 breakpoint keep no at :3 + stop only if 1 < 2 + ignore next 10 hits + 2 breakpoint keep yes at :4 + (Pdb) condition 1 + Breakpoint 1 is now unconditional. + (Pdb) enable 1 + Enabled breakpoint 1 at :3 + (Pdb) clear 1 + Deleted breakpoint 1 at :3 + (Pdb) commands 2 + (com) print 42 + (com) end + (Pdb) continue + 1 + 42 + > (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 @@ C-API 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. -- cgit v0.12