diff --git a/src/xdist/workermanage.py b/src/xdist/workermanage.py index 201c8e71..455e4d3b 100644 --- a/src/xdist/workermanage.py +++ b/src/xdist/workermanage.py @@ -1,6 +1,7 @@ from __future__ import annotations from collections.abc import Sequence +from concurrent.futures import ThreadPoolExecutor import enum import fnmatch import os @@ -92,17 +93,29 @@ def setup_nodes( self, putevent: Callable[[tuple[str, dict[str, Any]]], None], ) -> list[WorkerController]: + # create basetemp directory only once + if hasattr(self.config, "_tmp_path_factory"): + self.config._tmp_path_factory.getbasetemp() + self.config.hook.pytest_xdist_setupnodes(config=self.config, specs=self.specs) self.trace("setting up nodes") - return [self.setup_node(spec, putevent) for spec in self.specs] + with ThreadPoolExecutor(max_workers=len(self.specs)) as executor: + futs = [ + executor.submit(self.setup_node, spec, putevent, idx) + for idx, spec in enumerate(self.specs) + ] + return [f.result() for f in futs] def setup_node( self, spec: execnet.XSpec, putevent: Callable[[tuple[str, dict[str, Any]]], None], + idx: int | None = None, ) -> WorkerController: if getattr(spec, "execmodel", None) != "main_thread_only": spec = execnet.XSpec(f"execmodel=main_thread_only//{spec}") + if idx is not None: + spec = execnet.XSpec(f"{spec}//id=gw{idx}") gw = self.group.makegateway(spec) self.config.hook.pytest_xdist_newgateway(gateway=gw) self.rsync_roots(gw) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 42d5479d..98554ffa 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -1247,12 +1247,16 @@ def test(i): """ 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} + assert list( + get_workers_and_test_count_by_prefix( + "test_a.py::test", result.outlines + ).values() + ) == [10] + assert list( + get_workers_and_test_count_by_prefix( + "test_b.py::test", result.outlines + ).values() + ) == [20] def test_workqueue_ordered_by_input(self, pytester: pytest.Pytester) -> None: test_file = """ diff --git a/testing/test_workermanage.py b/testing/test_workermanage.py index b3e8a1c7..f128a065 100644 --- a/testing/test_workermanage.py +++ b/testing/test_workermanage.py @@ -82,11 +82,19 @@ def test_popen_makegateway_events( call = hookrecorder.popcall("pytest_xdist_setupnodes") assert len(call.specs) == 2 - call = hookrecorder.popcall("pytest_xdist_newgateway") - assert call.gateway.spec == execnet.XSpec("execmodel=main_thread_only//popen") - assert call.gateway.id == "gw0" - call = hookrecorder.popcall("pytest_xdist_newgateway") - assert call.gateway.id == "gw1" + # check expected gateways + gw_calls = [ + hookrecorder.popcall("pytest_xdist_newgateway"), + hookrecorder.popcall("pytest_xdist_newgateway"), + ] + assert {c.gateway.id for c in gw_calls} == {"gw0", "gw1"} + + for c in gw_calls: + expected_spec = execnet.XSpec( + f"execmodel=main_thread_only//popen//id={c.gateway.id}" + ) + assert c.gateway.spec == expected_spec + assert len(hm.group) == 2 hm.teardown_nodes() assert not len(hm.group)