Summary
LoadScopeScheduling._split_scope() uses nodeid.rsplit("::", 1) to
determine the test scope (file). When a parametrized test value contains
:: (e.g. IPv6 addresses), the split lands inside the parameter instead
of at the .py:: boundary. Tests from the same file get assigned to
different workers, each paying the full fixture setup/teardown cost.
Affected code
xdist/scheduler/loadscope.py, _split_scope():
def _split_scope(self, nodeid: str) -> str:
return nodeid.rsplit("::", 1)[0]
Example
nodeid: synthrecord/tests_synthrecord.py::test_forward[cafe:cafe::cafe-AAAA]
^^
rsplit lands here
expected scope: synthrecord/tests_synthrecord.py
actual scope: synthrecord/tests_synthrecord.py::test_forward[cafe:cafe
Impact observed in BIND 9 CI
Six synthrecord tests have IPv6 addresses (::1, cafe:cafe::cafe,
cafe::) in their parameters. These get misclassified into separate
scopes and routed to different workers. Each triggers a full named
startup/shutdown cycle (~30 s). In a real CI run (job 7468217), the
synthrecord scope spanned 1001 s instead of ~8 s because the orphaned
tests were interleaved with unrelated scopes on other workers.
Suggested fix
Split on .py:: instead of the last :::
def _split_scope(self, nodeid: str) -> str:
if ".py::" in nodeid:
return nodeid.split(".py::")[0] + ".py"
return nodeid.rsplit("::", 1)[0]
This is unambiguous: .py:: only appears at the module boundary.
Reproduction
from xdist.scheduler.loadscope import LoadScopeScheduling
s = LoadScopeScheduling.__new__(LoadScopeScheduling)
# Normal test — correct scope
print(s._split_scope("foo/bar.py::test_one"))
# -> foo/bar.py
# IPv6 in parameter — wrong scope
print(s._split_scope("foo/bar.py::test_ip[::1-expected]"))
# -> foo/bar.py::test_ip[
Summary
LoadScopeScheduling._split_scope()usesnodeid.rsplit("::", 1)todetermine the test scope (file). When a parametrized test value contains
::(e.g. IPv6 addresses), the split lands inside the parameter insteadof at the
.py::boundary. Tests from the same file get assigned todifferent workers, each paying the full fixture setup/teardown cost.
Affected code
xdist/scheduler/loadscope.py,_split_scope():Example
Impact observed in BIND 9 CI
Six
synthrecordtests have IPv6 addresses (::1,cafe:cafe::cafe,cafe::) in their parameters. These get misclassified into separatescopes and routed to different workers. Each triggers a full
namedstartup/shutdown cycle (~30 s). In a real CI run (job 7468217), the
synthrecord scope spanned 1001 s instead of ~8 s because the orphaned
tests were interleaved with unrelated scopes on other workers.
Suggested fix
Split on
.py::instead of the last:::This is unambiguous:
.py::only appears at the module boundary.Reproduction