diff --git a/python/lib/sift_client/_tests/util/test_report_context.py b/python/lib/sift_client/_tests/util/test_report_context.py index 73d738a7d2..ead4e5f732 100644 --- a/python/lib/sift_client/_tests/util/test_report_context.py +++ b/python/lib/sift_client/_tests/util/test_report_context.py @@ -99,3 +99,17 @@ def test_worker_nonzero_exit_warns_stderr_no_raise() -> None: assert "exited with code 2" in messages assert "rpc deadline exceeded" in messages assert "import-test-result-log --incremental" in messages + + +def test_replay_command_runs_module_through_current_interpreter() -> None: + """Replay argv must not depend on PATH resolution. + + The ``import-test-result-log`` console script installs into the venv's + ``bin/``, which is absent from PATH under ``sudo`` (sudoers' secure_path + replaces PATH even with ``-E``) and when pytest runs from a non-activated + venv. Spawning by bare name raised FileNotFoundError out of ``__enter__`` + there, erroring the session-scoped fixture and blocking every test. + """ + rc = ReportContext(_make_simulate_client(), name="test", log_file=True) + cmd = rc._build_replay_command() + assert cmd[:3] == [sys.executable, "-m", "sift_client.scripts.import_test_result_log"] diff --git a/python/lib/sift_client/util/test_results/context_manager.py b/python/lib/sift_client/util/test_results/context_manager.py index 2fc849d6f5..a3d59b1052 100644 --- a/python/lib/sift_client/util/test_results/context_manager.py +++ b/python/lib/sift_client/util/test_results/context_manager.py @@ -5,6 +5,7 @@ import os import socket import subprocess +import sys import traceback import warnings from collections import Counter @@ -325,7 +326,15 @@ def _build_replay_command(self) -> list[str]: branches without depending on the real replay binary. """ cmd = [ - "import-test-result-log", + # Invoked through the running interpreter rather than by the bare + # ``import-test-result-log`` name, which resolves through PATH. The + # console script installs into the venv's bin/, so a bare-name spawn + # raises FileNotFoundError wherever that bin/ isn't on PATH: under + # ``sudo`` (sudoers' secure_path replaces PATH even with ``-E``), or + # running ``python -m pytest`` against a non-activated venv. + sys.executable, + "-m", + "sift_client.scripts.import_test_result_log", "--incremental", str(self.log_file), "--grpc-url", @@ -361,7 +370,7 @@ def _open_import_proc(self): stderr=subprocess.PIPE, ) except OSError as exc: - # e.g. the ``import-test-result-log`` entry point isn't on PATH. + # e.g. the interpreter can't import the replay module. # Surface it; the JSONL log is still on disk for a manual replay. log_event( logger, logging.WARNING, "replay.spawn_failed", error=repr(exc), log=self.log_file