Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/14992.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed fixture setup order so fixtures requested by ``usefixtures`` remain ahead of dependencies of earlier autouse fixtures.
14 changes: 9 additions & 5 deletions src/_pytest/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -1985,14 +1985,18 @@ def sort_by_scope(arg_name: str) -> Scope:
else:
return fixturedefs[-1]._scope

fixturenames_closure = sorted(
traverse_fixture_closure(
# Keep the initial fixtures at the front of the closure. Their order
# is observable by hooks such as pytest_generate_tests.
fixturenames_closure = list(initialnames)
fixturenames_closure.extend(
argname
for argname in traverse_fixture_closure(
initialnames,
getfixturedefs=getfixturedefs,
),
key=sort_by_scope,
reverse=True,
)
if argname not in initialnames
)
fixturenames_closure.sort(key=sort_by_scope, reverse=True)

return fixturenames_closure, arg2fixturedefs

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ def b(a):


def test(b, request):
assert request.fixturenames == ["b", "a", "request", "dynamic"]
assert request.fixturenames == ["b", "request", "a", "dynamic"]
51 changes: 47 additions & 4 deletions testing/python/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -2529,6 +2529,49 @@ def test_hello(arg1):
reprec = pytester.inline_run()
reprec.assertoutcome(passed=1)

def test_usefixtures_before_autouse_dependencies(self, pytester: Pytester) -> None:
pytester.makepyfile(
"""
import pytest

order = []

@pytest.fixture
def precondition():
order.append("precondition")

@pytest.fixture
def subject_deps(precondition):
order.append("subject_deps")

@pytest.fixture(autouse=True)
def subject(subject_deps):
order.append("subject")

@pytest.fixture
def guard():
order.append("guard")

def pytest_generate_tests(metafunc):
names = metafunc.fixturenames
if "subject" in names:
names.remove("subject")
names.append("subject")

@pytest.mark.usefixtures("guard")
class TestOrdering:
def test_guard_is_set_up_first(self):
assert order == [
"guard",
"precondition",
"subject_deps",
"subject",
]
"""
)
result = pytester.runpytest()
result.assert_outcomes(passed=1)

@pytest.mark.parametrize("param1", ["", "params=[1]"], ids=["p00", "p01"])
@pytest.mark.parametrize("param2", ["", "params=[1]"], ids=["p10", "p11"])
def test_ordering_dependencies_torndown_first(
Expand Down Expand Up @@ -4673,8 +4716,8 @@ def test_foo(f1, p1, m1, f2, s1):
# Actual fixture execution differs from static order: dependent
# fixtures must be created first ("my_tmp_path").
assert fixture_order == [
"my_tmp_path_factory",
"s1",
"my_tmp_path_factory",
"p1",
"m1",
"my_tmp_path",
Expand All @@ -4689,13 +4732,13 @@ def test_foo(f1, p1, m1, f2, s1):
# Static order of fixtures based on their scope and position in the
# parameter list.
assert request.fixturenames == [
"my_tmp_path_factory",
"s1",
"my_tmp_path_factory",
"p1",
"m1",
"f1",
"my_tmp_path",
"f2",
"my_tmp_path",
]
result = pytester.runpytest("-vv")
result.assert_outcomes(passed=1)
Expand Down Expand Up @@ -5871,7 +5914,7 @@ def test_circular_deps(fix_a, fix_x):
)
items, _hookrec = pytester.inline_genitems()
assert isinstance(items[0], Function)
assert items[0].fixturenames == ["fix_a", "fix_b", "fix_x", "fix_y", "fix_z"]
assert items[0].fixturenames == ["fix_a", "fix_x", "fix_b", "fix_y", "fix_z"]


def test_fixture_closure_handles_diamond_dependencies(pytester: Pytester) -> None:
Expand Down