Merge 28169e96bf
into 63dfa4bb84
This commit is contained in:
commit
c3b2ec7221
|
@ -785,6 +785,8 @@ Session related reporting hooks:
|
|||
.. autofunction:: pytest_terminal_summary
|
||||
.. hook:: pytest_fixture_setup
|
||||
.. autofunction:: pytest_fixture_setup
|
||||
.. hook:: pytest_fixture_teardown
|
||||
.. autofunction:: pytest_fixture_teardown
|
||||
.. hook:: pytest_fixture_post_finalizer
|
||||
.. autofunction:: pytest_fixture_post_finalizer
|
||||
.. hook:: pytest_warning_recorded
|
||||
|
|
|
@ -1015,25 +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
|
||||
node.ihook.pytest_fixture_post_finalizer(fixturedef=self, 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.
|
||||
self.cached_result = None
|
||||
self._finalizers.clear()
|
||||
if len(exceptions) == 1:
|
||||
raise exceptions[0]
|
||||
elif len(exceptions) > 1:
|
||||
msg = f'errors while tearing down fixture "{self.argname}" of {node}'
|
||||
raise BaseExceptionGroup(msg, exceptions[::-1])
|
||||
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."""
|
||||
|
@ -1145,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",
|
||||
|
|
|
@ -889,8 +889,28 @@ def pytest_fixture_setup(
|
|||
"""
|
||||
|
||||
|
||||
def pytest_fixture_post_finalizer(
|
||||
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",
|
||||
) -> None:
|
||||
"""Called after fixture teardown, but before the cache is cleared, so
|
||||
the fixture result ``fixturedef.cached_result`` is still available (not
|
||||
|
|
|
@ -4054,6 +4054,65 @@ def test_pytest_fixture_setup_and_post_finalizer_hook(pytester: Pytester) -> Non
|
|||
)
|
||||
|
||||
|
||||
def test_exceptions_in_pytest_fixture_setup_and_pytest_fixture_teardown(
|
||||
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))
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_fixture_teardown(fixturedef):
|
||||
result = yield
|
||||
print('TEARDOWN EXCEPTION in {0}: {1}'.format(fixturedef.argname, result.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 of tests that ensure fixtures are ordered based on their scopes (#2405)"""
|
||||
|
||||
|
|
Loading…
Reference in New Issue