refactor and document parametrized sorting code.

This commit is contained in:
holger krekel 2013-12-05 06:09:29 +01:00
parent cb485e5af4
commit 98c6ced46e
1 changed files with 64 additions and 72 deletions

View File

@ -631,7 +631,7 @@ class CallSpec2(object):
self._checkargnotcontained(arg) self._checkargnotcontained(arg)
getattr(self, valtype)[arg] = val getattr(self, valtype)[arg] = val
# we want self.params to be always set because of # we want self.params to be always set because of
# parametrize_sorted() which groups tests by params/scope # reorder_items() which groups tests by params/scope
if valtype == "funcargs": if valtype == "funcargs":
self.params[arg] = id self.params[arg] = id
self.indices[arg] = param_index self.indices[arg] = param_index
@ -1600,7 +1600,7 @@ class FixtureManager:
def pytest_collection_modifyitems(self, items): def pytest_collection_modifyitems(self, items):
# separate parametrized setups # separate parametrized setups
items[:] = parametrize_sorted(items, set(), {}, 0) items[:] = reorder_items(items, set(), {}, 0)
@pytest.mark.trylast @pytest.mark.trylast
def pytest_runtest_teardown(self, item, nextitem): def pytest_runtest_teardown(self, item, nextitem):
@ -1822,85 +1822,77 @@ def getfuncargnames(function, startindex=None):
return tuple(argnames[startindex:]) return tuple(argnames[startindex:])
# algorithm for sorting on a per-parametrized resource setup basis # algorithm for sorting on a per-parametrized resource setup basis
# it is called for scopenum==0 (session) first and performs sorting
# down to the lower scopes such as to minimize number of "high scope"
# setups and teardowns
def parametrize_sorted(items, ignore, cache, scopenum): def reorder_items(items, ignore, cache, scopenum):
if scopenum >= 3: if scopenum >= scopenum_function:
return items return items
if len(items) < 2:
return items
#print "\nparametrize_Sorted", items, ignore, cache, scopenum
# we pick the first item which has a arg/param combo in the # we pick the first item which uses a fixture instance in the requested scope
# requested scope and sort other items with the same combo # and which we haven't seen yet. We slice the input items list into
# into "newitems" which then is a list of all items using this # a list of items_nomatch, items_using_same_fixtureinstance and
# arg/param. # items_remaining
slicing_argkey = None
similar_items = [] for i, item in enumerate(items):
other_items = [] argkeys = get_parametrized_fixture_keys(item, ignore, scopenum, cache)
slicing_argparam = None if slicing_argkey is None:
slicing_index = 0 if argkeys:
for item in items: slicing_argkey = argkeys.pop()
argparamlist = getfuncargparams(item, ignore, scopenum, cache) items_using_same_fixtureinstance = [item]
if slicing_argparam is None and argparamlist: items_nomatch = items[:i]
slicing_argparam = argparamlist[0] items_remaining = []
slicing_index = len(other_items) continue
if slicing_argparam in argparamlist: if slicing_argkey in argkeys:
similar_items.append(item) items_using_same_fixtureinstance.append(item)
else: else:
other_items.append(item) items_remaining.append(item)
if (len(similar_items) + slicing_index) > 1: if slicing_argkey is None or len(items_using_same_fixtureinstance) == 1:
newignore = ignore.copy() # nothing to sort on this level
newignore.add(slicing_argparam) return reorder_items(items, ignore, cache, scopenum+1)
part2 = parametrize_sorted(
similar_items + other_items[slicing_index:],
newignore, cache, scopenum)
part1 = parametrize_sorted(
other_items[:slicing_index], newignore,
cache, scopenum+1)
return part1 + part2
else:
other_items = parametrize_sorted(other_items, ignore, cache, scopenum+1)
return other_items + similar_items
def getfuncargparams(item, ignore, scopenum, cache): items_nomatch = reorder_items(items_nomatch, ignore, cache, scopenum+1)
""" return list of (arg,param) tuple, sorted by broader scope first. """ newignore = ignore.copy()
assert scopenum < 3 # function newignore.add(slicing_argkey)
part2 = reorder_items(items_using_same_fixtureinstance + items_remaining,
newignore, cache, scopenum)
return items_nomatch + part2
def get_parametrized_fixture_keys(item, ignore, scopenum, cache):
""" return list of keys for all parametrized arguments which match
the specified scope. """
assert scopenum < scopenum_function # function
keys = set()
try: try:
cs = item.callspec cs = item.callspec
except AttributeError: except AttributeError:
return [] return keys # no parametrization on this item
if scopenum == 0: # cs.indictes.items() is random order of argnames but
argparams = [x for x in cs.indices.items() if x not in ignore # then again different functions (items) can change order of
and cs._arg2scopenum[x[0]] == scopenum] # arguments so it doesn't matter much probably
elif scopenum == 1: # module for argname, param_index in cs.indices.items():
argparams = [] if cs._arg2scopenum[argname] != scopenum:
for argname, valindex in cs.indices.items(): continue
if cs._arg2scopenum[argname] == scopenum: if scopenum == 0: # session
key = (argname, valindex, item.fspath) key = (argname, param_index)
if key in ignore: elif scopenum == 1: # module
continue key = (argname, param_index, item.fspath)
argparams.append(key) elif scopenum == 2: # class
elif scopenum == 2: # class l = cache.setdefault(item.fspath, [])
argparams = [] try:
for argname, valindex in cs.indices.items(): i = l.index(item.cls)
if cs._arg2scopenum[argname] == scopenum: except ValueError:
l = cache.setdefault(item.fspath, []) i = len(l)
try: l.append(item.cls)
i = l.index(item.cls) key = (argname, param_index, item.fspath, i)
except ValueError: if key not in ignore:
i = len(l) keys.add(key)
l.append(item.cls) return keys
key = (argname, valindex, item.fspath, i)
if key in ignore:
continue
argparams.append(key)
#elif scopenum == 3:
# argparams = []
# for argname, param in cs.indices.items():
# if cs._arg2scopenum[argname] == scopenum:
# key = (argname, param, getfslineno(item.obj))
# if key in ignore:
# continue
# argparams.append(key)
return argparams
def xunitsetup(obj, name): def xunitsetup(obj, name):