Skip to content

Commit

Permalink
move fixture finalizing to standalone hook
Browse files Browse the repository at this point in the history
  • Loading branch information
ShurikMen committed Mar 27, 2024
1 parent d7bf600 commit 90172a2
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 30 deletions.
49 changes: 25 additions & 24 deletions src/_pytest/fixtures.py
Expand Up @@ -1014,31 +1014,8 @@ def addfinalizer(self, finalizer: Callable[[], object]) -> None:
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:
finalizer = functools.partial(self.finish, request=request)
Expand Down Expand Up @@ -1132,6 +1109,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)

Check warning on line 1121 in src/_pytest/fixtures.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/fixtures.py#L1120-L1121

Added lines #L1120 - L1121 were not covered by tests
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]

Check warning on line 1130 in src/_pytest/fixtures.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/fixtures.py#L1130

Added line #L1130 was not covered by tests
elif len(exceptions) > 1:
msg = f'errors while tearing down fixture "{fixturedef.argname}" of {node}'
raise BaseExceptionGroup(msg, exceptions[::-1])

Check warning on line 1133 in src/_pytest/fixtures.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/fixtures.py#L1132-L1133

Added lines #L1132 - L1133 were not covered by tests


def wrap_function_to_error_out_if_called_directly(
function: FixtureFunction,
fixture_marker: "FixtureFunctionMarker",
Expand Down
22 changes: 19 additions & 3 deletions src/_pytest/hookspec.py
Expand Up @@ -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
Expand All @@ -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
=======================
Expand Down
8 changes: 5 additions & 3 deletions testing/python/fixtures.py
Expand Up @@ -4014,7 +4014,7 @@ def test_func(my_fixture):
)


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(
Expand All @@ -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(
Expand Down

0 comments on commit 90172a2

Please sign in to comment.