Merge pull request #8323 from bluetech/setupstate-refactor-3

runner: a few more tweaks to SetupState
This commit is contained in:
Ran Benita 2021-02-08 13:56:04 +02:00 committed by GitHub
commit 1003beaffa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 31 additions and 26 deletions

View File

@ -372,7 +372,7 @@ def _fill_fixtures_impl(function: "Function") -> None:
fi = fm.getfixtureinfo(function.parent, function.obj, None) fi = fm.getfixtureinfo(function.parent, function.obj, None)
function._fixtureinfo = fi function._fixtureinfo = fi
request = function._request = FixtureRequest(function, _ispytest=True) request = function._request = FixtureRequest(function, _ispytest=True)
fm.session._setupstate.prepare(function) fm.session._setupstate.setup(function)
request._fillfixtures() request._fillfixtures()
# Prune out funcargs for jstests. # Prune out funcargs for jstests.
newfuncargs = {} newfuncargs = {}

View File

@ -509,9 +509,9 @@ def pytest_runtest_teardown(item: "Item", nextitem: Optional["Item"]) -> None:
:param nextitem: :param nextitem:
The scheduled-to-be-next test item (None if no further test item is The scheduled-to-be-next test item (None if no further test item is
scheduled). This argument can be used to perform exact teardowns, scheduled). This argument is used to perform exact teardowns, i.e.
i.e. calling just enough finalizers so that nextitem only needs to calling just enough finalizers so that nextitem only needs to call
call setup-functions. setup functions.
""" """

View File

@ -120,6 +120,8 @@ def runtestprotocol(
) -> List[TestReport]: ) -> List[TestReport]:
hasrequest = hasattr(item, "_request") hasrequest = hasattr(item, "_request")
if hasrequest and not item._request: # type: ignore[attr-defined] if hasrequest and not item._request: # type: ignore[attr-defined]
# This only happens if the item is re-run, as is done by
# pytest-rerunfailures.
item._initrequest() # type: ignore[attr-defined] item._initrequest() # type: ignore[attr-defined]
rep = call_and_report(item, "setup", log) rep = call_and_report(item, "setup", log)
reports = [rep] reports = [rep]
@ -151,7 +153,7 @@ def show_test_item(item: Item) -> None:
def pytest_runtest_setup(item: Item) -> None: def pytest_runtest_setup(item: Item) -> None:
_update_current_test_var(item, "setup") _update_current_test_var(item, "setup")
item.session._setupstate.prepare(item) item.session._setupstate.setup(item)
def pytest_runtest_call(item: Item) -> None: def pytest_runtest_call(item: Item) -> None:
@ -417,7 +419,7 @@ class SetupState:
[] []
During the setup phase of item1, prepare(item1) is called. What it does During the setup phase of item1, setup(item1) is called. What it does
is: is:
push session to stack, run session.setup() push session to stack, run session.setup()
@ -441,7 +443,7 @@ class SetupState:
[session] [session]
During the setup phase of item2, prepare(item2) is called. What it does During the setup phase of item2, setup(item2) is called. What it does
is: is:
push mod2 to stack, run mod2.setup() push mod2 to stack, run mod2.setup()
@ -477,23 +479,26 @@ class SetupState:
], ],
] = {} ] = {}
def prepare(self, item: Item) -> None: def setup(self, item: Item) -> None:
"""Setup objects along the collector chain to the item.""" """Setup objects along the collector chain to the item."""
needed_collectors = item.listchain()
# If a collector fails its setup, fail its entire subtree of items. # If a collector fails its setup, fail its entire subtree of items.
# The setup is not retried for each item - the same exception is used. # The setup is not retried for each item - the same exception is used.
for col, (finalizers, prepare_exc) in self.stack.items(): for col, (finalizers, exc) in self.stack.items():
if prepare_exc: assert col in needed_collectors, "previous item was not torn down properly"
raise prepare_exc if exc:
raise exc
needed_collectors = item.listchain()
for col in needed_collectors[len(self.stack) :]: for col in needed_collectors[len(self.stack) :]:
assert col not in self.stack assert col not in self.stack
# Push onto the stack.
self.stack[col] = ([col.teardown], None) self.stack[col] = ([col.teardown], None)
try: try:
col.setup() col.setup()
except TEST_OUTCOME as e: except TEST_OUTCOME as exc:
self.stack[col] = (self.stack[col][0], e) self.stack[col] = (self.stack[col][0], exc)
raise e raise exc
def addfinalizer(self, finalizer: Callable[[], object], node: Node) -> None: def addfinalizer(self, finalizer: Callable[[], object], node: Node) -> None:
"""Attach a finalizer to the given node. """Attach a finalizer to the given node.
@ -517,7 +522,7 @@ class SetupState:
while self.stack: while self.stack:
if list(self.stack.keys()) == needed_collectors[: len(self.stack)]: if list(self.stack.keys()) == needed_collectors[: len(self.stack)]:
break break
node, (finalizers, prepare_exc) = self.stack.popitem() node, (finalizers, _) = self.stack.popitem()
while finalizers: while finalizers:
fin = finalizers.pop() fin = finalizers.pop()
try: try:

View File

@ -131,7 +131,7 @@ class TestFillFixtures:
item = pytester.getitem(Path("test_funcarg_basic.py")) item = pytester.getitem(Path("test_funcarg_basic.py"))
assert isinstance(item, Function) assert isinstance(item, Function)
# Execute's item's setup, which fills fixtures. # Execute's item's setup, which fills fixtures.
item.session._setupstate.prepare(item) item.session._setupstate.setup(item)
del item.funcargs["request"] del item.funcargs["request"]
assert len(get_public_names(item.funcargs)) == 2 assert len(get_public_names(item.funcargs)) == 2
assert item.funcargs["some"] == "test_func" assert item.funcargs["some"] == "test_func"
@ -827,7 +827,7 @@ class TestRequestBasic:
req = item._request req = item._request
# Execute item's setup. # Execute item's setup.
item.session._setupstate.prepare(item) item.session._setupstate.setup(item)
with pytest.raises(pytest.FixtureLookupError): with pytest.raises(pytest.FixtureLookupError):
req.getfixturevalue("notexists") req.getfixturevalue("notexists")
@ -855,7 +855,7 @@ class TestRequestBasic:
""" """
) )
assert isinstance(item, Function) assert isinstance(item, Function)
item.session._setupstate.prepare(item) item.session._setupstate.setup(item)
item._request._fillfixtures() item._request._fillfixtures()
# successively check finalization calls # successively check finalization calls
parent = item.getparent(pytest.Module) parent = item.getparent(pytest.Module)

View File

@ -25,7 +25,7 @@ class TestSetupState:
item = pytester.getitem("def test_func(): pass") item = pytester.getitem("def test_func(): pass")
ss = item.session._setupstate ss = item.session._setupstate
values = [1] values = [1]
ss.prepare(item) ss.setup(item)
ss.addfinalizer(values.pop, item) ss.addfinalizer(values.pop, item)
assert values assert values
ss.teardown_exact(None) ss.teardown_exact(None)
@ -34,7 +34,7 @@ class TestSetupState:
def test_teardown_exact_stack_empty(self, pytester: Pytester) -> None: def test_teardown_exact_stack_empty(self, pytester: Pytester) -> None:
item = pytester.getitem("def test_func(): pass") item = pytester.getitem("def test_func(): pass")
ss = item.session._setupstate ss = item.session._setupstate
ss.prepare(item) ss.setup(item)
ss.teardown_exact(None) ss.teardown_exact(None)
ss.teardown_exact(None) ss.teardown_exact(None)
ss.teardown_exact(None) ss.teardown_exact(None)
@ -49,9 +49,9 @@ class TestSetupState:
) )
ss = item.session._setupstate ss = item.session._setupstate
with pytest.raises(ValueError): with pytest.raises(ValueError):
ss.prepare(item) ss.setup(item)
with pytest.raises(ValueError): with pytest.raises(ValueError):
ss.prepare(item) ss.setup(item)
def test_teardown_multiple_one_fails(self, pytester: Pytester) -> None: def test_teardown_multiple_one_fails(self, pytester: Pytester) -> None:
r = [] r = []
@ -67,7 +67,7 @@ class TestSetupState:
item = pytester.getitem("def test_func(): pass") item = pytester.getitem("def test_func(): pass")
ss = item.session._setupstate ss = item.session._setupstate
ss.prepare(item) ss.setup(item)
ss.addfinalizer(fin1, item) ss.addfinalizer(fin1, item)
ss.addfinalizer(fin2, item) ss.addfinalizer(fin2, item)
ss.addfinalizer(fin3, item) ss.addfinalizer(fin3, item)
@ -87,7 +87,7 @@ class TestSetupState:
item = pytester.getitem("def test_func(): pass") item = pytester.getitem("def test_func(): pass")
ss = item.session._setupstate ss = item.session._setupstate
ss.prepare(item) ss.setup(item)
ss.addfinalizer(fin1, item) ss.addfinalizer(fin1, item)
ss.addfinalizer(fin2, item) ss.addfinalizer(fin2, item)
with pytest.raises(Exception) as err: with pytest.raises(Exception) as err:
@ -106,7 +106,7 @@ class TestSetupState:
item = pytester.getitem("def test_func(): pass") item = pytester.getitem("def test_func(): pass")
mod = item.listchain()[-2] mod = item.listchain()[-2]
ss = item.session._setupstate ss = item.session._setupstate
ss.prepare(item) ss.setup(item)
ss.addfinalizer(fin_module, mod) ss.addfinalizer(fin_module, mod)
ss.addfinalizer(fin_func, item) ss.addfinalizer(fin_func, item)
with pytest.raises(Exception, match="oops1"): with pytest.raises(Exception, match="oops1"):