- fix issue181: --pdb now also works on collect errors. This was

implemented by a slight internal refactoring and the introduction
  of a new hook ``pytest_exception_interact`` hook.

- fix issue341: introduce new experimental hook for IDEs/terminals to
  intercept debugging: ``pytest_exception_interact(node, call, report)``.
This commit is contained in:
holger krekel
2013-09-06 11:56:04 +02:00
parent 8360c1e687
commit 94ee37cdb3
12 changed files with 107 additions and 34 deletions

View File

@@ -1,2 +1,2 @@
#
__version__ = '2.4.0.dev11'
__version__ = '2.4.0.dev12'

View File

@@ -298,7 +298,8 @@ class PluginManager(object):
showlocals=getattr(option, 'showlocals', False),
style=style,
)
res = self.hook.pytest_internalerror(excrepr=excrepr)
res = self.hook.pytest_internalerror(excrepr=excrepr,
excinfo=excinfo)
if not py.builtin.any(res):
for line in str(excrepr).split("\n"):
sys.stderr.write("INTERNALERROR> %s\n" %line)

View File

@@ -241,8 +241,18 @@ def pytest_plugin_registered(plugin, manager):
def pytest_plugin_unregistered(plugin):
""" a py lib plugin got unregistered. """
def pytest_internalerror(excrepr):
def pytest_internalerror(excrepr, excinfo):
""" called for internal errors. """
def pytest_keyboard_interrupt(excinfo):
""" called for keyboard interrupt. """
def pytest_exception_interact(node, call, report):
""" (experimental, new in 2.4) called when
an exception was raised which can potentially be
interactively handled.
This hook is only called if an exception was raised
that is not an internal exception like "skip.Exception".
"""

View File

@@ -10,7 +10,7 @@ except ImportError:
from UserDict import DictMixin as MappingMixin
from _pytest.mark import MarkInfo
import _pytest.runner
from _pytest.runner import collect_one_node, Skipped
tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
@@ -372,7 +372,7 @@ class Collector(Node):
# the set of exceptions to interpret as "Skip the whole module" during
# collection
skip_exceptions = (_pytest.runner.Skipped,)
skip_exceptions = (Skipped,)
class CollectError(Exception):
""" an error during collection, contains a custom message. """
@@ -512,8 +512,7 @@ class Session(FSCollector):
parts = self._parsearg(arg)
self._initialparts.append(parts)
self._initialpaths.add(parts[0])
self.ihook.pytest_collectstart(collector=self)
rep = self.ihook.pytest_make_collect_report(collector=self)
rep = collect_one_node(self)
self.ihook.pytest_collectreport(report=rep)
self.trace.root.indent -= 1
if self._notfound:
@@ -642,8 +641,7 @@ class Session(FSCollector):
resultnodes.append(node)
continue
assert isinstance(node, pytest.Collector)
node.ihook.pytest_collectstart(collector=node)
rep = node.ihook.pytest_make_collect_report(collector=node)
rep = collect_one_node(node)
if rep.passed:
has_matched = False
for x in rep.result:
@@ -664,8 +662,7 @@ class Session(FSCollector):
yield node
else:
assert isinstance(node, pytest.Collector)
node.ihook.pytest_collectstart(collector=node)
rep = node.ihook.pytest_make_collect_report(collector=node)
rep = collect_one_node(node)
if rep.passed:
for subnode in rep.result:
for x in self.genitems(subnode):

View File

@@ -52,31 +52,26 @@ def pytest_runtest_makereport():
pytestPDB.item = None
class PdbInvoke:
@pytest.mark.tryfirst
def pytest_runtest_makereport(self, item, call, __multicall__):
rep = __multicall__.execute()
if not call.excinfo or \
call.excinfo.errisinstance(pytest.skip.Exception) or \
call.excinfo.errisinstance(py.std.bdb.BdbQuit):
return rep
if hasattr(rep, "wasxfail"):
return rep
return _enter_pdb(item, call.excinfo, rep)
def pytest_exception_interact(self, node, call, report):
return _enter_pdb(node, call.excinfo, report)
def pytest_internalerror(self, excrepr, excinfo):
for line in str(excrepr).split("\n"):
sys.stderr.write("INTERNALERROR> %s\n" %line)
sys.stderr.flush()
tb = _postmortem_traceback(excinfo)
post_mortem(tb)
def _enter_pdb(item, excinfo, rep):
# we assume that the above execute() suspended capturing
def _enter_pdb(node, excinfo, rep):
# XXX we re-use the TerminalReporter's terminalwriter
# because this seems to avoid some encoding related troubles
# for not completely clear reasons.
tw = item.config.pluginmanager.getplugin("terminalreporter")._tw
tw = node.config.pluginmanager.getplugin("terminalreporter")._tw
tw.line()
tw.sep(">", "traceback")
rep.toterminal(tw)
tw.sep(">", "entering PDB")
tb = _postmortem_traceback(excinfo)
post_mortem(tb)
rep._pdbshown = True

View File

@@ -108,8 +108,16 @@ def call_and_report(item, when, log=True, **kwds):
report = hook.pytest_runtest_makereport(item=item, call=call)
if log:
hook.pytest_runtest_logreport(report=report)
if check_interactive_exception(call, report):
hook.pytest_exception_interact(node=item, call=call, report=report)
return report
def check_interactive_exception(call, report):
return call.excinfo and not (
hasattr(report, "wasxfail") or
call.excinfo.errisinstance(skip.Exception) or
call.excinfo.errisinstance(py.std.bdb.BdbQuit))
def call_runtest_hook(item, when, **kwds):
hookname = "pytest_runtest_" + when
ihook = getattr(item.ihook, hookname)
@@ -268,8 +276,11 @@ def pytest_make_collect_report(collector):
if not hasattr(errorinfo, "toterminal"):
errorinfo = CollectErrorRepr(errorinfo)
longrepr = errorinfo
return CollectReport(collector.nodeid, outcome, longrepr,
rep = CollectReport(collector.nodeid, outcome, longrepr,
getattr(call, 'result', None))
rep.call = call # see collect_one_node
return rep
class CollectReport(BaseReport):
def __init__(self, nodeid, outcome, longrepr, result, sections=(), **extra):
@@ -364,6 +375,16 @@ class SetupState(object):
col._prepare_exc = sys.exc_info()
raise
def collect_one_node(collector):
ihook = collector.ihook
ihook.pytest_collectstart(collector=collector)
rep = ihook.pytest_make_collect_report(collector=collector)
call = rep.__dict__.pop("call", None)
if call and check_interactive_exception(call, rep):
ihook.pytest_exception_interact(node=collector, call=call, report=rep)
return rep
# =============================================================
# Test OutcomeExceptions and helpers for creating them.