diff --git a/src/xdist/plugin.py b/src/xdist/plugin.py index 0a07b712..88c5b74a 100644 --- a/src/xdist/plugin.py +++ b/src/xdist/plugin.py @@ -97,6 +97,13 @@ def pytest_addoption(parser: pytest.Parser) -> None: help="Maximum number of workers that can be restarted " "when crashed (set to zero to disable this feature)", ) + group.addoption( + "--dist-reset-workers", + action="store_true", + dest="dist_reset_workers", + default=False, + help="If set, workers are restarted after each test file.", + ) group.addoption( "--dist", metavar="distmode", diff --git a/src/xdist/remote.py b/src/xdist/remote.py index 436b5ddf..9c88ab08 100644 --- a/src/xdist/remote.py +++ b/src/xdist/remote.py @@ -119,6 +119,7 @@ def __init__(self, config: pytest.Config, channel: execnet.Channel) -> None: self.channel = channel self.torun = TestQueue(self.channel.gateway.execmodel) self.nextitem_index: int | None | Literal[Marker.SHUTDOWN] = None + self.shutdown_on_finish = False config.pluginmanager.register(self) def sendevent(self, name: str, **kwargs: object) -> None: @@ -170,6 +171,8 @@ def handle_command( self.torun.put(i) elif name == "shutdown": self.torun.put(Marker.SHUTDOWN) + elif name == "shutdown_after_finished": + self.shutdown_on_finish = True elif name == "steal": self.steal(kwargs["indices"]) @@ -200,12 +203,15 @@ def steal(self, indices: Sequence[int]) -> None: @pytest.hookimpl def pytest_runtestloop(self, session: pytest.Session) -> bool: self.log("entering main loop") + self.shutdown_on_finish = False self.channel.setcallback(self.handle_command, endmarker=Marker.SHUTDOWN) self.nextitem_index = self.torun.get() while self.nextitem_index is not Marker.SHUTDOWN: self.run_one_test() if session.shouldfail or session.shouldstop: break + if self.shutdown_on_finish: + os._exit(1) return True def run_one_test(self) -> None: diff --git a/src/xdist/scheduler/loadscope.py b/src/xdist/scheduler/loadscope.py index 73162dcd..116bbcfe 100644 --- a/src/xdist/scheduler/loadscope.py +++ b/src/xdist/scheduler/loadscope.py @@ -281,6 +281,9 @@ def _assign_work_unit(self, node: WorkerController) -> None: node.send_runtest_some(nodeids_indexes) + if self.config.option.dist_reset_workers: + node.send_shutdown_after_finished() + def _split_scope(self, nodeid: str) -> str: """Determine the scope (grouping) of a nodeid. diff --git a/src/xdist/workermanage.py b/src/xdist/workermanage.py index 201c8e71..5fd7ff91 100644 --- a/src/xdist/workermanage.py +++ b/src/xdist/workermanage.py @@ -372,6 +372,9 @@ def send_runtest_all(self) -> None: def send_steal(self, indices: Sequence[int]) -> None: self.sendcommand("steal", indices=indices) + def send_shutdown_after_finished(self) -> None: + self.sendcommand("shutdown_after_finished") + def shutdown(self) -> None: if not self._down: try: