Skip to content
Merged
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/3941.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix bug where indirect parametrization would consider the scope of all fixtures used by the test function to determine the parametrization scope, and not only the scope of the fixtures being parametrized.
15 changes: 10 additions & 5 deletions src/_pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -1141,13 +1141,18 @@ def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
"""
from _pytest.fixtures import scopes

indirect_as_list = isinstance(indirect, (list, tuple))
all_arguments_are_fixtures = (
indirect is True or indirect_as_list and len(indirect) == argnames
)
if isinstance(indirect, (list, tuple)):
all_arguments_are_fixtures = len(indirect) == len(argnames)
else:
all_arguments_are_fixtures = bool(indirect)

if all_arguments_are_fixtures:
fixturedefs = arg2fixturedefs or {}
used_scopes = [fixturedef[0].scope for name, fixturedef in fixturedefs.items()]
used_scopes = [
fixturedef[0].scope
for name, fixturedef in fixturedefs.items()
if name in argnames
]
if used_scopes:
# Takes the most narrow scope from used fixtures
for scope in reversed(scopes):
Expand Down
79 changes: 79 additions & 0 deletions testing/python/metafunc.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,52 @@ def func(x):
except ValueError as ve:
assert "has an unsupported scope value 'doggy'" in str(ve)

def test_find_parametrized_scope(self):
"""unittest for _find_parametrized_scope (#3941)"""
from _pytest.python import _find_parametrized_scope

@attr.s
class DummyFixtureDef(object):
scope = attr.ib()

fixtures_defs = dict(
session_fix=[DummyFixtureDef("session")],
package_fix=[DummyFixtureDef("package")],
module_fix=[DummyFixtureDef("module")],
class_fix=[DummyFixtureDef("class")],
func_fix=[DummyFixtureDef("function")],
)

# use arguments to determine narrow scope; the cause of the bug is that it would look on all
# fixture defs given to the method
def find_scope(argnames, indirect):
return _find_parametrized_scope(argnames, fixtures_defs, indirect=indirect)

assert find_scope(["func_fix"], indirect=True) == "function"
assert find_scope(["class_fix"], indirect=True) == "class"
assert find_scope(["module_fix"], indirect=True) == "module"
assert find_scope(["package_fix"], indirect=True) == "package"
assert find_scope(["session_fix"], indirect=True) == "session"

assert find_scope(["class_fix", "func_fix"], indirect=True) == "function"
assert find_scope(["func_fix", "session_fix"], indirect=True) == "function"
assert find_scope(["session_fix", "class_fix"], indirect=True) == "class"
assert find_scope(["package_fix", "session_fix"], indirect=True) == "package"
assert find_scope(["module_fix", "session_fix"], indirect=True) == "module"

# when indirect is False or is not for all scopes, always use function
assert find_scope(["session_fix", "module_fix"], indirect=False) == "function"
assert (
find_scope(["session_fix", "module_fix"], indirect=["module_fix"])
== "function"
)
assert (
find_scope(
["session_fix", "module_fix"], indirect=["session_fix", "module_fix"]
)
== "module"
)

def test_parametrize_and_id(self):
def func(x, y):
pass
Expand Down Expand Up @@ -1383,6 +1429,39 @@ def test_2(animal, echo):
result = testdir.runpytest()
result.stdout.fnmatch_lines(["* 3 passed *"])

def test_parametrize_some_arguments_auto_scope(self, testdir, monkeypatch):
"""Integration test for (#3941)"""
class_fix_setup = []
monkeypatch.setattr(sys, "class_fix_setup", class_fix_setup, raising=False)
func_fix_setup = []
monkeypatch.setattr(sys, "func_fix_setup", func_fix_setup, raising=False)

testdir.makepyfile(
"""
import pytest
import sys

@pytest.fixture(scope='class', autouse=True)
def class_fix(request):
sys.class_fix_setup.append(request.param)

@pytest.fixture(autouse=True)
def func_fix():
sys.func_fix_setup.append(True)

@pytest.mark.parametrize('class_fix', [10, 20], indirect=True)
class Test:
def test_foo(self):
pass
def test_bar(self):
pass
"""
)
result = testdir.runpytest_inprocess()
result.stdout.fnmatch_lines(["* 4 passed in *"])
assert func_fix_setup == [True] * 4
assert class_fix_setup == [10, 20]

def test_parametrize_issue634(self, testdir):
testdir.makepyfile(
"""
Expand Down