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 Post-mortem debugging
~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~
Setting OS_POST_MORTEM_DEBUG=1 in the shell environment will ensure Setting OS_POST_MORTEM_DEBUGGER in the shell environment will ensure
that pdb.post_mortem() will be invoked on test failure:: 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() super(BaseTestCase, self).setUp()
# Configure this first to ensure pm debugging support for setUp() # Configure this first to ensure pm debugging support for setUp()
if os.environ.get('OS_POST_MORTEM_DEBUG') in TRUE_STRING: debugger = os.environ.get('OS_POST_MORTEM_DEBUGGER')
self.addOnException(post_mortem_debug.exception_handler) if debugger:
self.addOnException(post_mortem_debug.get_exception_handler(
debugger))
if os.environ.get('OS_DEBUG') in TRUE_STRING: if os.environ.get('OS_DEBUG') in TRUE_STRING:
_level = std_logging.DEBUG _level = std_logging.DEBUG

View File

@ -13,18 +13,38 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import pdb import functools
import traceback 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. """Exception handler enabling post-mortem debugging.
A class extending testtools.TestCase can add this handler in setUp(): A class extending testtools.TestCase can add this handler in setUp():
self.addOnException(post_mortem_debug.exception_handler) 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. session in the execution environment of the failure.
Frames associated with the testing framework are excluded so that Frames associated with the testing framework are excluded so that
@ -37,7 +57,7 @@ def exception_handler(exc_info):
if ignored_traceback: if ignored_traceback:
tb = FilteredTraceback(tb, ignored_traceback) tb = FilteredTraceback(tb, ignored_traceback)
traceback.print_exception(exc_info[0], exc_info[1], tb) traceback.print_exception(exc_info[0], exc_info[1], tb)
pdb.post_mortem(tb) debugger.post_mortem(tb)
def get_ignored_traceback(tb): def get_ignored_traceback(tb):

View File

@ -34,13 +34,26 @@ class TestTesttoolsExceptionHandler(base.BaseTestCase):
with mock.patch.object(post_mortem_debug, with mock.patch.object(post_mortem_debug,
'get_ignored_traceback', 'get_ignored_traceback',
return_value=mock.Mock()): 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 # traceback will become post_mortem_debug.FilteredTraceback
filtered_exc_info = (exc_info[0], exc_info[1], mock.ANY) filtered_exc_info = (exc_info[0], exc_info[1], mock.ANY)
mock_print_exception.assert_called_once_with(*filtered_exc_info) mock_print_exception.assert_called_once_with(*filtered_exc_info)
mock_post_mortem.assert_called_once_with(mock.ANY) 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): class TestFilteredTraceback(base.BaseTestCase):