speed up reorder for large higher-than-function-scoped parametrizations

This commit is contained in:
holger krekel 2013-12-09 10:05:44 +01:00
parent a4466342ae
commit ad2ac256de
1 changed files with 60 additions and 45 deletions

View File

@ -1626,7 +1626,7 @@ class FixtureManager:
def pytest_collection_modifyitems(self, items): def pytest_collection_modifyitems(self, items):
# separate parametrized setups # separate parametrized setups
items[:] = reorder_items(items, set(), 0) items[:] = reorder_items(items)
def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False): def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False):
if nodeid is not NOTSET: if nodeid is not NOTSET:
@ -1824,14 +1824,25 @@ def getfuncargnames(function, startindex=None):
# down to the lower scopes such as to minimize number of "high scope" # down to the lower scopes such as to minimize number of "high scope"
# setups and teardowns # setups and teardowns
def reorder_items(items, ignore, scopenum): def reorder_items(items):
argkeys_cache = {}
for scopenum in range(0, scopenum_function):
argkeys_cache[scopenum] = d = {}
for item in items:
keys = set(get_parametrized_fixture_keys(item, scopenum))
if keys:
d[item] = keys
return reorder_items_atscope(items, set(), argkeys_cache, 0)
def reorder_items_atscope(items, ignore, argkeys_cache, scopenum):
if scopenum >= scopenum_function or len(items) < 3: if scopenum >= scopenum_function or len(items) < 3:
return items return items
items_done = [] items_done = []
while 1: while 1:
items_before, items_same, items_other, newignore = \ items_before, items_same, items_other, newignore = \
slice_items(items, ignore, scopenum) slice_items(items, ignore, argkeys_cache[scopenum])
items_before = reorder_items(items_before, ignore, scopenum+1) items_before = reorder_items_atscope(
items_before, ignore, argkeys_cache,scopenum+1)
if items_same is None: if items_same is None:
# nothing to reorder in this scope # nothing to reorder in this scope
assert items_other is None assert items_other is None
@ -1841,54 +1852,58 @@ def reorder_items(items, ignore, scopenum):
ignore = newignore ignore = newignore
def slice_items(items, ignore, scopenum): def slice_items(items, ignore, scoped_argkeys_cache):
# we pick the first item which uses a fixture instance in the requested scope # we pick the first item which uses a fixture instance in the
# and which we haven't seen yet. We slice the input items list into # requested scope and which we haven't seen yet. We slice the input
# a list of items_nomatch, items_same and items_other # items list into a list of items_nomatch, items_same and
slicing_argkey = None # items_other
for i, item in enumerate(items): if scoped_argkeys_cache: # do we need to do work at all?
argkeys = get_parametrized_fixture_keys(item, ignore, scopenum) it = iter(items)
if slicing_argkey is None: # first find a slicing key
if argkeys: for i, item in enumerate(it):
slicing_argkey = argkeys.pop() argkeys = scoped_argkeys_cache.get(item)
items_before = items[:i] if argkeys is not None:
items_same = [item] argkeys = argkeys.difference(ignore)
items_other = [] if argkeys: # found a slicing key
continue slicing_argkey = argkeys.pop()
if slicing_argkey in argkeys: items_before = items[:i]
items_same.append(item) items_same = [item]
else: items_other = []
items_other.append(item) # now slice the remainder of the list
if slicing_argkey is None: for item in it:
return items, None, None, None argkeys = scoped_argkeys_cache.get(item)
newignore = ignore.copy() if argkeys and slicing_argkey in argkeys and \
newignore.add(slicing_argkey) slicing_argkey not in ignore:
return (items_before, items_same, items_other, newignore) items_same.append(item)
else:
items_other.append(item)
newignore = ignore.copy()
newignore.add(slicing_argkey)
return (items_before, items_same, items_other, newignore)
return items, None, None, None
def get_parametrized_fixture_keys(item, ignore, scopenum): def get_parametrized_fixture_keys(item, scopenum):
""" return list of keys for all parametrized arguments which match """ return list of keys for all parametrized arguments which match
the specified scope. """ the specified scope. """
assert scopenum < scopenum_function # function assert scopenum < scopenum_function # function
keys = set()
try: try:
cs = item.callspec cs = item.callspec
except AttributeError: except AttributeError:
return keys # no parametrization on this item pass
# cs.indictes.items() is random order of argnames but else:
# then again different functions (items) can change order of # cs.indictes.items() is random order of argnames but
# arguments so it doesn't matter much probably # then again different functions (items) can change order of
for argname, param_index in cs.indices.items(): # arguments so it doesn't matter much probably
if cs._arg2scopenum[argname] != scopenum: for argname, param_index in cs.indices.items():
continue if cs._arg2scopenum[argname] != scopenum:
if scopenum == 0: # session continue
key = (argname, param_index) if scopenum == 0: # session
elif scopenum == 1: # module key = (argname, param_index)
key = (argname, param_index, item.fspath) elif scopenum == 1: # module
elif scopenum == 2: # class key = (argname, param_index, item.fspath)
key = (argname, param_index, item.fspath, item.cls) elif scopenum == 2: # class
if key not in ignore: key = (argname, param_index, item.fspath, item.cls)
keys.add(key) yield key
return keys
def xunitsetup(obj, name): def xunitsetup(obj, name):