Skip to content

Commit

Permalink
Retain input ordering in loadscope
Browse files Browse the repository at this point in the history
* Optionally retain input ordering in loadscope for tests where relative
  ordering matters. i.e. guarantee that, given [input_1, input_2],
  input_2 never runs before input_1. On any given worker, either input_
  has ran before input_2, or input_1 has never and will never run on
  this worker.
  • Loading branch information
Toad2186 committed Jun 12, 2024
1 parent c7b4f61 commit ef17eac
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 5 deletions.
1 change: 1 addition & 0 deletions changelog/1083.trivial
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
With `--no-loadscope-reorder`, retain input ordering in loadscope for tests where relative ordering matters. i.e. guarantee that, given [input_1, input_2],input_2 never runs before input_1. On any given worker, either input_1 has ran before input_2, or input_1 has never and will never run on this worker. This only applies when using `loadscope`.
File renamed without changes.
11 changes: 11 additions & 0 deletions src/xdist/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,17 @@ def pytest_addoption(parser: pytest.Parser) -> None:
"(default) no: Run tests inprocess, don't distribute."
),
)
group.addoption(
"--no-loadscope-reorder",
action="store_true",
dest="noloadscopenoreorder",
default=False,
help=(
"Reorders tests when used in conjunction with loadscope.\n"
"Will order tests by number of tests per scope as a best-effort"
" attempt to evenly distribute scopes across all workers."
)
)
group.addoption(
"--tx",
dest="tx",
Expand Down
14 changes: 9 additions & 5 deletions src/xdist/scheduler/loadscope.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,11 +371,15 @@ def schedule(self) -> None:
work_unit = unsorted_workqueue.setdefault(scope, {})
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
if self.config.option.noloadscopenoreorder:
for scope, nodeids in unsorted_workqueue.items():
self.workqueue[scope] = nodeids
else:
# 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 @@ -1254,6 +1254,22 @@ def test(i):
"test_b.py::test", result.outlines
) == {"gw0": 20}

def test_workqueue_ordered_by_input(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", "--no-loadscope-reorder", "-v")
assert get_workers_and_test_count_by_prefix(
"test_a.py::test", result.outlines
) == {"gw0": 10}
assert get_workers_and_test_count_by_prefix(
"test_b.py::test", result.outlines
) == {"gw1": 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 ef17eac

Please sign in to comment.