Skip to content

Commit 7538680

Browse files
Merge pull request #1847 from nicoddemus/parametrize-session-scopes
Fix code which guesses parametrized scope based on arguments
2 parents 847067f + 53a0e2b commit 7538680

File tree

3 files changed

+153
-46
lines changed

3 files changed

+153
-46
lines changed

CHANGELOG.rst

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
* Fix regression when ``importorskip`` is used at module level (`#1822`_).
55
Thanks `@jaraco`_ and `@The-Compiler`_ for the report and `@nicoddemus`_ for the PR.
66

7-
*
7+
* Fix parametrization scope when session fixtures are used in conjunction
8+
with normal parameters in the same call (`#1832`_).
9+
Thanks `@The-Compiler`_ for the report, `@Kingdread`_ and `@nicoddemus`_ for the PR.
810

911
* Fix loader error when running ``pytest`` embedded in a zipfile.
1012
Thanks `@mbachry`_ for the PR.
@@ -13,10 +15,15 @@
1315

1416
*
1517

18+
*
19+
20+
*
1621

22+
.. _@Kingdread: https://github.com/Kingdread
1723
.. _@mbachry: https://github.com/mbachry
1824

1925
.. _#1822: https://github.com/pytest-dev/pytest/issues/1822
26+
.. _#1832: https://github.com/pytest-dev/pytest/issues/1832
2027

2128

2229
3.0.0

_pytest/python.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -802,14 +802,8 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None,
802802
newkeywords = [{newmark.markname: newmark}]
803803

804804
if scope is None:
805-
if self._arg2fixturedefs:
806-
# Takes the most narrow scope from used fixtures
807-
fixtures_scopes = [fixturedef[0].scope for fixturedef in self._arg2fixturedefs.values()]
808-
for scope in reversed(scopes):
809-
if scope in fixtures_scopes:
810-
break
811-
else:
812-
scope = 'function'
805+
scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
806+
813807
scopenum = scopes.index(scope)
814808
valtypes = {}
815809
for arg in argnames:
@@ -889,6 +883,30 @@ def addcall(self, funcargs=None, id=NOTSET, param=NOTSET):
889883
self._calls.append(cs)
890884

891885

886+
def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
887+
"""Find the most appropriate scope for a parametrized call based on its arguments.
888+
889+
When there's at least one direct argument, always use "function" scope.
890+
891+
When a test function is parametrized and all its arguments are indirect
892+
(e.g. fixtures), return the most narrow scope based on the fixtures used.
893+
894+
Related to issue #1832, based on code posted by @Kingdread.
895+
"""
896+
from _pytest.fixtures import scopes
897+
indirect_as_list = isinstance(indirect, (list, tuple))
898+
all_arguments_are_fixtures = indirect is True or \
899+
indirect_as_list and len(indirect) == argnames
900+
if all_arguments_are_fixtures:
901+
fixturedefs = arg2fixturedefs or {}
902+
used_scopes = [fixturedef[0].scope for name, fixturedef in fixturedefs.items()]
903+
if used_scopes:
904+
# Takes the most narrow scope from used fixtures
905+
for scope in reversed(scopes):
906+
if scope in used_scopes:
907+
return scope
908+
909+
return 'function'
892910

893911

894912
def _idval(val, argname, idx, idfn, config=None):

testing/python/metafunc.py

Lines changed: 119 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -930,43 +930,6 @@ def test_checklength():
930930
reprec = testdir.inline_run()
931931
reprec.assertoutcome(passed=5)
932932

933-
def test_parametrize_issue634(self, testdir):
934-
testdir.makepyfile('''
935-
import pytest
936-
937-
@pytest.fixture(scope='module')
938-
def foo(request):
939-
print('preparing foo-%d' % request.param)
940-
return 'foo-%d' % request.param
941-
942-
943-
def test_one(foo):
944-
pass
945-
946-
947-
def test_two(foo):
948-
pass
949-
950-
951-
test_two.test_with = (2, 3)
952-
953-
954-
def pytest_generate_tests(metafunc):
955-
params = (1, 2, 3, 4)
956-
if not 'foo' in metafunc.fixturenames:
957-
return
958-
959-
test_with = getattr(metafunc.function, 'test_with', None)
960-
if test_with:
961-
params = test_with
962-
metafunc.parametrize('foo', params, indirect=True)
963-
964-
''')
965-
result = testdir.runpytest("-s")
966-
output = result.stdout.str()
967-
assert output.count('preparing foo-2') == 1
968-
assert output.count('preparing foo-3') == 1
969-
970933
def test_parametrize_issue323(self, testdir):
971934
testdir.makepyfile("""
972935
import pytest
@@ -1047,6 +1010,125 @@ def test_foo(x):
10471010
assert expectederror in failures[0].longrepr.reprcrash.message
10481011

10491012

1013+
class TestMetafuncFunctionalAuto:
1014+
"""
1015+
Tests related to automatically find out the correct scope for parametrized tests (#1832).
1016+
"""
1017+
1018+
def test_parametrize_auto_scope(self, testdir):
1019+
testdir.makepyfile('''
1020+
import pytest
1021+
1022+
@pytest.fixture(scope='session', autouse=True)
1023+
def fixture():
1024+
return 1
1025+
1026+
@pytest.mark.parametrize('animal', ["dog", "cat"])
1027+
def test_1(animal):
1028+
assert animal in ('dog', 'cat')
1029+
1030+
@pytest.mark.parametrize('animal', ['fish'])
1031+
def test_2(animal):
1032+
assert animal == 'fish'
1033+
1034+
''')
1035+
result = testdir.runpytest()
1036+
result.stdout.fnmatch_lines(['* 3 passed *'])
1037+
1038+
def test_parametrize_auto_scope_indirect(self, testdir):
1039+
testdir.makepyfile('''
1040+
import pytest
1041+
1042+
@pytest.fixture(scope='session')
1043+
def echo(request):
1044+
return request.param
1045+
1046+
@pytest.mark.parametrize('animal, echo', [("dog", 1), ("cat", 2)], indirect=['echo'])
1047+
def test_1(animal, echo):
1048+
assert animal in ('dog', 'cat')
1049+
assert echo in (1, 2, 3)
1050+
1051+
@pytest.mark.parametrize('animal, echo', [('fish', 3)], indirect=['echo'])
1052+
def test_2(animal, echo):
1053+
assert animal == 'fish'
1054+
assert echo in (1, 2, 3)
1055+
''')
1056+
result = testdir.runpytest()
1057+
result.stdout.fnmatch_lines(['* 3 passed *'])
1058+
1059+
def test_parametrize_auto_scope_override_fixture(self, testdir):
1060+
testdir.makepyfile('''
1061+
import pytest
1062+
1063+
@pytest.fixture(scope='session', autouse=True)
1064+
def animal():
1065+
return 'fox'
1066+
1067+
@pytest.mark.parametrize('animal', ["dog", "cat"])
1068+
def test_1(animal):
1069+
assert animal in ('dog', 'cat')
1070+
''')
1071+
result = testdir.runpytest()
1072+
result.stdout.fnmatch_lines(['* 2 passed *'])
1073+
1074+
def test_parametrize_all_indirects(self, testdir):
1075+
testdir.makepyfile('''
1076+
import pytest
1077+
1078+
@pytest.fixture()
1079+
def animal(request):
1080+
return request.param
1081+
1082+
@pytest.fixture(scope='session')
1083+
def echo(request):
1084+
return request.param
1085+
1086+
@pytest.mark.parametrize('animal, echo', [("dog", 1), ("cat", 2)], indirect=True)
1087+
def test_1(animal, echo):
1088+
assert animal in ('dog', 'cat')
1089+
assert echo in (1, 2, 3)
1090+
1091+
@pytest.mark.parametrize('animal, echo', [("fish", 3)], indirect=True)
1092+
def test_2(animal, echo):
1093+
assert animal == 'fish'
1094+
assert echo in (1, 2, 3)
1095+
''')
1096+
result = testdir.runpytest()
1097+
result.stdout.fnmatch_lines(['* 3 passed *'])
1098+
1099+
def test_parametrize_issue634(self, testdir):
1100+
testdir.makepyfile('''
1101+
import pytest
1102+
1103+
@pytest.fixture(scope='module')
1104+
def foo(request):
1105+
print('preparing foo-%d' % request.param)
1106+
return 'foo-%d' % request.param
1107+
1108+
def test_one(foo):
1109+
pass
1110+
1111+
def test_two(foo):
1112+
pass
1113+
1114+
test_two.test_with = (2, 3)
1115+
1116+
def pytest_generate_tests(metafunc):
1117+
params = (1, 2, 3, 4)
1118+
if not 'foo' in metafunc.fixturenames:
1119+
return
1120+
1121+
test_with = getattr(metafunc.function, 'test_with', None)
1122+
if test_with:
1123+
params = test_with
1124+
metafunc.parametrize('foo', params, indirect=True)
1125+
''')
1126+
result = testdir.runpytest("-s")
1127+
output = result.stdout.str()
1128+
assert output.count('preparing foo-2') == 1
1129+
assert output.count('preparing foo-3') == 1
1130+
1131+
10501132
class TestMarkersWithParametrization:
10511133
pytestmark = pytest.mark.issue308
10521134
def test_simple_mark(self, testdir):

0 commit comments

Comments
 (0)