speed up reorder for large higher-than-function-scoped parametrizations
This commit is contained in:
		
							parent
							
								
									a4466342ae
								
							
						
					
					
						commit
						ad2ac256de
					
				|  | @ -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): | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue