handle final teardown properly, add a new experimental hook for it.
--HG-- branch : 1.0.x
This commit is contained in:
parent
61c53602f2
commit
737c32c783
|
@ -1,6 +1,8 @@
|
|||
Changes between 1.0.0b8 and 1.0.0b9
|
||||
=====================================
|
||||
|
||||
* cleanly handle and report final teardown of test setup
|
||||
|
||||
* fix svn-1.6 compat issue with py.path.svnwc().versioned()
|
||||
(thanks Wouter Vanden Hove)
|
||||
|
||||
|
|
|
@ -115,6 +115,7 @@ class SlaveNode(object):
|
|||
self.config.basetemp = py.path.local(basetemp)
|
||||
self.config.pluginmanager.do_configure(self.config)
|
||||
self.config.pluginmanager.register(self)
|
||||
self.runner = self.config.pluginmanager.getplugin("pytest_runner")
|
||||
self.sendevent("slaveready")
|
||||
try:
|
||||
while 1:
|
||||
|
@ -135,26 +136,15 @@ class SlaveNode(object):
|
|||
raise
|
||||
|
||||
def run_single(self, item):
|
||||
call = CallInfo(item._checkcollectable, 'setup')
|
||||
call = self.runner.CallInfo(item._checkcollectable, when='setup')
|
||||
if call.excinfo:
|
||||
# likely it is not collectable here because of
|
||||
# platform/import-dependency induced skips
|
||||
# XXX somewhat ugly shortcuts - also makes a collection
|
||||
# failure into an ItemTestReport - this might confuse
|
||||
# pytest_runtest_logreport hooks
|
||||
runner = item.config.pluginmanager.getplugin("pytest_runner")
|
||||
rep = runner.pytest_runtest_makereport(item=item, call=call)
|
||||
rep = self.runner.pytest_runtest_makereport(item=item, call=call)
|
||||
self.pytest_runtest_logreport(rep)
|
||||
return
|
||||
item.config.hook.pytest_runtest_protocol(item=item)
|
||||
|
||||
class CallInfo:
|
||||
excinfo = None
|
||||
def __init__(self, func, when):
|
||||
self.when = when
|
||||
try:
|
||||
self.result = func()
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.excinfo = py.code.ExceptionInfo()
|
||||
|
|
|
@ -86,6 +86,14 @@ pytest_runtest_makereport.firstresult = True
|
|||
def pytest_runtest_logreport(rep):
|
||||
""" process item test report. """
|
||||
|
||||
# special handling for final teardown - somewhat internal for now
|
||||
def pytest__teardown_final(session):
|
||||
""" called before test session finishes. """
|
||||
pytest__teardown_final.firstresult = True
|
||||
|
||||
def pytest__teardown_final_logerror(rep):
|
||||
""" called if runtest_teardown_final failed. """
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# test session related hooks
|
||||
# -------------------------------------------------------------------------
|
||||
|
|
|
@ -188,6 +188,20 @@ class CaptureManager:
|
|||
def pytest_runtest_teardown(self, item):
|
||||
self.resumecapture_item(item)
|
||||
|
||||
def pytest_runtest_teardown(self, item):
|
||||
self.resumecapture_item(item)
|
||||
|
||||
def pytest__teardown_final(self, __call__, session):
|
||||
method = self._getmethod(session.config, None)
|
||||
self.resumecapture(method)
|
||||
try:
|
||||
rep = __call__.execute(firstresult=True)
|
||||
finally:
|
||||
outerr = self.suspendcapture()
|
||||
if rep:
|
||||
addouterr(rep, outerr)
|
||||
return rep
|
||||
|
||||
def pytest_keyboard_interrupt(self, excinfo):
|
||||
if hasattr(self, '_capturing'):
|
||||
self.suspendcapture()
|
||||
|
|
|
@ -22,7 +22,10 @@ def pytest_configure(config):
|
|||
def pytest_sessionfinish(session, exitstatus):
|
||||
# XXX see above
|
||||
if hasattr(session.config, '_setupstate'):
|
||||
session.config._setupstate.teardown_all()
|
||||
hook = session.config.hook
|
||||
rep = hook.pytest__teardown_final(session=session)
|
||||
if rep:
|
||||
hook.pytest__teardown_final_logerror(rep=rep)
|
||||
# prevent logging module atexit handler from choking on
|
||||
# its attempt to close already closed streams
|
||||
# see http://bugs.python.org/issue6333
|
||||
|
@ -70,6 +73,12 @@ def pytest_runtest_makereport(item, call):
|
|||
def pytest_runtest_teardown(item):
|
||||
item.config._setupstate.teardown_exact(item)
|
||||
|
||||
def pytest__teardown_final(session):
|
||||
call = CallInfo(session.config._setupstate.teardown_all, when="teardown")
|
||||
if call.excinfo:
|
||||
rep = TeardownErrorReport(call.excinfo)
|
||||
return rep
|
||||
|
||||
def pytest_report_teststatus(rep):
|
||||
if rep.when in ("setup", "teardown"):
|
||||
if rep.failed:
|
||||
|
@ -83,22 +92,24 @@ def pytest_report_teststatus(rep):
|
|||
# Implementation
|
||||
|
||||
def call_and_report(item, when, log=True):
|
||||
call = RuntestHookCall(item, when)
|
||||
call = call_runtest_hook(item, when)
|
||||
hook = item.config.hook
|
||||
report = hook.pytest_runtest_makereport(item=item, call=call)
|
||||
if log and (when == "call" or not report.passed):
|
||||
hook.pytest_runtest_logreport(rep=report)
|
||||
return report
|
||||
|
||||
class RuntestHookCall:
|
||||
def call_runtest_hook(item, when):
|
||||
hookname = "pytest_runtest_" + when
|
||||
hook = getattr(item.config.hook, hookname)
|
||||
return CallInfo(lambda: hook(item=item), when=when)
|
||||
|
||||
class CallInfo:
|
||||
excinfo = None
|
||||
_prefix = "pytest_runtest_"
|
||||
def __init__(self, item, when):
|
||||
def __init__(self, func, when):
|
||||
self.when = when
|
||||
hookname = self._prefix + when
|
||||
hook = getattr(item.config.hook, hookname)
|
||||
try:
|
||||
self.result = hook(item=item)
|
||||
self.result = func()
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
|
@ -209,6 +220,13 @@ class CollectReport(BaseReport):
|
|||
def getnode(self):
|
||||
return self.collector
|
||||
|
||||
class TeardownErrorReport(BaseReport):
|
||||
skipped = passed = False
|
||||
failed = True
|
||||
when = "teardown"
|
||||
def __init__(self, excinfo):
|
||||
self.longrepr = excinfo.getrepr(funcargs=True)
|
||||
|
||||
class SetupState(object):
|
||||
""" shared state for setting up/tearing down test items or collectors. """
|
||||
def __init__(self):
|
||||
|
|
|
@ -166,8 +166,10 @@ class TerminalReporter:
|
|||
fspath, lineno, msg = self._getreportinfo(item)
|
||||
self.write_fspath_result(fspath, "")
|
||||
|
||||
def pytest__teardown_final_logerror(self, rep):
|
||||
self.stats.setdefault("error", []).append(rep)
|
||||
|
||||
def pytest_runtest_logreport(self, rep):
|
||||
fspath = rep.item.fspath
|
||||
cat, letter, word = self.getcategoryletterword(rep)
|
||||
if not letter and not word:
|
||||
# probably passed setup/teardown
|
||||
|
@ -290,9 +292,11 @@ class TerminalReporter:
|
|||
def _getfailureheadline(self, rep):
|
||||
if hasattr(rep, "collector"):
|
||||
return str(rep.collector.fspath)
|
||||
else:
|
||||
elif hasattr(rep, 'item'):
|
||||
fspath, lineno, msg = self._getreportinfo(rep.item)
|
||||
return msg
|
||||
else:
|
||||
return "test session"
|
||||
|
||||
def _getreportinfo(self, item):
|
||||
try:
|
||||
|
|
|
@ -123,7 +123,6 @@ class TestPerTestCapturing:
|
|||
#"*1 fixture failure*"
|
||||
])
|
||||
|
||||
@py.test.mark.xfail
|
||||
def test_teardown_final_capturing(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
def teardown_module(mod):
|
||||
|
@ -134,8 +133,10 @@ class TestPerTestCapturing:
|
|||
""")
|
||||
result = testdir.runpytest(p)
|
||||
assert result.stdout.fnmatch_lines([
|
||||
"teardown module*",
|
||||
#"*1 fixture failure*"
|
||||
"*def teardown_module(mod):*",
|
||||
"*Captured stdout*",
|
||||
"*teardown module*",
|
||||
"*1 error*",
|
||||
])
|
||||
|
||||
def test_capturing_outerr(self, testdir):
|
||||
|
|
Loading…
Reference in New Issue