Skip to content

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

Closed

Description

@dszady-rtbh

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

Activity

  1. added
    topic: fixturesanything involving fixtures directly or indirectly
    on Oct 9, 2023
  2. robert-iancu-jr commented on Oct 10, 2023

    @robert-iancu-jr

    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.

  3. bluetech commented on Jan 4, 2024

    @bluetech
    Member

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    topic: fixturesanything involving fixtures directly or indirectly

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions