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
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
# 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
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):
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):