- make request.funcargnames carry the closure of all used funcargs
- make metafunc.funcargnames carry the closure of used funcargs
This commit is contained in:
parent
9568ff3b23
commit
cd1ead4f7b
|
@ -296,7 +296,7 @@ class PyCollector(PyobjMixin, pytest.Collector):
|
|||
clscol = self.getparent(Class)
|
||||
cls = clscol and clscol.obj or None
|
||||
transfer_markers(funcobj, cls, module)
|
||||
metafunc = Metafunc(funcobj, parentid=self.nodeid, config=self.config,
|
||||
metafunc = Metafunc(funcobj, parentnode=self, config=self.config,
|
||||
cls=cls, module=module)
|
||||
gentesthook = self.config.hook.pytest_generate_tests
|
||||
extra = [module]
|
||||
|
@ -594,13 +594,19 @@ class CallSpec2(object):
|
|||
|
||||
class Metafunc:
|
||||
def __init__(self, function, config=None, cls=None, module=None,
|
||||
parentid=""):
|
||||
parentnode=None):
|
||||
self.config = config
|
||||
self.module = module
|
||||
self.function = function
|
||||
self.parentid = parentid
|
||||
self.funcargnames = getfuncargnames(function,
|
||||
startindex=int(cls is not None))
|
||||
self.parentnode = parentnode
|
||||
self.parentid = getattr(parentnode, "nodeid", "")
|
||||
argnames = getfuncargnames(function, startindex=int(cls is not None))
|
||||
if parentnode is not None:
|
||||
fm = parentnode.session.funcargmanager
|
||||
self.funcargnames, self._arg2facdeflist = fm.getallfuncargnames(
|
||||
argnames, parentnode)
|
||||
else:
|
||||
self.funcargnames = argnames
|
||||
self.cls = cls
|
||||
self.module = module
|
||||
self._calls = []
|
||||
|
@ -961,8 +967,11 @@ class FuncargRequest:
|
|||
self._name2factory = {}
|
||||
self.funcargmanager = pyfuncitem.session.funcargmanager
|
||||
self._currentarg = None
|
||||
self.funcargnames = getfuncargnames(self.function)
|
||||
self.parentid = pyfuncitem.parent.nodeid
|
||||
self.funcargnames, self._arg2facdeflist_ = \
|
||||
self.funcargmanager.getallfuncargnames(
|
||||
getfuncargnames(self.function), # XXX _pyfuncitem...
|
||||
pyfuncitem.parent)
|
||||
self._factorystack = []
|
||||
|
||||
@property
|
||||
|
@ -972,19 +981,20 @@ class FuncargRequest:
|
|||
|
||||
def _getfaclist(self, argname):
|
||||
facdeflist = self._name2factory.get(argname, None)
|
||||
getfactb = None
|
||||
function = None
|
||||
if facdeflist is None:
|
||||
if self._factorystack:
|
||||
function = self._factorystack[-1].func
|
||||
getfactb = lambda: self._factorystack[:-1]
|
||||
else:
|
||||
function = self.function
|
||||
getfactb = None
|
||||
facdeflist = self.funcargmanager.getfactorylist(
|
||||
argname, self.parentid, function, getfactb)
|
||||
argname, self.parentid)
|
||||
self._name2factory[argname] = facdeflist
|
||||
elif not facdeflist:
|
||||
self.funcargmanager._raiselookupfailed(argname, self.function,
|
||||
self.parentid)
|
||||
if not facdeflist:
|
||||
self.funcargmanager._raiselookupfailed(argname, function,
|
||||
self.parentid, getfactb)
|
||||
return facdeflist
|
||||
|
||||
@property
|
||||
|
@ -1068,15 +1078,14 @@ class FuncargRequest:
|
|||
|
||||
|
||||
def _fillfuncargs(self):
|
||||
if self.funcargnames:
|
||||
assert not getattr(self._pyfuncitem, '_args', None), (
|
||||
item = self._pyfuncitem
|
||||
funcargnames = getattr(item, "funcargnames", self.funcargnames)
|
||||
if funcargnames:
|
||||
assert not getattr(item, '_args', None), (
|
||||
"yielded functions cannot have funcargs")
|
||||
while self.funcargnames:
|
||||
argname = self.funcargnames.pop(0)
|
||||
if argname not in self._pyfuncitem.funcargs:
|
||||
self._pyfuncitem.funcargs[argname] = \
|
||||
self.getfuncargvalue(argname)
|
||||
|
||||
for argname in funcargnames:
|
||||
if argname not in item.funcargs:
|
||||
item.funcargs[argname] = self.getfuncargvalue(argname)
|
||||
|
||||
def _callsetup(self):
|
||||
self.funcargmanager.ensure_setupcalls(self)
|
||||
|
@ -1305,26 +1314,47 @@ class FuncargManager:
|
|||
for plugin in plugins:
|
||||
self.pytest_plugin_registered(plugin)
|
||||
|
||||
def getallfuncargnames(self, funcargnames, parentnode):
|
||||
# collect the closure of all funcargs, starting with
|
||||
# funcargnames as the initial set
|
||||
# we populate and return a arg2facdeflist mapping
|
||||
# so that the caller can reuse it and does not have to re-discover
|
||||
# factories again for each funcargname
|
||||
parentid = parentnode.nodeid
|
||||
funcargnames = list(funcargnames)
|
||||
_, setupargs = self.getsetuplist(parentnode)
|
||||
def merge(otherlist):
|
||||
for arg in otherlist:
|
||||
if arg not in funcargnames:
|
||||
funcargnames.append(arg)
|
||||
merge(setupargs)
|
||||
arg2facdeflist = {}
|
||||
lastlen = -1
|
||||
while lastlen != len(funcargnames):
|
||||
lastlen = len(funcargnames)
|
||||
for argname in list(funcargnames):
|
||||
if argname in arg2facdeflist:
|
||||
continue
|
||||
facdeflist = self.getfactorylist(argname, parentid)
|
||||
arg2facdeflist[argname] = facdeflist
|
||||
if facdeflist is not None:
|
||||
for facdef in facdeflist:
|
||||
merge(facdef.funcargnames)
|
||||
try:
|
||||
funcargnames.remove("__request__")
|
||||
except ValueError:
|
||||
pass
|
||||
return funcargnames, arg2facdeflist
|
||||
|
||||
def pytest_generate_tests(self, metafunc):
|
||||
funcargnames = list(metafunc.funcargnames)
|
||||
_, allargnames = self.getsetuplist(metafunc.parentid)
|
||||
#print "setuplist, allargnames", setuplist, allargnames
|
||||
funcargnames.extend(allargnames)
|
||||
seen = set()
|
||||
while funcargnames:
|
||||
argname = funcargnames.pop(0)
|
||||
if argname in seen or argname == "request":
|
||||
continue
|
||||
seen.add(argname)
|
||||
faclist = self.getfactorylist(argname, metafunc.parentid,
|
||||
metafunc.function, raising=False)
|
||||
for argname in metafunc.funcargnames:
|
||||
faclist = metafunc._arg2facdeflist[argname]
|
||||
if faclist is None:
|
||||
continue # will raise FuncargLookupError at setup time
|
||||
for facdef in faclist:
|
||||
if facdef.params is not None:
|
||||
metafunc.parametrize(argname, facdef.params, indirect=True,
|
||||
scope=facdef.scope)
|
||||
funcargnames.extend(facdef.funcargnames)
|
||||
scope=facdef.scope)
|
||||
|
||||
def pytest_collection_modifyitems(self, items):
|
||||
# separate parametrized setups
|
||||
|
@ -1388,7 +1418,8 @@ class FuncargManager:
|
|||
faclist.append(factorydef)
|
||||
### check scope/params mismatch?
|
||||
|
||||
def getsetuplist(self, nodeid):
|
||||
def getsetuplist(self, node):
|
||||
nodeid = node.nodeid
|
||||
l = []
|
||||
allargnames = set()
|
||||
for setupcall in self.setuplist:
|
||||
|
@ -1399,12 +1430,11 @@ class FuncargManager:
|
|||
return l, allargnames
|
||||
|
||||
|
||||
def getfactorylist(self, argname, nodeid, function, getfactb=None, raising=True):
|
||||
def getfactorylist(self, argname, nodeid):
|
||||
try:
|
||||
factorydeflist = self.arg2facspec[argname]
|
||||
except KeyError:
|
||||
if raising:
|
||||
self._raiselookupfailed(argname, function, nodeid, getfactb)
|
||||
return None
|
||||
else:
|
||||
return self._matchfactories(factorydeflist, nodeid)
|
||||
|
||||
|
@ -1429,7 +1459,7 @@ class FuncargManager:
|
|||
raise FuncargLookupError(function, msg, lines)
|
||||
|
||||
def ensure_setupcalls(self, request):
|
||||
setuplist, allnames = self.getsetuplist(request._pyfuncitem.nodeid)
|
||||
setuplist, allnames = self.getsetuplist(request._pyfuncitem)
|
||||
for setupcall in setuplist:
|
||||
if setupcall.active:
|
||||
continue
|
||||
|
|
|
@ -1340,6 +1340,27 @@ class TestMetafuncFunctional:
|
|||
"*1 passed*"
|
||||
])
|
||||
|
||||
def test_parametrize_on_setup_arg(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
assert "arg1" in metafunc.funcargnames
|
||||
metafunc.parametrize("arg1", [1], indirect=True)
|
||||
|
||||
def pytest_funcarg__arg1(request):
|
||||
return request.param
|
||||
|
||||
def pytest_funcarg__arg2(request, arg1):
|
||||
return 10 * arg1
|
||||
|
||||
def test_func(arg2):
|
||||
assert arg2 == 10
|
||||
""")
|
||||
result = testdir.runpytest("-v", p)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*test_func*1*PASS*",
|
||||
"*1 passed*"
|
||||
])
|
||||
|
||||
|
||||
def test_conftest_funcargs_only_available_in_subdir(testdir):
|
||||
sub1 = testdir.mkpydir("sub1")
|
||||
|
@ -1648,38 +1669,6 @@ def test_issue117_sessionscopeteardown(testdir):
|
|||
"*ZeroDivisionError*",
|
||||
])
|
||||
|
||||
class TestRequestAPI:
|
||||
@pytest.mark.xfail(reason="consider flub feedback")
|
||||
def test_setup_can_query_funcargs(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
def pytest_runtest_setup(item):
|
||||
assert not hasattr(item, "_request")
|
||||
""")
|
||||
testdir.makepyfile("""
|
||||
def pytest_funcarg__a(request):
|
||||
return 1
|
||||
def pytest_funcarg__b(request):
|
||||
return request.getfuncargvalue("a") + 1
|
||||
def test_hello(b):
|
||||
assert b == 2
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines([
|
||||
"*1 passed*",
|
||||
])
|
||||
|
||||
result = testdir.makeconftest("""
|
||||
import pytest
|
||||
@pytest.setup()
|
||||
def mysetup(request):
|
||||
request.uses_funcarg("db")
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines([
|
||||
"*1 passed*",
|
||||
])
|
||||
|
||||
class TestFuncargFactory:
|
||||
def test_receives_funcargs(self, testdir):
|
||||
|
@ -1828,7 +1817,7 @@ class TestFuncargManager:
|
|||
testdir.makepyfile("""
|
||||
def test_hello(item, fm):
|
||||
for name in ("fm", "hello", "item"):
|
||||
faclist = fm.getfactorylist(name, item.nodeid, item.obj)
|
||||
faclist = fm.getfactorylist(name, item.nodeid)
|
||||
assert len(faclist) == 1
|
||||
fac = faclist[0]
|
||||
assert fac.func.__name__ == "pytest_funcarg__" + name
|
||||
|
@ -1844,7 +1833,7 @@ class TestFuncargManager:
|
|||
def pytest_funcarg__hello(self, request):
|
||||
return "class"
|
||||
def test_hello(self, item, fm):
|
||||
faclist = fm.getfactorylist("hello", item.nodeid, item.obj)
|
||||
faclist = fm.getfactorylist("hello", item.nodeid)
|
||||
print (faclist)
|
||||
assert len(faclist) == 3
|
||||
assert faclist[0].func(item._request) == "conftest"
|
||||
|
@ -1880,7 +1869,7 @@ class TestSetupDiscovery:
|
|||
def test_parsefactories_conftest(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
def test_check_setup(item, fm):
|
||||
setupcalls, allnames = fm.getsetuplist(item.nodeid)
|
||||
setupcalls, allnames = fm.getsetuplist(item)
|
||||
assert len(setupcalls) == 2
|
||||
assert setupcalls[0].func.__name__ == "perfunction"
|
||||
assert "request" in setupcalls[0].funcargnames
|
||||
|
@ -2650,3 +2639,23 @@ def test_setup_funcarg_order(testdir):
|
|||
""")
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
|
||||
def test_request_funcargnames(testdir):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
@pytest.factory()
|
||||
def arg1():
|
||||
pass
|
||||
@pytest.factory()
|
||||
def farg(arg1):
|
||||
pass
|
||||
@pytest.setup()
|
||||
def sarg(tmpdir):
|
||||
pass
|
||||
def test_function(request, farg):
|
||||
assert set(request.funcargnames) == \
|
||||
set(["tmpdir", "arg1", "request", "farg"])
|
||||
""")
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
|
Loading…
Reference in New Issue