move fixture finalizing to standalone hook

This commit is contained in:
Aleksandr Brodin 2024-03-27 10:13:11 +07:00
parent 108f99086d
commit 86a3ed6ae4
3 changed files with 49 additions and 30 deletions

View File

@ -1015,31 +1015,8 @@ class FixtureDef(Generic[FixtureValue]):
self._finalizers.append(finalizer)
def finish(self, request: SubRequest) -> None:
exceptions: List[BaseException] = []
while self._finalizers:
fin = self._finalizers.pop()
try:
fin()
except BaseException as e:
exceptions.append(e)
node = request.node
if len(exceptions) == 1:
final_exception = exceptions[0]
elif len(exceptions) > 1:
msg = f'errors while tearing down fixture "{self.argname}" of {node}'
final_exception = BaseExceptionGroup(msg, exceptions[::-1])
else:
final_exception = None
node.ihook.pytest_fixture_post_finalizer(
fixturedef=self, request=request, exception=final_exception
)
# Even if finalization fails, we invalidate the cached fixture
# value and remove all finalizers because they may be bound methods
# which will keep instances alive.
self.cached_result = None
self._finalizers.clear()
if final_exception:
raise final_exception
node.ihook.pytest_fixture_teardown(fixturedef=self, request=request)
def execute(self, request: SubRequest) -> FixtureValue:
"""Return the value of this fixture, executing it if not cached."""
@ -1151,6 +1128,30 @@ def pytest_fixture_setup(
return result
def pytest_fixture_teardown(
fixturedef: FixtureDef[FixtureValue], request: SubRequest
) -> None:
exceptions: List[BaseException] = []
while fixturedef._finalizers:
fin = fixturedef._finalizers.pop()
try:
fin()
except BaseException as e:
exceptions.append(e)
node = request.node
node.ihook.pytest_fixture_post_finalizer(fixturedef=fixturedef, request=request)
# Even if finalization fails, we invalidate the cached fixture
# value and remove all finalizers because they may be bound methods
# which will keep instances alive.
fixturedef.cached_result = None
fixturedef._finalizers.clear()
if len(exceptions) == 1:
raise exceptions[0]
elif len(exceptions) > 1:
msg = f'errors while tearing down fixture "{fixturedef.argname}" of {node}'
raise BaseExceptionGroup(msg, exceptions[::-1])
def wrap_function_to_error_out_if_called_directly(
function: FixtureFunction,
fixture_marker: "FixtureFunctionMarker",

View File

@ -860,10 +860,28 @@ def pytest_fixture_setup(
"""
def pytest_fixture_teardown(
fixturedef: "FixtureDef[Any]", request: "SubRequest"
) -> None:
"""Perform fixture teardown execution.
:param fixturdef:
The fixture definition object.
:param request:
The fixture request object.
Use in conftest plugins
=======================
Any conftest file can implement this hook. For a given fixture, only
conftest files in the fixture scope's directory and its parent directories
are consulted.
"""
def pytest_fixture_post_finalizer(
fixturedef: "FixtureDef[Any]",
request: "SubRequest",
exception: "BaseException | None",
) -> None:
"""Called after fixture teardown, but before the cache is cleared, so
the fixture result ``fixturedef.cached_result`` is still available (not
@ -873,8 +891,6 @@ def pytest_fixture_post_finalizer(
The fixture definition object.
:param request:
The fixture request object.
:param exception:
An exception raised in the finalisation of the fixtures.
Use in conftest plugins
=======================

View File

@ -4014,7 +4014,7 @@ def test_pytest_fixture_setup_and_post_finalizer_hook(pytester: Pytester) -> Non
)
def test_exceptions_in_pytest_fixture_setup_and_post_finalizer_hook(
def test_exceptions_in_pytest_fixture_setup_and_pytest_fixture_teardown(
pytester: Pytester,
) -> None:
pytester.makeconftest(
@ -4024,8 +4024,10 @@ def test_exceptions_in_pytest_fixture_setup_and_post_finalizer_hook(
def pytest_fixture_setup(fixturedef):
result = yield
print('SETUP EXCEPTION in {0}: {1}'.format(fixturedef.argname, result.exception))
def pytest_fixture_post_finalizer(fixturedef, exception):
print('TEARDOWN EXCEPTION in {0}: {1}'.format(fixturedef.argname, exception))
@pytest.hookimpl(hookwrapper=True)
def pytest_fixture_teardown(fixturedef):
result = yield
print('TEARDOWN EXCEPTION in {0}: {1}'.format(fixturedef.argname, result.exception))
"""
)
pytester.makepyfile(