add exception spec to pytest_fixture_post_finalizer hook
This commit is contained in:
parent
1f001cd105
commit
5df7641107
|
@ -1023,17 +1023,23 @@ class FixtureDef(Generic[FixtureValue]):
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
exceptions.append(e)
|
exceptions.append(e)
|
||||||
node = request.node
|
node = request.node
|
||||||
node.ihook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
|
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
|
# Even if finalization fails, we invalidate the cached fixture
|
||||||
# value and remove all finalizers because they may be bound methods
|
# value and remove all finalizers because they may be bound methods
|
||||||
# which will keep instances alive.
|
# which will keep instances alive.
|
||||||
self.cached_result = None
|
self.cached_result = None
|
||||||
self._finalizers.clear()
|
self._finalizers.clear()
|
||||||
if len(exceptions) == 1:
|
if final_exception:
|
||||||
raise exceptions[0]
|
raise final_exception
|
||||||
elif len(exceptions) > 1:
|
|
||||||
msg = f'errors while tearing down fixture "{self.argname}" of {node}'
|
|
||||||
raise BaseExceptionGroup(msg, exceptions[::-1])
|
|
||||||
|
|
||||||
def execute(self, request: SubRequest) -> FixtureValue:
|
def execute(self, request: SubRequest) -> FixtureValue:
|
||||||
"""Return the value of this fixture, executing it if not cached."""
|
"""Return the value of this fixture, executing it if not cached."""
|
||||||
|
|
|
@ -861,7 +861,9 @@ def pytest_fixture_setup(
|
||||||
|
|
||||||
|
|
||||||
def pytest_fixture_post_finalizer(
|
def pytest_fixture_post_finalizer(
|
||||||
fixturedef: "FixtureDef[Any]", request: "SubRequest"
|
fixturedef: "FixtureDef[Any]",
|
||||||
|
request: "SubRequest",
|
||||||
|
exception: "BaseException | None",
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Called after fixture teardown, but before the cache is cleared, so
|
"""Called after fixture teardown, but before the cache is cleared, so
|
||||||
the fixture result ``fixturedef.cached_result`` is still available (not
|
the fixture result ``fixturedef.cached_result`` is still available (not
|
||||||
|
@ -871,6 +873,8 @@ def pytest_fixture_post_finalizer(
|
||||||
The fixture definition object.
|
The fixture definition object.
|
||||||
:param request:
|
:param request:
|
||||||
The fixture request object.
|
The fixture request object.
|
||||||
|
:param exception:
|
||||||
|
The list of exceptions received at the end of the fixtures.
|
||||||
|
|
||||||
Use in conftest plugins
|
Use in conftest plugins
|
||||||
=======================
|
=======================
|
||||||
|
|
|
@ -4014,6 +4014,63 @@ def test_pytest_fixture_setup_and_post_finalizer_hook(pytester: Pytester) -> Non
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_exceptions_in_pytest_fixture_setup_and_post_finalizer_hook(
|
||||||
|
pytester: Pytester,
|
||||||
|
) -> None:
|
||||||
|
pytester.makeconftest(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
|
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))
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
pytester.makepyfile(
|
||||||
|
**{
|
||||||
|
"tests/test_fixture_exceptions.py": """
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.fixture(scope='module')
|
||||||
|
def module_teardown_exeption():
|
||||||
|
yield
|
||||||
|
raise ValueError('exeption in module_teardown_exeption')
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def func_teardown_exeption():
|
||||||
|
yield
|
||||||
|
raise ValueError('exeption in func_teardown_exeption')
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def func_setup_exeption():
|
||||||
|
raise ValueError('exeption in func_setup_exeption')
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures(
|
||||||
|
'module_teardown_exeption',
|
||||||
|
'func_teardown_exeption',
|
||||||
|
'func_setup_exeption',
|
||||||
|
)
|
||||||
|
def test_func():
|
||||||
|
pass
|
||||||
|
""",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
result = pytester.runpytest("-s")
|
||||||
|
assert result.ret == 1
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
[
|
||||||
|
"*SETUP EXCEPTION in module_teardown_exeption: None*",
|
||||||
|
"*SETUP EXCEPTION in func_teardown_exeption: None*",
|
||||||
|
"*SETUP EXCEPTION in func_setup_exeption: exeption in func_setup_exeption*",
|
||||||
|
"*TEARDOWN EXCEPTION in func_setup_exeption: None*",
|
||||||
|
"*TEARDOWN EXCEPTION in func_teardown_exeption: exeption in func_teardown_exeption*",
|
||||||
|
"*TEARDOWN EXCEPTION in module_teardown_exeption: exeption in module_teardown_exeption*",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestScopeOrdering:
|
class TestScopeOrdering:
|
||||||
"""Class of tests that ensure fixtures are ordered based on their scopes (#2405)"""
|
"""Class of tests that ensure fixtures are ordered based on their scopes (#2405)"""
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue