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
|
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):
|
def pytest_itemstart(item, node=None):
|
||||||
""" (deprecated, use pytest_runtest_logstart). """
|
""" (deprecated, use pytest_runtest_logstart). """
|
||||||
|
|
||||||
def pytest_runtest_protocol(item):
|
def pytest_runtest_protocol(item, nextitem):
|
||||||
""" implements the standard runtest_setup/call/teardown protocol including
|
""" implements the runtest_setup/call/teardown protocol for
|
||||||
capturing exceptions and calling reporting hooks on the results accordingly.
|
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.
|
:return boolean: True if no further hook implementations should be invoked.
|
||||||
"""
|
"""
|
||||||
pytest_runtest_protocol.firstresult = True
|
pytest_runtest_protocol.firstresult = True
|
||||||
|
|
||||||
def pytest_runtest_logstart(nodeid, location):
|
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):
|
def pytest_runtest_setup(item):
|
||||||
""" called before ``pytest_runtest_call(item)``. """
|
""" called before ``pytest_runtest_call(item)``. """
|
||||||
|
@ -138,8 +145,14 @@ def pytest_runtest_setup(item):
|
||||||
def pytest_runtest_call(item):
|
def pytest_runtest_call(item):
|
||||||
""" called to execute the test ``item``. """
|
""" called to execute the test ``item``. """
|
||||||
|
|
||||||
def pytest_runtest_teardown(item):
|
def pytest_runtest_teardown(item, nextitem):
|
||||||
""" called after ``pytest_runtest_call``. """
|
""" 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):
|
def pytest_runtest_makereport(item, call):
|
||||||
""" return a :py:class:`_pytest.runner.TestReport` object
|
""" return a :py:class:`_pytest.runner.TestReport` object
|
||||||
|
|
|
@ -106,8 +106,12 @@ def pytest_collection(session):
|
||||||
def pytest_runtestloop(session):
|
def pytest_runtestloop(session):
|
||||||
if session.config.option.collectonly:
|
if session.config.option.collectonly:
|
||||||
return True
|
return True
|
||||||
for item in session.items:
|
for i, item in enumerate(session.items):
|
||||||
item.config.hook.pytest_runtest_protocol(item=item)
|
try:
|
||||||
|
nextitem = session.items[i+1]
|
||||||
|
except IndexError:
|
||||||
|
nextitem = None
|
||||||
|
item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
|
||||||
if session.shouldstop:
|
if session.shouldstop:
|
||||||
raise session.Interrupted(session.shouldstop)
|
raise session.Interrupted(session.shouldstop)
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -59,33 +59,20 @@ class NodeInfo:
|
||||||
def __init__(self, location):
|
def __init__(self, location):
|
||||||
self.location = location
|
self.location = location
|
||||||
|
|
||||||
def perform_pending_teardown(config, nextitem):
|
def pytest_runtest_protocol(item, 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)
|
|
||||||
item.ihook.pytest_runtest_logstart(
|
item.ihook.pytest_runtest_logstart(
|
||||||
nodeid=item.nodeid, location=item.location,
|
nodeid=item.nodeid, location=item.location,
|
||||||
)
|
)
|
||||||
runtestprotocol(item, teardowndelayed=True)
|
runtestprotocol(item, nextitem=nextitem)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def runtestprotocol(item, log=True, teardowndelayed=False):
|
def runtestprotocol(item, log=True, nextitem=None):
|
||||||
rep = call_and_report(item, "setup", log)
|
rep = call_and_report(item, "setup", log)
|
||||||
reports = [rep]
|
reports = [rep]
|
||||||
if rep.passed:
|
if rep.passed:
|
||||||
reports.append(call_and_report(item, "call", log))
|
reports.append(call_and_report(item, "call", log))
|
||||||
if teardowndelayed:
|
reports.append(call_and_report(item, "teardown", log,
|
||||||
item.config._pendingteardown = item, log
|
nextitem=nextitem))
|
||||||
else:
|
|
||||||
reports.append(call_and_report(item, "teardown", log))
|
|
||||||
return reports
|
return reports
|
||||||
|
|
||||||
def pytest_runtest_setup(item):
|
def pytest_runtest_setup(item):
|
||||||
|
@ -94,17 +81,8 @@ def pytest_runtest_setup(item):
|
||||||
def pytest_runtest_call(item):
|
def pytest_runtest_call(item):
|
||||||
item.runtest()
|
item.runtest()
|
||||||
|
|
||||||
def pytest_runtest_teardown(item):
|
def pytest_runtest_teardown(item, nextitem):
|
||||||
item.session._setupstate.teardown_exact(item)
|
item.session._setupstate.teardown_exact(item, nextitem)
|
||||||
|
|
||||||
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_report_teststatus(report):
|
def pytest_report_teststatus(report):
|
||||||
if report.when in ("setup", "teardown"):
|
if report.when in ("setup", "teardown"):
|
||||||
|
@ -120,18 +98,18 @@ def pytest_report_teststatus(report):
|
||||||
#
|
#
|
||||||
# Implementation
|
# Implementation
|
||||||
|
|
||||||
def call_and_report(item, when, log=True):
|
def call_and_report(item, when, log=True, **kwds):
|
||||||
call = call_runtest_hook(item, when)
|
call = call_runtest_hook(item, when, **kwds)
|
||||||
hook = item.ihook
|
hook = item.ihook
|
||||||
report = hook.pytest_runtest_makereport(item=item, call=call)
|
report = hook.pytest_runtest_makereport(item=item, call=call)
|
||||||
if log:
|
if log:
|
||||||
hook.pytest_runtest_logreport(report=report)
|
hook.pytest_runtest_logreport(report=report)
|
||||||
return report
|
return report
|
||||||
|
|
||||||
def call_runtest_hook(item, when):
|
def call_runtest_hook(item, when, **kwds):
|
||||||
hookname = "pytest_runtest_" + when
|
hookname = "pytest_runtest_" + when
|
||||||
ihook = getattr(item.ihook, hookname)
|
ihook = getattr(item.ihook, hookname)
|
||||||
return CallInfo(lambda: ihook(item=item), when=when)
|
return CallInfo(lambda: ihook(item=item, **kwds), when=when)
|
||||||
|
|
||||||
class CallInfo:
|
class CallInfo:
|
||||||
""" Result/Exception info a function invocation. """
|
""" Result/Exception info a function invocation. """
|
||||||
|
@ -338,9 +316,8 @@ class SetupState(object):
|
||||||
self._teardown_with_finalization(None)
|
self._teardown_with_finalization(None)
|
||||||
assert not self._finalizers
|
assert not self._finalizers
|
||||||
|
|
||||||
def teardown_exact(self, item):
|
def teardown_exact(self, item, nextitem):
|
||||||
colitem = item.nextitem
|
needed_collectors = nextitem and nextitem.listchain() or []
|
||||||
needed_collectors = colitem and colitem.listchain() or []
|
|
||||||
self._teardown_towards(needed_collectors)
|
self._teardown_towards(needed_collectors)
|
||||||
|
|
||||||
def _teardown_towards(self, needed_collectors):
|
def _teardown_towards(self, needed_collectors):
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -24,7 +24,7 @@ def main():
|
||||||
name='pytest',
|
name='pytest',
|
||||||
description='py.test: simple powerful testing with Python',
|
description='py.test: simple powerful testing with Python',
|
||||||
long_description = long_description,
|
long_description = long_description,
|
||||||
version='2.2.0',
|
version='2.2.1.dev1',
|
||||||
url='http://pytest.org',
|
url='http://pytest.org',
|
||||||
license='MIT license',
|
license='MIT license',
|
||||||
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
||||||
|
|
|
@ -689,7 +689,7 @@ class TestRequest:
|
||||||
teardownlist = item.getparent(pytest.Module).obj.teardownlist
|
teardownlist = item.getparent(pytest.Module).obj.teardownlist
|
||||||
ss = item.session._setupstate
|
ss = item.session._setupstate
|
||||||
assert not teardownlist
|
assert not teardownlist
|
||||||
ss.teardown_exact(item)
|
ss.teardown_exact(item, None)
|
||||||
print(ss.stack)
|
print(ss.stack)
|
||||||
assert teardownlist == [1]
|
assert teardownlist == [1]
|
||||||
|
|
||||||
|
|
|
@ -30,9 +30,9 @@ class TestSetupState:
|
||||||
def test_teardown_exact_stack_empty(self, testdir):
|
def test_teardown_exact_stack_empty(self, testdir):
|
||||||
item = testdir.getitem("def test_func(): pass")
|
item = testdir.getitem("def test_func(): pass")
|
||||||
ss = runner.SetupState()
|
ss = runner.SetupState()
|
||||||
ss.teardown_exact(item)
|
ss.teardown_exact(item, None)
|
||||||
ss.teardown_exact(item)
|
ss.teardown_exact(item, None)
|
||||||
ss.teardown_exact(item)
|
ss.teardown_exact(item, None)
|
||||||
|
|
||||||
def test_setup_fails_and_failure_is_cached(self, testdir):
|
def test_setup_fails_and_failure_is_cached(self, testdir):
|
||||||
item = testdir.getitem("""
|
item = testdir.getitem("""
|
||||||
|
|
Loading…
Reference in New Issue