Skip to content

Instantly share code, notes, and snippets.

@phillip-lu-axomic
Created February 21, 2020 00:49
Show Gist options
  • Save phillip-lu-axomic/3a8c8c246975667da3162decac9b5f2e to your computer and use it in GitHub Desktop.
Save phillip-lu-axomic/3a8c8c246975667da3162decac9b5f2e to your computer and use it in GitHub Desktop.
Pytest function that reorders the test execution order based on dependencies.
# guess we're spinning up our own reordering function
def pytest_collection_modifyitems(config, items):
"""
After collection, this will reorder the function test ordering by dependency.
Here's what we're gonna do:
bucket the functions by their parents
For each parent bucket, iterate over all functions, keeping track of dependency tree.
Start doing BFS down the dependency tree.
TODO: check for circular dependency
TODO: make the files test in the same order
"""
# creating the parent buckets
function_buckets = {}
for item in items:
if item.parent.name not in function_buckets:
function_buckets[item.parent.name] = []
function_buckets[item.parent.name].append(item)
master_sorted_list = []
# sorting the functions within each file
# f_items = file items: test functions within this test_file
for parent, f_items in function_buckets.items():
item_dict = {item.name: item for item in f_items} # item.name -> item
# removing duplicate function names. Python >3.7 preserves ordering for
# dict insertion
func_names = list(dict.fromkeys([item.name for item in f_items]))
# building the dependency tree
dependency_tree = {} # item.name --> [names]
for item in f_items:
dependencies = [
f.kwargs["depends"]
for f in item.iter_markers(name="dependency")
if "depends" in f.kwargs
]
dependency_tree[item.name] = dependencies
# BFS through the list
# we're gonna use the items (funcs within this file)
# as the set of functions we iterate through
# we don't end until this list is empty
# for each function: it either depends on a function in our remaining set, or it doesn't
# if it doesn't: put it in the list:
# none of the functions this function depends on will run after this.
# if it does: put at the end of the remaining functions (items)
# this way, we can fit the dependent functions first
sorted_list = []
while func_names:
func_name = func_names[0]
dependent_functions = dependency_tree[func_name]
if len(dependent_functions) == 1:
dependent_functions = dependent_functions[0]
if all([f not in func_names for f in dependent_functions]):
sorted_list.append(item_dict[func_names[0]])
func_names = func_names[1:] # purge it cause we're done with it
else:
# shuffle this entry to the back
func_names = func_names[1:] + [func_names[0]]
master_sorted_list.extend(sorted_list)
assert len(master_sorted_list) == len(items)
items[:] = master_sorted_list
@aishwarya-45
Copy link

Thanks a lot for this code! However, I am facing some issues. So, when I use this code in my pytest_collection_modifyitems for the cases in a class, it does reorder the cases according to the dependencies, but then, due to some unknown reason, even after all the other cases are passing correctly the one dependent on them is getting skipped. Do you have any idea why is it happening?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment