Skip to content

fixture scope "package" wrong teardown order with nested packages #11480

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

Closed
dszady-rtb opened this issue Oct 5, 2023 · 2 comments
Closed

fixture scope "package" wrong teardown order with nested packages #11480

dszady-rtb opened this issue Oct 5, 2023 · 2 comments
Labels
topic: fixtures anything involving fixtures directly or indirectly

Comments

@dszady-rtb
Copy link

I have a following tests structure
image
with such fixtures:

# package_1/conftest.py
import pytest

@pytest.fixture(autouse=True, scope="package")
def some_fixture1():
    print("\n------------Setup package 1")

    yield

    print("\n------------ Teardown package 1")

and

# package_2/conftest.py

import pytest

@pytest.fixture(autouse=True, scope="package")
def some_fixture2():
    print("\n------------Setup package 2")

    yield

    print("\n------------Teardown package 2")

and tests

# package_1/nested_dir/test_package_1.py

def test_1():
    print("\n------------running test_1")
    assert True

and

# package_2/test_package_2.py

def test_1():
    print("\n------------running test_2")
    assert True

When I run the tests I would expect the package_1 teardown to happen before setup of package_2, but this is not what is happening. Instead, teardown happens at the end. See output:
image

But when I move the test file out of the nested_dir so the file structure looks like this:
image
the teardown order seems to be correct.
image

Anyways I would love to have a correct teardown order for nested tests file structure.

Pip list output:

(venv) ➜  pythonProject pip list          
Package    Version
---------- -------
iniconfig  2.0.0
packaging  23.2
pip        22.3.1
pluggy     1.3.0
pytest     7.4.2
setuptools 65.5.1
wheel      0.38.4

Operating system:
Macos Ventura 13.0

@Zac-HD Zac-HD added the topic: fixtures anything involving fixtures directly or indirectly label Oct 9, 2023
@robert-iancu-jr
Copy link

Hello Dominik,

I analyzed a little bit your case and here are my conclusions.

The Theory

The first step in executing a test suite with pytest implies the gathering of a list of each test method/function that must be called, and store them in a list named items in the order they are found.

Test Hooks

For each item in the items list, 3 hooks are executed:

  • pytest_runtest_setup
  • pytest_runtest_call
  • pytest_runtest_teardown

The 1st hook

During the pytest_runtest_setup hook, the fixtures are initialized in the following order:

  1. Session's fixtures
  2. Package's fixtures
  3. Module's fixtures
  4. Method's fixtures

The fixture is initialized at the step 4 (Method's fixtures), not sure why.

Note: In the pytest_runtest_setup hook, the session step is executed only for the first item.

The 2nd hook

The pytest_runtest_call hook is not really relevant for your problem, so on brief, this one just executes the test method/function.

The 3rd hook

Finally, in the pytest_runtest_teardown hook, the fixtures are teardown in the reverse order:

  1. Method's fixtures
  2. Module's fixtures
  3. Package's fixtures
  4. Session's fixtures

This time, the teardown of the fixture is not executed at the method's layer, instead it's executed at the package's layer.

Note: In the pytest_runtest_teardown hook, the session step is executed only for the last item.

Examples

Now, let's use your examples to highlight what's going on.

Example 1: test file in the same package as conftest

items = [Func test_1. Func test_2]

Apply the hooks on Func test_1

  • pytest_runtest_setup:
    • Session: nothing
    • package_1 = nothing
    • test_package_1.py = nothing
    • test_1 = Setup package 1
  • pytest_runtest_teardown:
    • test_1 = nothing
    • test_package_1.py = nothing
    • package_1 = Teardown package 1

Apply the hooks on Func test_2:

  • pytest_runtest_setup:
    • package_2 = nothing
    • test_package_2.py = nothing
    • test_2 = Setup package 2
  • pytest_runtest_teardown:
    • test_2 = nothing
    • test_package_2.py = nothing
    • package_2 = Teardown package 2
    • Session: nothing

So everything was good, because each test was in the same package as the used conftest.

Example 2: test file in a subpackage of conftest's package

items = [Func test_1. Func test_2]

Apply the hooks on Func test_1

  • pytest_runtest_setup:
    • Session: nothing
    • nested_dir = nothing
    • test_package_1.py = nothing
    • test_1 = Setup package 1
  • pytest_runtest_teardown:
    • test_1 = nothing
    • test_package_1.py = nothing
    • nested_dir = nothing

Apply the hooks on Func test_2:

  • pytest_runtest_setup:
    • package_2 = nothing
    • test_package_2.py = nothing
    • test_2 = Setup package 2
  • pytest_runtest_teardown:
    • test_2 = nothing
    • test_package_2.py = nothing
    • package_2 = Teardown package 2
    • Session: Teardown package 1

As you can see, the conftest is in the package_1, not in nested_dir. Meaning that the teardown of Func test_1 never goes to package_1. Thus, the teardown of the conftest from package_1 will be managed in the session's teardown, which is executed in the last item from the list.

Solution

The most obvious solution would be to include all the parent packages in the stack, such that the nodes of the Func test_1 to be [Session, package_1, nested_dir, test_package_1, test_1], instead of just [Session, nested_dir, test_package_1, test_1].
Unfortunately, this could impact other areas, so it's better if a contributor could share their opinion about this.

@bluetech
Copy link
Member

bluetech commented Jan 4, 2024

This is fixed by #11646, will will be included in pytest 8.0.0. The fix is as @robert-iancu-jr said -- now the Dir is nested under the Package. You can test the release candidate with pip install pytest==8.0.0rc1.

@bluetech bluetech closed this as completed Jan 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: fixtures anything involving fixtures directly or indirectly
Projects
None yet
Development

No branches or pull requests

4 participants