Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update fixtures.rst w/ finalizer order #10171

Merged
merged 13 commits into from
Aug 15, 2022
77 changes: 75 additions & 2 deletions doc/en/how-to/fixtures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -735,8 +735,81 @@ does offer some nuances for when you're in a pinch.
.. code-block:: pytest

$ pytest -q test_emaillib.py
. [100%]
1 passed in 0.12s
. [100%]
1 passed in 0.12s

Note on finalizer order
""""""""""""""""""""""""

Finalizers are executed in a first-in-last-out order.
For yield fixtures, the first teardown code to run is from the right-most fixture, i.e. the last test parameter.

.. regendoc:wipe

aizpurua23a marked this conversation as resolved.
Show resolved Hide resolved
.. code-block:: python

import pytest


def test_bar(fix_w_yield1, fix_w_yield2):
print("test_bar")


@pytest.fixture
def fix_w_yield1():
yield
print("after_yield_1")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a good example I think, because of course both prints will be executed sequentially (pytest cannot mess with how Python executes code within a function).

I suggest we use two fixtures:

    @pytest.fixture
    def fix_w_yield1():
        yield
        print("after_yield_1")

    @pytest.fixture
    def fix_w_yield2():
        yield
        print("after_yield_2")

And then make test_bar request both:

    def test_bar(fix_w_yield1, fix_w_yield2):
        print("test_bar")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see.
The execution order with the addfinalizer method is evident, so the execution order only needs clarification in this case.
Lovely! How about now?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can keep the examples using addfinalizer as they are useful for comparision, so could you please bring them back? Thanks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing, re-added.



@pytest.fixture
def fix_w_yield2():
yield
print("after_yield_2")


.. code-block:: pytest

$ pytest test_module.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
collected 1 item

test_module.py test_bar
.after_yield_2
after_yield_1



For finalizers, the first fixture to run is last call to `request.addfinalizer`.

.. code-block:: python

import pytest


@pytest.fixture
def fix_w_finalizers(request):
request.addfinalizer(partial(print, "finalizer_2"))
request.addfinalizer(partial(print, "finalizer_1"))


def test_bar(fix_w_finalizers):
print("test_bar")


.. code-block:: pytest

$ pytest test_module.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
collected 1 item

test_module.py test_bar
.finalizer_1
finalizer_2

This is so because yield fixtures use `addfinalizer` behind the scenes: when the fixture executes, `addfinalizer` registers a function that resumes the generator, which in turn calls the teardown code.


.. _`safe teardowns`:

Expand Down