Support pudb as a different post mortem debugger

Post mortem debugger starts pdb at the assert failure place
when OS_POST_MORTEM_DEBUGGER is set and an exception or assert
failure happens.

This patch adds a neutron env variable OS_POST_MORTEM_DEBUGGER
to allow invocation of pudb instead of pdb. pudb module is not
a hard requisite as it will be only imported if you set
OS_POST_MORTEM_DEBUGGER=pudb.

The old OS_POST_MORTEM_DEBUG env variable is removed now.

Change-Id: I5d40913add439cf9c30305bafc98af9f8cd4d12f
This commit is contained in:
Miguel Angel Ajo 2014-10-28 17:09:46 +01:00
parent 89554315da
commit 2c99a24351
4 changed files with 60 additions and 10 deletions

View File

@ -184,7 +184,22 @@ overwritten during the next tox run.
Post-mortem debugging
~~~~~~~~~~~~~~~~~~~~~
Setting OS_POST_MORTEM_DEBUG=1 in the shell environment will ensure
that pdb.post_mortem() will be invoked on test failure::
Setting OS_POST_MORTEM_DEBUGGER in the shell environment will ensure
that the debugger .post_mortem() method will be invoked on test failure::
$ OS_POST_MORTEM_DEBUG=1 ./run_tests.sh -d [test module path]
$ OS_POST_MORTEM_DEBUGGER=pdb ./run_tests.sh -d [test module path]
Supported debuggers are pdb, and pudb. Pudb is full-screen, console-based
visual debugger for Python which let you inspect variables, the stack,
and breakpoints in a very visual way, keeping a high degree of compatibility
with pdb::
$ ./.venv/bin/pip install pudb
$ OS_POST_MORTEM_DEBUGGER=pudb ./run_tests.sh -d [test module path]
References
==========
.. [#pudb] PUDB debugger:
https://pypi.python.org/pypi/pudb

View File

@ -73,8 +73,10 @@ class BaseTestCase(testtools.TestCase):
super(BaseTestCase, self).setUp()
# Configure this first to ensure pm debugging support for setUp()
if os.environ.get('OS_POST_MORTEM_DEBUG') in TRUE_STRING:
self.addOnException(post_mortem_debug.exception_handler)
debugger = os.environ.get('OS_POST_MORTEM_DEBUGGER')
if debugger:
self.addOnException(post_mortem_debug.get_exception_handler(
debugger))
if os.environ.get('OS_DEBUG') in TRUE_STRING:
_level = std_logging.DEBUG

View File

@ -13,18 +13,38 @@
# License for the specific language governing permissions and limitations
# under the License.
import pdb
import functools
import traceback
DEFAULT_DEBUGGER = 'pdb'
def exception_handler(exc_info):
def get_exception_handler(debugger_name=None):
debugger = _get_debugger(debugger_name or DEFAULT_DEBUGGER)
return functools.partial(_exception_handler, debugger)
def _get_debugger(debugger_name):
try:
debugger = __import__(debugger_name)
if 'post_mortem' in dir(debugger):
return debugger
except ImportError:
raise ValueError(
_("can't import %s module as a post mortem debugger") %
debugger_name)
raise ValueError(
_("%s is not a supported post mortem debugger") % debugger_name)
def _exception_handler(debugger, exc_info):
"""Exception handler enabling post-mortem debugging.
A class extending testtools.TestCase can add this handler in setUp():
self.addOnException(post_mortem_debug.exception_handler)
When an exception occurs, the user will be dropped into a pdb
When an exception occurs, the user will be dropped into a debugger
session in the execution environment of the failure.
Frames associated with the testing framework are excluded so that
@ -37,7 +57,7 @@ def exception_handler(exc_info):
if ignored_traceback:
tb = FilteredTraceback(tb, ignored_traceback)
traceback.print_exception(exc_info[0], exc_info[1], tb)
pdb.post_mortem(tb)
debugger.post_mortem(tb)
def get_ignored_traceback(tb):

View File

@ -34,13 +34,26 @@ class TestTesttoolsExceptionHandler(base.BaseTestCase):
with mock.patch.object(post_mortem_debug,
'get_ignored_traceback',
return_value=mock.Mock()):
post_mortem_debug.exception_handler(exc_info)
post_mortem_debug.get_exception_handler()(exc_info)
# traceback will become post_mortem_debug.FilteredTraceback
filtered_exc_info = (exc_info[0], exc_info[1], mock.ANY)
mock_print_exception.assert_called_once_with(*filtered_exc_info)
mock_post_mortem.assert_called_once_with(mock.ANY)
def test__get_debugger(self):
def import_mock(name, *args):
mod_mock = mock.Mock()
mod_mock.__name__ = name
mod_mock.post_mortem = mock.Mock()
return mod_mock
with mock.patch('__builtin__.__import__', side_effect=import_mock):
pdb_debugger = post_mortem_debug._get_debugger('pdb')
pudb_debugger = post_mortem_debug._get_debugger('pudb')
self.assertEqual('pdb', pdb_debugger.__name__)
self.assertEqual('pudb', pudb_debugger.__name__)
class TestFilteredTraceback(base.BaseTestCase):