Merge pull request #5589 from graingert/fixture-yield-exit-stack
de-emphasize request.addfinalizer Fixes #5587
This commit is contained in:
commit
7440cece59
|
@ -379,6 +379,29 @@ The ``smtp_connection`` connection will be closed after the test finished
|
||||||
execution because the ``smtp_connection`` object automatically closes when
|
execution because the ``smtp_connection`` object automatically closes when
|
||||||
the ``with`` statement ends.
|
the ``with`` statement ends.
|
||||||
|
|
||||||
|
Using the contextlib.ExitStack context manager finalizers will always be called
|
||||||
|
regardless if the fixture *setup* code raises an exception. This is handy to properly
|
||||||
|
close all resources created by a fixture even if one of them fails to be created/acquired:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# content of test_yield3.py
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from .utils import connect
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def equipments():
|
||||||
|
with contextlib.ExitStack() as stack:
|
||||||
|
yield [stack.enter_context(connect(port)) for port in ("C1", "C3", "C28")]
|
||||||
|
|
||||||
|
In the example above, if ``"C28"`` fails with an exception, ``"C1"`` and ``"C3"`` will still
|
||||||
|
be properly closed.
|
||||||
|
|
||||||
Note that if an exception happens during the *setup* code (before the ``yield`` keyword), the
|
Note that if an exception happens during the *setup* code (before the ``yield`` keyword), the
|
||||||
*teardown* code (after the ``yield``) will not be called.
|
*teardown* code (after the ``yield``) will not be called.
|
||||||
|
|
||||||
|
@ -407,27 +430,34 @@ Here's the ``smtp_connection`` fixture changed to use ``addfinalizer`` for clean
|
||||||
return smtp_connection # provide the fixture value
|
return smtp_connection # provide the fixture value
|
||||||
|
|
||||||
|
|
||||||
|
Here's the ``equipments`` fixture changed to use ``addfinalizer`` for cleanup:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# content of test_yield3.py
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
import functools
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from .utils import connect
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def equipments(request):
|
||||||
|
r = []
|
||||||
|
for port in ("C1", "C3", "C28"):
|
||||||
|
cm = connect(port)
|
||||||
|
equip = cm.__enter__()
|
||||||
|
request.addfinalizer(functools.partial(cm.__exit__, None, None, None))
|
||||||
|
r.append(equip)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
Both ``yield`` and ``addfinalizer`` methods work similarly by calling their code after the test
|
Both ``yield`` and ``addfinalizer`` methods work similarly by calling their code after the test
|
||||||
ends, but ``addfinalizer`` has two key differences over ``yield``:
|
ends. Of course, if an exception happens before the finalize function is registered then it
|
||||||
|
will not be executed.
|
||||||
1. It is possible to register multiple finalizer functions.
|
|
||||||
|
|
||||||
2. Finalizers will always be called regardless if the fixture *setup* code raises an exception.
|
|
||||||
This is handy to properly close all resources created by a fixture even if one of them
|
|
||||||
fails to be created/acquired::
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def equipments(request):
|
|
||||||
r = []
|
|
||||||
for port in ('C1', 'C3', 'C28'):
|
|
||||||
equip = connect(port)
|
|
||||||
request.addfinalizer(equip.disconnect)
|
|
||||||
r.append(equip)
|
|
||||||
return r
|
|
||||||
|
|
||||||
In the example above, if ``"C28"`` fails with an exception, ``"C1"`` and ``"C3"`` will still
|
|
||||||
be properly closed. Of course, if an exception happens before the finalize function is
|
|
||||||
registered then it will not be executed.
|
|
||||||
|
|
||||||
|
|
||||||
.. _`request-context`:
|
.. _`request-context`:
|
||||||
|
|
Loading…
Reference in New Issue