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