make factorydeflist immutable by using an index

This commit is contained in:
holger krekel 2012-10-16 13:59:12 +02:00
parent ccaa1af534
commit 37965657d0
3 changed files with 51 additions and 56 deletions

View File

@ -36,5 +36,5 @@ attributes:
- names_initial: list of initial fixture names (see above) - names_initial: list of initial fixture names (see above)
- names_closure: closure of all fixture names - names_closure: closure of all fixture names
- name2fixturedeflist: for creating the value - name2fixturedefs: for creating the value

View File

@ -361,26 +361,16 @@ class FixtureMapper:
usefixtures = getattr(func, "usefixtures", None) usefixtures = getattr(func, "usefixtures", None)
if usefixtures is not None: if usefixtures is not None:
argnames = usefixtures.args + argnames argnames = usefixtures.args + argnames
names_closure, arg2fixturedeflist = self.fm.getfixtureclosure( names_closure, arg2fixturedefs = self.fm.getfixtureclosure(
argnames, self.node) argnames, self.node)
fixtureinfo = FuncFixtureInfo(names_closure, arg2fixturedeflist) fixtureinfo = FuncFixtureInfo(names_closure, arg2fixturedefs)
self._name2fixtureinfo[func] = fixtureinfo self._name2fixtureinfo[func] = fixtureinfo
return fixtureinfo return fixtureinfo
class FuncFixtureInfo: class FuncFixtureInfo:
def __init__(self, names_closure, name2fixturedeflist): def __init__(self, names_closure, name2fixturedefs):
self.names_closure = names_closure self.names_closure = names_closure
self.name2fixturedeflist = name2fixturedeflist self.name2fixturedefs = name2fixturedefs
def getname2fixturedeflist_copy(self):
d = {}
for name, val in self.name2fixturedeflist.items():
try:
val = list(val)
except TypeError:
pass
d[name] = val
return d
def transfer_markers(funcobj, cls, mod): def transfer_markers(funcobj, cls, mod):
# XXX this should rather be code in the mark plugin or the mark # XXX this should rather be code in the mark plugin or the mark
@ -675,7 +665,7 @@ class Metafunc(FuncargnamesCompatAttr):
self.module = module self.module = module
self.function = function self.function = function
self.fixturenames = fixtureinfo.names_closure self.fixturenames = fixtureinfo.names_closure
self._arg2fixturedeflist = fixtureinfo.name2fixturedeflist self._arg2fixturedefs = fixtureinfo.name2fixturedefs
self.cls = cls self.cls = cls
self.module = module self.module = module
self._calls = [] self._calls = []
@ -804,12 +794,12 @@ def _showfixtures_main(config, session):
fm = session._fixturemanager fm = session._fixturemanager
available = [] available = []
for argname in fm.arg2fixturedeflist: for argname in fm.arg2fixturedefs:
fixturedeflist = fm.getfixturedeflist(argname, nodeid) fixturedefs = fm.getfixturedefs(argname, nodeid)
assert fixturedeflist is not None assert fixturedefs is not None
if not fixturedeflist: if not fixturedefs:
continue continue
fixturedef = fixturedeflist[-1] fixturedef = fixturedefs[-1]
loc = getlocation(fixturedef.func, curdir) loc = getlocation(fixturedef.func, curdir)
available.append((len(fixturedef.baseid), available.append((len(fixturedef.baseid),
curdir.bestrelpath(loc), curdir.bestrelpath(loc),
@ -1055,7 +1045,8 @@ class FixtureRequest(FuncargnamesCompatAttr):
self.getparent = pyfuncitem.getparent self.getparent = pyfuncitem.getparent
self._funcargs = self._pyfuncitem.funcargs.copy() self._funcargs = self._pyfuncitem.funcargs.copy()
self._fixtureinfo = fi = pyfuncitem._fixtureinfo self._fixtureinfo = fi = pyfuncitem._fixtureinfo
self._arg2fixturedeflist = fi.getname2fixturedeflist_copy() self._arg2fixturedefs = fi.name2fixturedefs
self._arg2index = {}
self.fixturenames = self._fixtureinfo.names_closure self.fixturenames = self._fixtureinfo.names_closure
self._fixturemanager = pyfuncitem.session._fixturemanager self._fixturemanager = pyfuncitem.session._fixturemanager
self._parentid = pyfuncitem.parent.nodeid self._parentid = pyfuncitem.parent.nodeid
@ -1066,15 +1057,20 @@ class FixtureRequest(FuncargnamesCompatAttr):
""" underlying collection node (depends on current request scope)""" """ underlying collection node (depends on current request scope)"""
return self._getscopeitem(self.scope) return self._getscopeitem(self.scope)
def _getfixturedeflist(self, argname): def _getnextfixturedef(self, argname):
fixturedeflist = self._arg2fixturedeflist.get(argname, None) fixturedefs = self._arg2fixturedefs.get(argname, None)
if fixturedeflist is None: if fixturedefs is None:
fixturedeflist = self._fixturemanager.getfixturedeflist( # we arrive here because of a getfuncargvalue(argname) usage which
# was naturally not knowable at parsing/collection time
fixturedefs = self._fixturemanager.getfixturedefs(
argname, self._parentid) argname, self._parentid)
self._arg2fixturedeflist[argname] = fixturedeflist self._arg2fixturedefs[argname] = fixturedefs
if not fixturedeflist: # fixturedefs is immutable so we maintain a decreasing index
index = self._arg2index.get(argname, 0) - 1
if fixturedefs is None or (-index > len(fixturedefs)):
raise FixtureLookupError(argname, self) raise FixtureLookupError(argname, self)
return fixturedeflist self._arg2index[argname] = index
return fixturedefs[index]
@property @property
def config(self): def config(self):
@ -1212,12 +1208,11 @@ class FixtureRequest(FuncargnamesCompatAttr):
except KeyError: except KeyError:
pass pass
try: try:
fixturedeflist = self._getfixturedeflist(argname) fixturedef = self._getnextfixturedef(argname)
except FixtureLookupError: except FixtureLookupError:
if argname == "request": if argname == "request":
return self return self
raise raise
fixturedef = fixturedeflist.pop()
self._fixturestack.append(fixturedef) self._fixturestack.append(fixturedef)
try: try:
result = self._getfuncargvalue(fixturedef) result = self._getfuncargvalue(fixturedef)
@ -1353,7 +1348,7 @@ class FixtureLookupError(LookupError):
fm = self.request._fixturemanager fm = self.request._fixturemanager
nodeid = self.request._parentid nodeid = self.request._parentid
available = [] available = []
for name, fixturedef in fm.arg2fixturedeflist.items(): for name, fixturedef in fm.arg2fixturedefs.items():
faclist = list(fm._matchfactories(fixturedef, self.request._parentid)) faclist = list(fm._matchfactories(fixturedef, self.request._parentid))
if faclist: if faclist:
available.append(name) available.append(name)
@ -1388,7 +1383,7 @@ class FixtureManager:
def __init__(self, session): def __init__(self, session):
self.session = session self.session = session
self.config = session.config self.config = session.config
self.arg2fixturedeflist = {} self.arg2fixturedefs = {}
self._seenplugins = set() self._seenplugins = set()
self._holderobjseen = set() self._holderobjseen = set()
self._arg2finish = {} self._arg2finish = {}
@ -1427,7 +1422,7 @@ class FixtureManager:
# make sure the self._autofixtures list is sorted # make sure the self._autofixtures list is sorted
# by scope, scopenum 0 is session # by scope, scopenum 0 is session
self._autofixtures.sort( self._autofixtures.sort(
key=lambda x: self.arg2fixturedeflist[x][-1].scopenum) key=lambda x: self.arg2fixturedefs[x][-1].scopenum)
defaultfixtures = defaultfixtures + tuple(self._autofixtures) defaultfixtures = defaultfixtures + tuple(self._autofixtures)
self._defaultfixtures = defaultfixtures self._defaultfixtures = defaultfixtures
return defaultfixtures return defaultfixtures
@ -1435,7 +1430,7 @@ class FixtureManager:
def getfixtureclosure(self, fixturenames, parentnode): def getfixtureclosure(self, fixturenames, parentnode):
# collect the closure of all fixtures , starting with the given # collect the closure of all fixtures , starting with the given
# fixturenames as the initial set. As we have to visit all # fixturenames as the initial set. As we have to visit all
# factory definitions anyway, we also return a arg2fixturedeflist # factory definitions anyway, we also return a arg2fixturedefs
# mapping so that the caller can reuse it and does not have # mapping so that the caller can reuse it and does not have
# to re-discover fixturedefs again for each fixturename # to re-discover fixturedefs again for each fixturename
# (discovering matching fixtures for a given name/node is expensive) # (discovering matching fixtures for a given name/node is expensive)
@ -1447,23 +1442,23 @@ class FixtureManager:
if arg not in fixturenames_closure: if arg not in fixturenames_closure:
fixturenames_closure.append(arg) fixturenames_closure.append(arg)
merge(fixturenames) merge(fixturenames)
arg2fixturedeflist = {} arg2fixturedefs = {}
lastlen = -1 lastlen = -1
while lastlen != len(fixturenames_closure): while lastlen != len(fixturenames_closure):
lastlen = len(fixturenames_closure) lastlen = len(fixturenames_closure)
for argname in fixturenames_closure: for argname in fixturenames_closure:
if argname in arg2fixturedeflist: if argname in arg2fixturedefs:
continue continue
fixturedeflist = self.getfixturedeflist(argname, parentid) fixturedefs = self.getfixturedefs(argname, parentid)
arg2fixturedeflist[argname] = fixturedeflist arg2fixturedefs[argname] = fixturedefs
if fixturedeflist is not None: if fixturedefs is not None:
for fixturedef in fixturedeflist: for fixturedef in fixturedefs:
merge(fixturedef.fixturenames) merge(fixturedef.fixturenames)
return fixturenames_closure, arg2fixturedeflist return fixturenames_closure, arg2fixturedefs
def pytest_generate_tests(self, metafunc): def pytest_generate_tests(self, metafunc):
for argname in metafunc.fixturenames: for argname in metafunc.fixturenames:
faclist = metafunc._arg2fixturedeflist[argname] faclist = metafunc._arg2fixturedefs[argname]
if faclist is None: if faclist is None:
continue # will raise FixtureLookupError at setup time continue # will raise FixtureLookupError at setup time
for fixturedef in faclist: for fixturedef in faclist:
@ -1527,7 +1522,7 @@ class FixtureManager:
fixturedef = FixtureDef(self, nodeid, name, obj, fixturedef = FixtureDef(self, nodeid, name, obj,
marker.scope, marker.params, marker.scope, marker.params,
unittest=unittest) unittest=unittest)
faclist = self.arg2fixturedeflist.setdefault(name, []) faclist = self.arg2fixturedefs.setdefault(name, [])
faclist.append(fixturedef) faclist.append(fixturedef)
if marker.autouse: if marker.autouse:
self._autofixtures.append(name) self._autofixtures.append(name)
@ -1536,16 +1531,16 @@ class FixtureManager:
except AttributeError: except AttributeError:
pass pass
def getfixturedeflist(self, argname, nodeid): def getfixturedefs(self, argname, nodeid):
try: try:
fixturedeflist = self.arg2fixturedeflist[argname] fixturedefs = self.arg2fixturedefs[argname]
except KeyError: except KeyError:
return None return None
else: else:
return list(self._matchfactories(fixturedeflist, nodeid)) return tuple(self._matchfactories(fixturedefs, nodeid))
def _matchfactories(self, fixturedeflist, nodeid): def _matchfactories(self, fixturedefs, nodeid):
for fixturedef in fixturedeflist: for fixturedef in fixturedefs:
if nodeid.startswith(fixturedef.baseid): if nodeid.startswith(fixturedef.baseid):
yield fixturedef yield fixturedef

View File

@ -640,7 +640,7 @@ class TestRequest:
assert req.cls.__name__ == "TestB" assert req.cls.__name__ == "TestB"
assert req.instance.__class__ == req.cls assert req.instance.__class__ == req.cls
def XXXtest_request_contains_funcarg_arg2fixturedeflist(self, testdir): def XXXtest_request_contains_funcarg_arg2fixturedefs(self, testdir):
modcol = testdir.getmodulecol(""" modcol = testdir.getmodulecol("""
def pytest_funcarg__something(request): def pytest_funcarg__something(request):
pass pass
@ -650,9 +650,9 @@ class TestRequest:
""") """)
item1, = testdir.genitems([modcol]) item1, = testdir.genitems([modcol])
assert item1.name == "test_method" assert item1.name == "test_method"
arg2fixturedeflist = funcargs.FixtureRequest(item1)._arg2fixturedeflist arg2fixturedefs = funcargs.FixtureRequest(item1)._arg2fixturedefs
assert len(arg2fixturedeflist) == 1 assert len(arg2fixturedefs) == 1
assert arg2fixturedeflist[0].__name__ == "pytest_funcarg__something" assert arg2fixturedefs[0].__name__ == "pytest_funcarg__something"
def test_getfuncargvalue_recursive(self, testdir): def test_getfuncargvalue_recursive(self, testdir):
testdir.makeconftest(""" testdir.makeconftest("""
@ -918,7 +918,7 @@ class TestMetafunc:
# on the funcarg level, so we don't need a full blown # on the funcarg level, so we don't need a full blown
# initiliazation # initiliazation
class FixtureInfo: class FixtureInfo:
name2fixturedeflist = None name2fixturedefs = None
def __init__(self, names): def __init__(self, names):
self.names_closure = names self.names_closure = names
names = funcargs.getfuncargnames(func) names = funcargs.getfuncargnames(func)
@ -1974,7 +1974,7 @@ class TestFixtureManager:
testdir.makepyfile(""" testdir.makepyfile("""
def test_hello(item, fm): def test_hello(item, fm):
for name in ("fm", "hello", "item"): for name in ("fm", "hello", "item"):
faclist = fm.getfixturedeflist(name, item.nodeid) faclist = fm.getfixturedefs(name, item.nodeid)
assert len(faclist) == 1 assert len(faclist) == 1
fac = faclist[0] fac = faclist[0]
assert fac.func.__name__ == "pytest_funcarg__" + name assert fac.func.__name__ == "pytest_funcarg__" + name
@ -1990,7 +1990,7 @@ class TestFixtureManager:
def pytest_funcarg__hello(self, request): def pytest_funcarg__hello(self, request):
return "class" return "class"
def test_hello(self, item, fm): def test_hello(self, item, fm):
faclist = fm.getfixturedeflist("hello", item.nodeid) faclist = fm.getfixturedefs("hello", item.nodeid)
print (faclist) print (faclist)
assert len(faclist) == 3 assert len(faclist) == 3
assert faclist[0].func(item._request) == "conftest" assert faclist[0].func(item._request) == "conftest"