Skip to content

Commit

Permalink
LoadScope scheduler: Sort scopes by number of tests to assign biggest…
Browse files Browse the repository at this point in the history
… scopes first (#778)

Follow up to # 632.

---------

Co-authored-by: Jörg Kohlsdorf <joerg@cornershopapp.com>
  • Loading branch information
cryvate and Jörg Kohlsdorf authored Nov 21, 2023
1 parent f36ea25 commit 3fe877b
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 1 deletion.
1 change: 1 addition & 0 deletions changelog/632.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
``--dist=loadscope`` now sorts scopes by number of tests to assign largest scopes early -- in many cases this should improve overall test session running time, as there is less chance of a large scope being left to be processed near the end of the session, leaving other workers idle.
9 changes: 8 additions & 1 deletion src/xdist/scheduler/loadscope.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,11 +350,18 @@ def schedule(self):
return

# Determine chunks of work (scopes)
unsorted_workqueue = OrderedDict()
for nodeid in self.collection:
scope = self._split_scope(nodeid)
work_unit = self.workqueue.setdefault(scope, default=OrderedDict())
work_unit = unsorted_workqueue.setdefault(scope, default=OrderedDict())
work_unit[nodeid] = False

# Insert tests scopes into work queue ordered by number of tests.
for scope, nodeids in sorted(
unsorted_workqueue.items(), key=lambda item: -len(item[1])
):
self.workqueue[scope] = nodeids

# Avoid having more workers than work
extra_nodes = len(self.nodes) - len(self.workqueue)

Expand Down
16 changes: 16 additions & 0 deletions testing/acceptance_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1232,6 +1232,22 @@ def test(self, i):
"test_a.py::TestB", result.outlines
) in ({"gw0": 10}, {"gw1": 10})

def test_workqueue_ordered_by_size(self, pytester: pytest.Pytester) -> None:
test_file = """
import pytest
@pytest.mark.parametrize('i', range({}))
def test(i):
pass
"""
pytester.makepyfile(test_a=test_file.format(10), test_b=test_file.format(20))
result = pytester.runpytest("-n2", "--dist=loadscope", "-v")
assert get_workers_and_test_count_by_prefix(
"test_a.py::test", result.outlines
) == {"gw1": 10}
assert get_workers_and_test_count_by_prefix(
"test_b.py::test", result.outlines
) == {"gw0": 20}

def test_module_single_start(self, pytester: pytest.Pytester) -> None:
"""Fix test suite never finishing in case all workers start with a single test (#277)."""
test_file1 = """
Expand Down

0 comments on commit 3fe877b

Please sign in to comment.