Skip to content

Commit 62d6d64

Browse files
authored
Merge pull request #31 from YannickJadoul/nested-lazy-fixtures
Enable lazy fixtures with parametrized subfixtures and lazy fixtures in subfixtures
2 parents f7fb1a7 + 46c2842 commit 62d6d64

File tree

2 files changed

+148
-38
lines changed

2 files changed

+148
-38
lines changed

pytest_lazyfixture.py

Lines changed: 57 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
# -*- coding: utf-8 -*-
2-
import os
32
import sys
43
import types
54
from collections import defaultdict
6-
import py
75
import pytest
86
from _pytest.fixtures import scopenum_function
97

@@ -43,13 +41,28 @@ def fill(request):
4341
return fill
4442

4543

44+
@pytest.hookimpl(tryfirst=True)
45+
def pytest_fixture_setup(fixturedef, request):
46+
val = getattr(request, 'param', None)
47+
if is_lazy_fixture(val):
48+
request.param = request.getfixturevalue(val.name)
49+
50+
4651
def pytest_runtest_call(item):
4752
if hasattr(item, 'funcargs'):
4853
for arg, val in item.funcargs.items():
4954
if is_lazy_fixture(val):
5055
item.funcargs[arg] = item._request.getfixturevalue(val.name)
5156

5257

58+
@pytest.hookimpl(hookwrapper=True)
59+
def pytest_pycollect_makeitem(collector, name, obj):
60+
global current_node
61+
current_node = collector
62+
yield
63+
current_node = None
64+
65+
5366
@pytest.hookimpl(hookwrapper=True)
5467
def pytest_generate_tests(metafunc):
5568
yield
@@ -66,40 +79,57 @@ def normalize_metafunc_calls(metafunc, valtype):
6679
metafunc._calls = newcalls
6780

6881

82+
def parametrize_callspecs(callspecs, metafunc, fname, fparams):
83+
allnewcallspecs = []
84+
for i, param in enumerate(fparams):
85+
try:
86+
newcallspecs = [call.copy() for call in callspecs]
87+
except TypeError:
88+
# pytest < 3.6.3
89+
newcallspecs = [call.copy(metafunc) for call in callspecs]
90+
91+
# TODO: for now it uses only function scope
92+
# TODO: idlist
93+
setmulti_args = (
94+
{fname: 'params'}, (fname,), (param,),
95+
None, (), scopenum_function, i
96+
)
97+
try:
98+
for newcallspec in newcallspecs:
99+
newcallspec.setmulti2(*setmulti_args)
100+
except AttributeError:
101+
# pytest < 3.3.0
102+
for newcallspec in newcallspecs:
103+
newcallspec.setmulti(*setmulti_args)
104+
allnewcallspecs.extend(newcallspecs)
105+
return allnewcallspecs
106+
107+
69108
def normalize_call(callspec, metafunc, valtype, used_keys=None):
70109
fm = metafunc.config.pluginmanager.get_plugin('funcmanage')
71-
config = metafunc.config
72110

73111
used_keys = used_keys or set()
74112
valtype_keys = set(getattr(callspec, valtype).keys()) - used_keys
75113

76-
newcalls = []
77114
for arg in valtype_keys:
78115
val = getattr(callspec, valtype)[arg]
79116
if is_lazy_fixture(val):
80-
fname = val.name
81-
nodeid = get_nodeid(metafunc.module, config.rootdir)
82-
fdef = fm.getfixturedefs(fname, nodeid)
83-
if fname not in callspec.params and fdef and fdef[-1].params:
84-
for i, param in enumerate(fdef[0].params):
85-
try:
86-
newcallspec = callspec.copy()
87-
except TypeError:
88-
# pytest < 3.6.3
89-
newcallspec = callspec.copy(metafunc)
90-
91-
# TODO: for now it uses only function scope
92-
# TODO: idlist
93-
setmulti_args = (
94-
{fname: 'params'}, (fname,), (param,),
95-
None, (), scopenum_function, i
96-
)
97-
try:
98-
newcallspec.setmulti2(*setmulti_args)
99-
except AttributeError:
100-
# pytest < 3.3.0
101-
newcallspec.setmulti(*setmulti_args)
102-
117+
try:
118+
_, fixturenames_closure, arg2fixturedefs = fm.getfixtureclosure([val.name], metafunc.definition.parent)
119+
except AttributeError:
120+
# pytest < 3.10.0
121+
fixturenames_closure, arg2fixturedefs = fm.getfixtureclosure([val.name], current_node)
122+
123+
extra_fixture_params = [(fname, arg2fixturedefs[fname][-1].params)
124+
for fname in fixturenames_closure if fname not in callspec.params
125+
if arg2fixturedefs.get(fname) and arg2fixturedefs[fname][-1].params]
126+
127+
if extra_fixture_params:
128+
newcallspecs = [callspec]
129+
for fname, fparams in extra_fixture_params:
130+
newcallspecs = parametrize_callspecs(newcallspecs, metafunc, fname, fparams)
131+
newcalls = []
132+
for newcallspec in newcallspecs:
103133
calls = normalize_call(newcallspec, metafunc, valtype, used_keys | set([arg]))
104134
newcalls.extend(calls)
105135
return newcalls
@@ -151,14 +181,6 @@ def _tree_to_list(trees, leave):
151181
return lst
152182

153183

154-
def get_nodeid(module, rootdir):
155-
path = py.path.local(module.__file__)
156-
relpath = path.relto(rootdir)
157-
if os.sep != "/":
158-
relpath = relpath.replace(os.sep, "/")
159-
return relpath
160-
161-
162184
def lazy_fixture(names):
163185
if isinstance(names, string_type):
164186
return LazyFixture(names)

tests/test_lazyfixture.py

Lines changed: 91 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ def zero(request):
373373
return request.param
374374
375375
@pytest.mark.parametrize(('a', 'b'), [
376-
pytest.mark.xfail((1, pytest.lazy_fixture('zero')), reason=ZeroDivisionError)
376+
pytest.param(1, pytest.lazy_fixture('zero'), marks=pytest.mark.xfail(reason=ZeroDivisionError))
377377
])
378378
def test_division(a, b):
379379
division(a, b)
@@ -411,7 +411,7 @@ def one():
411411
return 1
412412
413413
@pytest.mark.parametrize('a', [
414-
pytest.mark.skip(pytest.lazy_fixture('one'), reason='skip')
414+
pytest.param(pytest.lazy_fixture('one'), marks=pytest.mark.skip(reason='skip'))
415415
])
416416
def test_skip1(a):
417417
assert a == 1
@@ -447,7 +447,7 @@ def test_model_a(self, a):
447447
assert a == 1
448448
449449
@pytest.mark.parametrize('a', [
450-
pytest.mark.skip(pytest.lazy_fixture('one'), reason='skip this')
450+
pytest.param(pytest.lazy_fixture('one'), marks=pytest.mark.skip(reason='skip this'))
451451
])
452452
def test_model_b(self, a):
453453
assert a == 1
@@ -580,3 +580,91 @@ def test_sorted_by_dependency(params, expected_paths):
580580
])
581581
def test_sorted_argnames(params, fixturenames, expect_keys):
582582
assert list(_sorted_argnames(params, fixturenames)) == expect_keys
583+
584+
585+
def test_lazy_fixtures_with_subfixtures(testdir):
586+
testdir.makepyfile("""
587+
import pytest
588+
589+
@pytest.fixture(params=["a", "A"])
590+
def a(request):
591+
return request.param
592+
593+
@pytest.fixture(params=["b", "B"])
594+
def b(a, request):
595+
return request.param + a
596+
597+
@pytest.fixture
598+
def c(a):
599+
return "c" + a
600+
601+
@pytest.fixture(params=[pytest.lazy_fixture('a'), pytest.lazy_fixture('b'), pytest.lazy_fixture('c')])
602+
def d(request):
603+
return "d" + request.param
604+
605+
@pytest.fixture(params=[pytest.lazy_fixture('a'), pytest.lazy_fixture('d'), ""])
606+
def e(request):
607+
return "e" + request.param
608+
609+
def test_one(d):
610+
assert d in ("da", "dA", "dba", "dbA", "dBa", "dBA", "dca", "dcA")
611+
612+
def test_two(e):
613+
assert e in ("ea", "eA", "eda", "edA", "edba", "edbA", "edBa", "edBA", "edca", "edcA", "e")
614+
""")
615+
reprec = testdir.inline_run('-s', '-v')
616+
reprec.assertoutcome(passed=19)
617+
618+
619+
def test_lazy_fixtures_in_subfixture(testdir):
620+
testdir.makepyfile("""
621+
import pytest
622+
623+
@pytest.fixture
624+
def a():
625+
return "a"
626+
627+
@pytest.fixture
628+
def b():
629+
return "b"
630+
631+
@pytest.fixture(params=[pytest.lazy_fixture('a'), pytest.lazy_fixture('b')])
632+
def c(request):
633+
return "c" + request.param
634+
635+
@pytest.fixture
636+
def d(c):
637+
return "d" + c
638+
639+
def test_one(d):
640+
assert d in ("dca", "dcb")
641+
""")
642+
reprec = testdir.inline_run('-s', '-v')
643+
reprec.assertoutcome(passed=2)
644+
645+
646+
@pytest.mark.parametrize('autouse', [False, True])
647+
def test_issues23(testdir, autouse):
648+
testdir.makepyfile("""
649+
import pytest
650+
651+
@pytest.fixture(params=[0, 1], autouse={})
652+
def zero(request):
653+
return request.param
654+
655+
@pytest.fixture(params=[1])
656+
def one(request, zero):
657+
return zero * request.param
658+
659+
@pytest.fixture(params=[
660+
pytest.lazy_fixture('one'),
661+
])
662+
def some(request):
663+
return request.param
664+
665+
def test_func(some):
666+
assert some in [0, 1]
667+
668+
""".format(autouse))
669+
reprec = testdir.inline_run('-s', '-v')
670+
reprec.assertoutcome(passed=2)

0 commit comments

Comments
 (0)