handle final teardown properly, add a new experimental hook for it.

--HG--
branch : 1.0.x
This commit is contained in:
holger krekel 2009-07-31 14:22:02 +02:00
parent 61c53602f2
commit 737c32c783
7 changed files with 63 additions and 26 deletions

View File

@ -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)

View File

@ -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()

View File

@ -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
# -------------------------------------------------------------------------

View File

@ -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()

View File

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

View File

@ -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:

View File

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