fix issue93 - avoid "delayed" teardowns for distributed testing by
simplifying handling of teardowns.
This commit is contained in:
parent
b28977fbaf
commit
c4fe622b82
10
CHANGELOG
10
CHANGELOG
|
@ -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
|
||||
----------------------------------------
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#
|
||||
__version__ = '2.2.0'
|
||||
__version__ = '2.2.1.dev1'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
4
setup.py
4
setup.py
|
@ -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'],
|
||||
|
@ -70,4 +70,4 @@ def make_entry_points():
|
|||
return {'console_scripts': l}
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
main()
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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("""
|
||||
|
|
Loading…
Reference in New Issue