From 4d4313d59dc0020fd9cd913e020de88b98f0ba50 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 5 May 2009 08:54:11 +0000 Subject: #5142: add module skipping feature to pdb. --- Doc/library/pdb.rst | 53 +++++++++++++++++++++++++--- Lib/bdb.py | 13 ++++++- Lib/pdb.py | 4 +-- Lib/test/test_pdb.py | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Misc/NEWS | 2 ++ 5 files changed, 164 insertions(+), 7 deletions(-) create mode 100644 Lib/test/test_pdb.py diff --git a/Doc/library/pdb.rst b/Doc/library/pdb.rst index cfc219c..99dea6d 100644 --- a/Doc/library/pdb.rst +++ b/Doc/library/pdb.rst @@ -1,4 +1,3 @@ - .. _debugger: :mod:`pdb` --- The Python Debugger @@ -53,7 +52,16 @@ useful than quitting the debugger upon program's exit. .. versionadded:: 2.4 Restarting post-mortem behavior added. -Typical usage to inspect a crashed program is:: +The typical usage to break into the debugger from a running program is to +insert :: + + import pdb; pdb.set_trace() + +at the location you want to break into the debugger. You can then step through +the code following this statement, and continue running without debugger using +the ``c`` command. + +The typical usage to inspect a crashed program is:: >>> import pdb >>> import mymodule @@ -70,10 +78,10 @@ Typical usage to inspect a crashed program is:: -> print spam (Pdb) + The module defines the following functions; each enters the debugger in a slightly different way: - .. function:: run(statement[, globals[, locals]]) Execute the *statement* (given as a string) under debugger control. The @@ -117,7 +125,38 @@ slightly different way: .. function:: pm() - Enter post-mortem debugging of the traceback found in ``sys.last_traceback``. + Enter post-mortem debugging of the traceback found in + :data:`sys.last_traceback`. + + +The ``run_*`` functions and :func:`set_trace` are aliases for instantiating the +:class:`Pdb` class and calling the method of the same name. If you want to +access further features, you have to do this yourself: + +.. class:: Pdb(completekey='tab', stdin=None, stdout=None, skip=None) + + :class:`Pdb` is the debugger class. + + The *completekey*, *stdin* and *stdout* arguments are passed to the + underlying :class:`cmd.Cmd` class; see the description there. + + The *skip* argument, if given, must be an iterable of glob-style module name + patterns. The debugger will not step into frames that originate in a module + that matches one of these patterns. [1]_ + + Example call to enable tracing with *skip*:: + + import pdb; pdb.Pdb(skip=['django.*']).set_trace() + + .. versionadded:: 2.7 + The *skip* argument. + + .. method:: run(statement[, globals[, locals]]) + runeval(expression[, globals[, locals]]) + runcall(function[, argument, ...]) + set_trace() + + See the documentation for the functions explained above. .. _debugger-commands: @@ -351,3 +390,9 @@ run [*args* ...] q(uit) Quit from the debugger. The program being executed is aborted. + + +.. rubric:: Footnotes + +.. [1] Whether a frame is considered to originate in a certain module + is determined by the ``__name__`` in the frame globals. diff --git a/Lib/bdb.py b/Lib/bdb.py index f29fa46..6d6d419 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -1,5 +1,6 @@ """Debugger basics""" +import fnmatch import sys import os import types @@ -19,7 +20,8 @@ class Bdb: The standard debugger class (pdb.Pdb) is an example. """ - def __init__(self): + def __init__(self, skip=None): + self.skip = set(skip) if skip else None self.breaks = {} self.fncache = {} @@ -94,9 +96,18 @@ class Bdb: # methods, but they may if they want to redefine the # definition of stopping and breakpoints. + def is_skipped_module(self, module_name): + for pattern in self.skip: + if fnmatch.fnmatch(module_name, pattern): + return True + return False + def stop_here(self, frame): # (CT) stopframe may now also be None, see dispatch_call. # (CT) the former test for None is therefore removed from here. + if self.skip and \ + self.is_skipped_module(frame.f_globals.get('__name__')): + return False if frame is self.stopframe: return frame.f_lineno >= self.stoplineno while frame is not None and frame is not self.stopframe: diff --git a/Lib/pdb.py b/Lib/pdb.py index 2f42b31..2ff265c 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -58,8 +58,8 @@ line_prefix = '\n-> ' # Probably a better default class Pdb(bdb.Bdb, cmd.Cmd): - def __init__(self, completekey='tab', stdin=None, stdout=None): - bdb.Bdb.__init__(self) + def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None): + bdb.Bdb.__init__(self, skip=skip) cmd.Cmd.__init__(self, completekey, stdin, stdout) if stdout: self.use_rawinput = 0 diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py new file mode 100644 index 0000000..68a7702 --- /dev/null +++ b/Lib/test/test_pdb.py @@ -0,0 +1,99 @@ +# A test suite for pdb; at the moment, this only validates skipping of +# specified test modules (RFE #5142). + +import imp +import os +import sys +import doctest +import tempfile + +from test import test_support +# This little helper class is essential for testing pdb under doctest. +from test_doctest import _FakeInput + + +def test_pdb_skip_modules(): + """This illustrates the simple case of module skipping. + + >>> def skip_module(): + ... import string + ... import pdb;pdb.Pdb(skip=['string*']).set_trace() + ... string.lower('FOO') + >>> real_stdin = sys.stdin + >>> sys.stdin = _FakeInput([ + ... 'step', + ... 'continue', + ... ]) + + >>> try: + ... skip_module() + ... finally: + ... sys.stdin = real_stdin + > (4)skip_module() + -> string.lower('FOO') + (Pdb) step + --Return-- + > (4)skip_module()->None + -> string.lower('FOO') + (Pdb) continue +""" + + +# Module for testing skipping of module that makes a callback +mod = imp.new_module('module_to_skip') +exec 'def foo_pony(callback): x = 1; callback(); return None' in mod.__dict__ + + +def test_pdb_skip_modules_with_callback(): + """This illustrates skipping of modules that call into other code. + + >>> def skip_module(): + ... def callback(): + ... return None + ... import pdb;pdb.Pdb(skip=['module_to_skip*']).set_trace() + ... mod.foo_pony(callback) + >>> real_stdin = sys.stdin + >>> sys.stdin = _FakeInput([ + ... 'step', + ... 'step', + ... 'step', + ... 'step', + ... 'step', + ... 'continue', + ... ]) + + >>> try: + ... skip_module() + ... finally: + ... sys.stdin = real_stdin + > (5)skip_module() + -> mod.foo_pony(callback) + (Pdb) step + --Call-- + > (2)callback() + -> def callback(): + (Pdb) step + > (3)callback() + -> return None + (Pdb) step + --Return-- + > (3)callback()->None + -> return None + (Pdb) step + --Return-- + > (5)skip_module()->None + -> mod.foo_pony(callback) + (Pdb) step + > (4)() + -> sys.stdin = real_stdin + (Pdb) continue +""" + + +def test_main(): + from test import test_pdb + test_support.run_doctest(test_pdb, verbosity=True) + + +if __name__ == '__main__': + test_main() diff --git a/Misc/NEWS b/Misc/NEWS index 4efa003..0152d9a 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -273,6 +273,8 @@ Core and Builtins Library ------- +- Issue #5142: Add the ability to skip modules while stepping to pdb. + - Issue #1309567: Fix linecache behavior of stripping subdirectories when looking for files given by a relative filename. -- cgit v0.12