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
20 changes: 19 additions & 1 deletion src/_pytest/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -1505,6 +1505,23 @@ def getfixtureclosure(
# to re-discover fixturedefs again for each fixturename
# (discovering matching fixtures for a given name/node is expensive).

def dependent_fixtures_argnames(
fixture_defs: Sequence[FixtureDef[Any]],
) -> List[str]:
# Initialize with the argnames of the last fixture
dependent_argnames = list(fixture_defs[-1].argnames)
# Iterate over the list in reverse order, skipping the last element already processed.
for index, current_fixture in enumerate(
reversed(fixture_defs[:-1]), start=1
):
if current_fixture.argname in fixture_defs[-index].argnames:
Copy link

Choose a reason for hiding this comment

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

does the only fixture_defs[-index] not all indexes?

for argname in current_fixture.argnames:
if argname not in dependent_argnames:
dependent_argnames.append(argname)
else:
break
return dependent_argnames

fixturenames_closure = list(initialnames)

arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]] = {}
Expand All @@ -1519,7 +1536,8 @@ def getfixtureclosure(
fixturedefs = self.getfixturedefs(argname, parentnode)
if fixturedefs:
arg2fixturedefs[argname] = fixturedefs
for arg in fixturedefs[-1].argnames:
argnames = dependent_fixtures_argnames(fixturedefs)
for arg in argnames:
if arg not in fixturenames_closure:
fixturenames_closure.append(arg)

Expand Down
47 changes: 47 additions & 0 deletions testing/python/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -1785,6 +1785,53 @@ def test_collect_custom_items(self, pytester: Pytester) -> None:
result = pytester.runpytest("foo")
result.stdout.fnmatch_lines(["*passed*"])

def test_get_fixture_clousure_override_conftest_module_and_class(
self, pytester: Pytester
):
pytester.makepyfile(
"""\
import pytest

@pytest.fixture
def hello(param, hello):
return "module"
class TestClass(object):
@pytest.fixture
def hello(self, hello):
return "class"
@pytest.mark.parametrize("param", ["foo"])
def test_hello(self, item, hello, fm):
print(item)
clousurelist, _ = fm.getfixtureclosure(item, ("hello",), {})
assert clousurelist == ["hello", "param", "request"]
"""
)
reprec = pytester.inline_run("-s")
reprec.assertoutcome(passed=1)

def test_get_fixture_clousure_override_module_and_class(self, pytester: Pytester):
pytester.makepyfile(
"""\
import pytest

@pytest.fixture
def hello(param):
return "module"
class TestClass(object):
@pytest.fixture
def hello(self, hello):
return "class"
@pytest.mark.parametrize("param", ["foo"])
def test_hello(self, item, hello, fm):
print(item)
clousurelist, _ = fm.getfixtureclosure(item, ("hello",), {})
print(clousurelist)
assert clousurelist == ["hello", "param"]
"""
)
reprec = pytester.inline_run("-s")
reprec.assertoutcome(passed=1)


class TestAutouseDiscovery:
@pytest.fixture
Expand Down
44 changes: 44 additions & 0 deletions testing/test_mark.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,50 @@ def test_func(a, b):
assert result.ret == 0


def test_parametrize_overriden_extended_fixture(pytester: Pytester) -> None:
"""Overriden fixtures must pass over dependend fixtures for parameterization (#12091)"""
pytester.makeconftest(
"""
import pytest

@pytest.fixture
def not_needed():
assert False, "Should not be called!"

@pytest.fixture
def main(foo):
assert False, "Should not be called!"

"""
)

py_file = pytester.makepyfile(
"""\
import pytest

@pytest.fixture
def param() -> int:
return 1

@pytest.fixture
def main(param: int) -> int:
return sum(range(param + 1))


class TestFoo:
@pytest.fixture
def main(self, main: int) -> int:
return main

@pytest.mark.parametrize("param", [2])
def test_foo(self, main: int) -> None:
assert main == 3
"""
)
result = pytester.runpytest(py_file)
assert result.ret == 0


def test_parametrize_iterator(pytester: Pytester) -> None:
"""`parametrize` should work with generators (#5354)."""
py_file = pytester.makepyfile(
Expand Down