Skip to content
Merged
58 changes: 55 additions & 3 deletions doc/en/how-to/fixtures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -732,12 +732,64 @@ Here's how the previous example would look using the ``addfinalizer`` method:
It's a bit longer than yield fixtures and a bit more complex, but it
does offer some nuances for when you're in a pinch.

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

Finalizers are executed in a first-in-last-out order, while operations after yield are executed sequentially.
Copy link
Member

Choose a reason for hiding this comment

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

while operations after yield are executed sequentially.

This sentence needs tweaking if we look at the output. 👍

Finalizers are executed in FILO order, that is correct, because we can see that adding fin2 and fin1 we get back fn1, fn2 in the output.

But the same can be said for the yield fixtures: we request them in fix_w_yield1, fix_w_yield2 order, and we get fix_w_yield2 and fix_w_yield1 in the output.

That's because yield fixtures use addfinalizer behind the scenes: when the fixture executes, we register a function with addfinalizer that resumes the generator (which in turn calls the teardown code).

Not sure how to "fix" this sentence, or perhaps we can explain something along the lines of what I wrote above, if that makes things clearer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, that makes sense.
I tried to explain how both examples show the similar FILO behavior, plus added the hidden usage of addfinalizer after the examples.
What do you think?

Consider the differences in the following examples:

.. code-block:: python

import pytest
from functools import partial


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


@pytest.fixture
def fix_w_yield():
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.

print("after_yield_2")


def test_foo(fix_w_finalizers):
print("test_foo")
pass


def test_bar(fix_w_yield):
print("test_bar")
pass



.. code-block:: pytest

$ pytest -q test_emaillib.py
. [100%]
1 passed in 0.12s
$ pytest test_module.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
collected 2 items

main.py
test_foo
.finalizer_1
finalizer_2

test_bar
.after_yield_1
after_yield_2


.. code-block:: pytest

$ pytest -q test_emaillib.py
. [100%]
1 passed in 0.12s
.. _`safe teardowns`:

Safe teardowns
Expand Down