Skip to content

Commit 4d1892a

Browse files
committed
Retain input ordering in loadscope
* 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.
1 parent c7b4f61 commit 4d1892a

File tree

4 files changed

+36
-5
lines changed

4 files changed

+36
-5
lines changed
File renamed without changes.

src/xdist/plugin.py

+11
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,17 @@ def pytest_addoption(parser: pytest.Parser) -> None:
127127
"(default) no: Run tests inprocess, don't distribute."
128128
),
129129
)
130+
group.addoption(
131+
"--no-loadscope-reorder",
132+
action="store_true",
133+
dest="noloadscopenoreorder",
134+
default=False,
135+
help=(
136+
"Reorders tests when used in conjunction with loadscope.\n"
137+
"Will order tests by number of tests per scope as a best-effort"
138+
" attempt to evenly distribute scopes across all workers."
139+
)
140+
)
130141
group.addoption(
131142
"--tx",
132143
dest="tx",

src/xdist/scheduler/loadscope.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -371,11 +371,15 @@ def schedule(self) -> None:
371371
work_unit = unsorted_workqueue.setdefault(scope, {})
372372
work_unit[nodeid] = False
373373

374-
# Insert tests scopes into work queue ordered by number of tests.
375-
for scope, nodeids in sorted(
376-
unsorted_workqueue.items(), key=lambda item: -len(item[1])
377-
):
378-
self.workqueue[scope] = nodeids
374+
if self.config.option.noloadscopenoreorder:
375+
for scope, nodeids in unsorted_workqueue.items():
376+
self.workqueue[scope] = nodeids
377+
else:
378+
# Insert tests scopes into work queue ordered by number of tests.
379+
for scope, nodeids in sorted(
380+
unsorted_workqueue.items(), key=lambda item: -len(item[1])
381+
):
382+
self.workqueue[scope] = nodeids
379383

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

testing/acceptance_test.py

+16
Original file line numberDiff line numberDiff line change
@@ -1254,6 +1254,22 @@ def test(i):
12541254
"test_b.py::test", result.outlines
12551255
) == {"gw0": 20}
12561256

1257+
def test_workqueue_ordered_by_input(self, pytester: pytest.Pytester) -> None:
1258+
test_file = """
1259+
import pytest
1260+
@pytest.mark.parametrize('i', range({}))
1261+
def test(i):
1262+
pass
1263+
"""
1264+
pytester.makepyfile(test_a=test_file.format(10), test_b=test_file.format(20))
1265+
result = pytester.runpytest("-n2", "--dist=loadscope", "--no-loadscope-reorder", "-v")
1266+
assert get_workers_and_test_count_by_prefix(
1267+
"test_a.py::test", result.outlines
1268+
) == {"gw0": 10}
1269+
assert get_workers_and_test_count_by_prefix(
1270+
"test_b.py::test", result.outlines
1271+
) == {"gw1": 20}
1272+
12571273
def test_module_single_start(self, pytester: pytest.Pytester) -> None:
12581274
"""Fix test suite never finishing in case all workers start with a single test (#277)."""
12591275
test_file1 = """

0 commit comments

Comments
 (0)