Created
February 21, 2020 00:49
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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?