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
92 changes: 57 additions & 35 deletions pytest_lazyfixture.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
# -*- coding: utf-8 -*-
import os
import sys
import types
from collections import defaultdict
import py
import pytest
from _pytest.fixtures import scopenum_function

Expand Down Expand Up @@ -43,13 +41,28 @@ def fill(request):
return fill


@pytest.hookimpl(tryfirst=True)
def pytest_fixture_setup(fixturedef, request):
val = getattr(request, 'param', None)
if is_lazy_fixture(val):
request.param = request.getfixturevalue(val.name)


def pytest_runtest_call(item):
if hasattr(item, 'funcargs'):
for arg, val in item.funcargs.items():
if is_lazy_fixture(val):
item.funcargs[arg] = item._request.getfixturevalue(val.name)


@pytest.hookimpl(hookwrapper=True)
def pytest_pycollect_makeitem(collector, name, obj):
global current_node
current_node = collector
yield
current_node = None


@pytest.hookimpl(hookwrapper=True)
def pytest_generate_tests(metafunc):
yield
Expand All @@ -66,40 +79,57 @@ def normalize_metafunc_calls(metafunc, valtype):
metafunc._calls = newcalls


def parametrize_callspecs(callspecs, metafunc, fname, fparams):
allnewcallspecs = []
for i, param in enumerate(fparams):
try:
newcallspecs = [call.copy() for call in callspecs]
except TypeError:
# pytest < 3.6.3
newcallspecs = [call.copy(metafunc) for call in callspecs]

# TODO: for now it uses only function scope
# TODO: idlist
setmulti_args = (
{fname: 'params'}, (fname,), (param,),
None, (), scopenum_function, i
)
try:
for newcallspec in newcallspecs:
newcallspec.setmulti2(*setmulti_args)
except AttributeError:
# pytest < 3.3.0
for newcallspec in newcallspecs:
newcallspec.setmulti(*setmulti_args)
allnewcallspecs.extend(newcallspecs)
return allnewcallspecs


def normalize_call(callspec, metafunc, valtype, used_keys=None):
fm = metafunc.config.pluginmanager.get_plugin('funcmanage')
config = metafunc.config

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

newcalls = []
for arg in valtype_keys:
val = getattr(callspec, valtype)[arg]
if is_lazy_fixture(val):
fname = val.name
nodeid = get_nodeid(metafunc.module, config.rootdir)
fdef = fm.getfixturedefs(fname, nodeid)
if fname not in callspec.params and fdef and fdef[-1].params:
for i, param in enumerate(fdef[0].params):
try:
newcallspec = callspec.copy()
except TypeError:
# pytest < 3.6.3
newcallspec = callspec.copy(metafunc)

# TODO: for now it uses only function scope
# TODO: idlist
setmulti_args = (
{fname: 'params'}, (fname,), (param,),
None, (), scopenum_function, i
)
try:
newcallspec.setmulti2(*setmulti_args)
except AttributeError:
# pytest < 3.3.0
newcallspec.setmulti(*setmulti_args)

try:
_, fixturenames_closure, arg2fixturedefs = fm.getfixtureclosure([val.name], metafunc.definition.parent)
except AttributeError:
# pytest < 3.10.0
fixturenames_closure, arg2fixturedefs = fm.getfixtureclosure([val.name], current_node)

extra_fixture_params = [(fname, arg2fixturedefs[fname][-1].params)
for fname in fixturenames_closure if fname not in callspec.params
if arg2fixturedefs.get(fname) and arg2fixturedefs[fname][-1].params]

if extra_fixture_params:
newcallspecs = [callspec]
for fname, fparams in extra_fixture_params:
newcallspecs = parametrize_callspecs(newcallspecs, metafunc, fname, fparams)
newcalls = []
for newcallspec in newcallspecs:
calls = normalize_call(newcallspec, metafunc, valtype, used_keys | set([arg]))
newcalls.extend(calls)
return newcalls
Expand Down Expand Up @@ -151,14 +181,6 @@ def _tree_to_list(trees, leave):
return lst


def get_nodeid(module, rootdir):
path = py.path.local(module.__file__)
relpath = path.relto(rootdir)
if os.sep != "/":
relpath = relpath.replace(os.sep, "/")
return relpath


def lazy_fixture(names):
if isinstance(names, string_type):
return LazyFixture(names)
Expand Down
94 changes: 91 additions & 3 deletions tests/test_lazyfixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ def zero(request):
return request.param

@pytest.mark.parametrize(('a', 'b'), [
pytest.mark.xfail((1, pytest.lazy_fixture('zero')), reason=ZeroDivisionError)
pytest.param(1, pytest.lazy_fixture('zero'), marks=pytest.mark.xfail(reason=ZeroDivisionError))
])
def test_division(a, b):
division(a, b)
Expand Down Expand Up @@ -411,7 +411,7 @@ def one():
return 1

@pytest.mark.parametrize('a', [
pytest.mark.skip(pytest.lazy_fixture('one'), reason='skip')
pytest.param(pytest.lazy_fixture('one'), marks=pytest.mark.skip(reason='skip'))
])
def test_skip1(a):
assert a == 1
Expand Down Expand Up @@ -447,7 +447,7 @@ def test_model_a(self, a):
assert a == 1

@pytest.mark.parametrize('a', [
pytest.mark.skip(pytest.lazy_fixture('one'), reason='skip this')
pytest.param(pytest.lazy_fixture('one'), marks=pytest.mark.skip(reason='skip this'))
])
def test_model_b(self, a):
assert a == 1
Expand Down Expand Up @@ -580,3 +580,91 @@ def test_sorted_by_dependency(params, expected_paths):
])
def test_sorted_argnames(params, fixturenames, expect_keys):
assert list(_sorted_argnames(params, fixturenames)) == expect_keys


def test_lazy_fixtures_with_subfixtures(testdir):
testdir.makepyfile("""
import pytest

@pytest.fixture(params=["a", "A"])
def a(request):
return request.param

@pytest.fixture(params=["b", "B"])
def b(a, request):
return request.param + a

@pytest.fixture
def c(a):
return "c" + a

@pytest.fixture(params=[pytest.lazy_fixture('a'), pytest.lazy_fixture('b'), pytest.lazy_fixture('c')])
def d(request):
return "d" + request.param

@pytest.fixture(params=[pytest.lazy_fixture('a'), pytest.lazy_fixture('d'), ""])
def e(request):
return "e" + request.param

def test_one(d):
assert d in ("da", "dA", "dba", "dbA", "dBa", "dBA", "dca", "dcA")

def test_two(e):
assert e in ("ea", "eA", "eda", "edA", "edba", "edbA", "edBa", "edBA", "edca", "edcA", "e")
""")
reprec = testdir.inline_run('-s', '-v')
reprec.assertoutcome(passed=19)


def test_lazy_fixtures_in_subfixture(testdir):
testdir.makepyfile("""
import pytest

@pytest.fixture
def a():
return "a"

@pytest.fixture
def b():
return "b"

@pytest.fixture(params=[pytest.lazy_fixture('a'), pytest.lazy_fixture('b')])
def c(request):
return "c" + request.param

@pytest.fixture
def d(c):
return "d" + c

def test_one(d):
assert d in ("dca", "dcb")
""")
reprec = testdir.inline_run('-s', '-v')
reprec.assertoutcome(passed=2)


@pytest.mark.parametrize('autouse', [False, True])
def test_issues23(testdir, autouse):
testdir.makepyfile("""
import pytest

@pytest.fixture(params=[0, 1], autouse={})
def zero(request):
return request.param

@pytest.fixture(params=[1])
def one(request, zero):
return zero * request.param

@pytest.fixture(params=[
pytest.lazy_fixture('one'),
])
def some(request):
return request.param

def test_func(some):
assert some in [0, 1]

""".format(autouse))
reprec = testdir.inline_run('-s', '-v')
reprec.assertoutcome(passed=2)