fix issue93 - avoid "delayed" teardowns for distributed testing by

simplifying handling of teardowns.
This commit is contained in:
holger krekel 2011-12-02 21:00:19 +00:00
parent b28977fbaf
commit c4fe622b82
8 changed files with 55 additions and 51 deletions

View File

@ -1,3 +1,13 @@
Changes between 2.2.0 and 2.2.1.dev
----------------------------------------
- fix issue93 (in pytest and pytest-xdist) avoid "delayed teardowns":
the final test in a test node will now run its teardown directly
instead of waiting for the end of the session. Thanks Dave Hunt for
the good reporting and feedback. The pytest_runtest_protocol as well
as the pytest_runtest_teardown hooks now have "nextitem" available
which will be None indicating the end of the test run.
Changes between 2.1.3 and 2.2.0
----------------------------------------

View File

@ -1,2 +1,2 @@
#
__version__ = '2.2.0'
__version__ = '2.2.1.dev1'

View File

@ -121,16 +121,23 @@ def pytest_generate_tests(metafunc):
def pytest_itemstart(item, node=None):
""" (deprecated, use pytest_runtest_logstart). """
def pytest_runtest_protocol(item):
""" implements the standard runtest_setup/call/teardown protocol including
capturing exceptions and calling reporting hooks on the results accordingly.
def pytest_runtest_protocol(item, nextitem):
""" implements the runtest_setup/call/teardown protocol for
the given test item, including capturing exceptions and calling
reporting hooks.
:arg item: test item for which the runtest protocol is performed.
:arg nexitem: the scheduled-to-be-next test item (or None if this
is the end my friend). This argument is passed on to
:py:func:`pytest_runtest_teardown`.
:return boolean: True if no further hook implementations should be invoked.
"""
pytest_runtest_protocol.firstresult = True
def pytest_runtest_logstart(nodeid, location):
""" signal the start of a test run. """
""" signal the start of running a single test item. """
def pytest_runtest_setup(item):
""" called before ``pytest_runtest_call(item)``. """
@ -138,8 +145,14 @@ def pytest_runtest_setup(item):
def pytest_runtest_call(item):
""" called to execute the test ``item``. """
def pytest_runtest_teardown(item):
""" called after ``pytest_runtest_call``. """
def pytest_runtest_teardown(item, nextitem):
""" called after ``pytest_runtest_call``.
:arg nexitem: the scheduled-to-be-next test item (None if no further
test item is scheduled). This argument can be used to
perform exact teardowns, i.e. calling just enough finalizers
so that nextitem only needs to call setup-functions.
"""
def pytest_runtest_makereport(item, call):
""" return a :py:class:`_pytest.runner.TestReport` object

View File

@ -106,8 +106,12 @@ def pytest_collection(session):
def pytest_runtestloop(session):
if session.config.option.collectonly:
return True
for item in session.items:
item.config.hook.pytest_runtest_protocol(item=item)
for i, item in enumerate(session.items):
try:
nextitem = session.items[i+1]
except IndexError:
nextitem = None
item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
if session.shouldstop:
raise session.Interrupted(session.shouldstop)
return True

View File

@ -59,33 +59,20 @@ class NodeInfo:
def __init__(self, location):
self.location = location
def perform_pending_teardown(config, nextitem):
try:
olditem, log = config._pendingteardown
except AttributeError:
pass
else:
del config._pendingteardown
olditem.nextitem = nextitem
call_and_report(olditem, "teardown", log)
def pytest_runtest_protocol(item):
perform_pending_teardown(item.config, item)
def pytest_runtest_protocol(item, nextitem):
item.ihook.pytest_runtest_logstart(
nodeid=item.nodeid, location=item.location,
)
runtestprotocol(item, teardowndelayed=True)
runtestprotocol(item, nextitem=nextitem)
return True
def runtestprotocol(item, log=True, teardowndelayed=False):
def runtestprotocol(item, log=True, nextitem=None):
rep = call_and_report(item, "setup", log)
reports = [rep]
if rep.passed:
reports.append(call_and_report(item, "call", log))
if teardowndelayed:
item.config._pendingteardown = item, log
else:
reports.append(call_and_report(item, "teardown", log))
reports.append(call_and_report(item, "teardown", log,
nextitem=nextitem))
return reports
def pytest_runtest_setup(item):
@ -94,17 +81,8 @@ def pytest_runtest_setup(item):
def pytest_runtest_call(item):
item.runtest()
def pytest_runtest_teardown(item):
item.session._setupstate.teardown_exact(item)
def pytest__teardown_final(session):
perform_pending_teardown(session.config, None)
#call = CallInfo(session._setupstate.teardown_all, when="teardown")
#if call.excinfo:
# ntraceback = call.excinfo.traceback .cut(excludepath=py._pydir)
# call.excinfo.traceback = ntraceback.filter()
# longrepr = call.excinfo.getrepr(funcargs=True)
# return TeardownErrorReport(longrepr)
def pytest_runtest_teardown(item, nextitem):
item.session._setupstate.teardown_exact(item, nextitem)
def pytest_report_teststatus(report):
if report.when in ("setup", "teardown"):
@ -120,18 +98,18 @@ def pytest_report_teststatus(report):
#
# Implementation
def call_and_report(item, when, log=True):
call = call_runtest_hook(item, when)
def call_and_report(item, when, log=True, **kwds):
call = call_runtest_hook(item, when, **kwds)
hook = item.ihook
report = hook.pytest_runtest_makereport(item=item, call=call)
if log:
hook.pytest_runtest_logreport(report=report)
return report
def call_runtest_hook(item, when):
def call_runtest_hook(item, when, **kwds):
hookname = "pytest_runtest_" + when
ihook = getattr(item.ihook, hookname)
return CallInfo(lambda: ihook(item=item), when=when)
return CallInfo(lambda: ihook(item=item, **kwds), when=when)
class CallInfo:
""" Result/Exception info a function invocation. """
@ -338,9 +316,8 @@ class SetupState(object):
self._teardown_with_finalization(None)
assert not self._finalizers
def teardown_exact(self, item):
colitem = item.nextitem
needed_collectors = colitem and colitem.listchain() or []
def teardown_exact(self, item, nextitem):
needed_collectors = nextitem and nextitem.listchain() or []
self._teardown_towards(needed_collectors)
def _teardown_towards(self, needed_collectors):

View File

@ -24,7 +24,7 @@ def main():
name='pytest',
description='py.test: simple powerful testing with Python',
long_description = long_description,
version='2.2.0',
version='2.2.1.dev1',
url='http://pytest.org',
license='MIT license',
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],

View File

@ -689,7 +689,7 @@ class TestRequest:
teardownlist = item.getparent(pytest.Module).obj.teardownlist
ss = item.session._setupstate
assert not teardownlist
ss.teardown_exact(item)
ss.teardown_exact(item, None)
print(ss.stack)
assert teardownlist == [1]

View File

@ -30,9 +30,9 @@ class TestSetupState:
def test_teardown_exact_stack_empty(self, testdir):
item = testdir.getitem("def test_func(): pass")
ss = runner.SetupState()
ss.teardown_exact(item)
ss.teardown_exact(item)
ss.teardown_exact(item)
ss.teardown_exact(item, None)
ss.teardown_exact(item, None)
ss.teardown_exact(item, None)
def test_setup_fails_and_failure_is_cached(self, testdir):
item = testdir.getitem("""