Files
hermes-agent/tests/pm/test_worker.py
ethernet 9b4ed5f5ec refactor(pm): one plugin-input request for sync_venv and venv_is_current
sync_venv took four mutually exclusive plugin kwargs (plugin_dirs,
extra_plugin_dirs, selection, staged_plugin) and venv_is_current two, with
runtime ValueErrors guarding the combinations. Replace them with a single
`plugins=` argument typed as one of pm.plugin_inputs.Members, Candidates,
Selection or StagedUpdate, so a conflicting request cannot be expressed.
The module also owns the worker wire encoding that client.py and worker.py
each duplicated.

pm.install.sync_venv is split into cohesive helpers (feature policy,
install lock, publication snapshot, target selection, commit) with the
same ordering, receipts and recovery; its CC drops from 54 to 16.

All in-tree callers and tests move to the new argument.
2026-09-24 14:08:21 -04:00

608 lines
28 KiB
Python

"""Real isolated workers: no parent imports or callables cross the wire."""
from __future__ import annotations
import importlib
from pathlib import Path
import subprocess
import shutil
import sys
import pytest
from pm import paths
from pm.package import InstallError
from pm.plugin_inputs import Members, Selection
from pm.runtime import runtime_python
from tests.pm._range_server import RangeHandler, dl_server, url # noqa: F401
from tests.pm.test_runtime_wheelhouse import locked_wheelhouse # noqa: F401
from tests.pm._fixtures import (
_wheel,
build_worker as build_worker,
client as client,
isolated_python as isolated_python,
)
# Spawns children with a home it builds itself; the parent's must stay real.
pytestmark = pytest.mark.real_machine_home
def test_isolated_worker_preserves_install_error(client, monkeypatch):
engine = importlib.import_module("pm.install")
monkeypatch.setattr(engine, "ensure", lambda *a, **kw: pytest.fail("engine ran in caller"))
with pytest.raises(InstallError) as caught:
client.ensure("node", explicit=True)
assert caught.value.package == "node"
assert caught.value.cause == "not in the lockfile"
assert caught.value.remedy == "add it with `hermes pm lock --bump`"
assert not paths.facts_path().exists()
def test_refused_or_already_paused_install_does_not_acquire_runtime(client, monkeypatch):
import threading
from pm.downloader import DownloadPaused
monkeypatch.setattr(client, "runtime_command", lambda path, **kwargs: pytest.fail("refusal acquired PM runtime"))
monkeypatch.setenv("HERMES_DISABLE_LAZY_INSTALLS", "1")
with pytest.raises(InstallError, match="lazy installs are disabled"):
client.ensure("node")
paused = threading.Event()
paused.set()
with pytest.raises(DownloadPaused):
client.ensure("node", explicit=True, pause_event=paused)
def _current_environment(tmp_path, monkeypatch, members):
from pm.environments import install_state_dir
from pm.lock import Facts
from pm.packages import Venv
repo = tmp_path / "project"
repo.mkdir()
(repo / "uv.lock").write_text("version = 1\n")
monkeypatch.setattr(paths, "repo_root", lambda: repo)
environment = install_state_dir(repo) / "environments" / "existing" / "venv"
environment.mkdir(parents=True)
(environment / "pyvenv.cfg").write_text("home = test\n")
Facts(paths.runtime_facts_path()).record_state(
"venv", Venv().expected_stamp([], plugin_dirs=members), [], environment=environment,
)
return repo
@pytest.mark.parametrize("member_shape", ["paths", "sources"])
@pytest.mark.parametrize("route", ["worker", "direct", "foreign-runtime"])
def test_currency_probe_preserves_union_and_candidate_inputs(client, tmp_path, monkeypatch, isolated_python,
member_shape, route):
import json
from pm.environments import runtime_facts_path, selected_venv
from pm.lock import Facts
from pm.packages import Venv
candidate = tmp_path / "candidate"
candidate.mkdir()
manifest = candidate / "plugin.yaml"
manifest.write_text('name: candidate\npython_dependencies: ["candidate-dep==1"]\n')
members = [candidate] if member_shape == "paths" else {tmp_path / "installed": candidate}
repo = _current_environment(tmp_path, monkeypatch, members)
environment = selected_venv(repo)
recorded = ["incumbent-extra", "provider-extra"]
facts_path = runtime_facts_path(repo)
Facts(facts_path).record_state(
"venv", Venv(repo).expected_stamp(recorded, plugin_dirs=members), recorded,
environment=environment,
)
monkeypatch.setattr(client, "is_runtime", lambda: route != "worker")
if route == "foreign-runtime":
monkeypatch.setattr(paths, "repo_root", lambda: tmp_path / "other-project")
root_args = {"project_root": repo} if route != "worker" else {}
acquisitions = []
def ready_runtime(*, bootstrap, cache):
assert bootstrap is False, "currency probe attempted to bootstrap PM"
acquisitions.append(bootstrap)
return isolated_python
monkeypatch.setattr("pm.runtime.runtime_python", ready_runtime)
if route != "direct":
engine = importlib.import_module("pm.install")
monkeypatch.setattr(engine, "venv_is_current", lambda **kw: pytest.fail("probe ran in caller"))
def snapshot():
return {path.relative_to(tmp_path): (path.read_bytes() if path.is_file() else None)
for path in tmp_path.rglob("*")}
from pm.lock import Lockfile
pins = Lockfile(paths.lockfile_path())
pins.set_pin("python", "test.1", {"any": {"url": "https://example.invalid/python", "sha256": "1" * 64}})
pins.save()
Facts(facts_path).record_state("venv", Venv(repo).expected_stamp(recorded, plugin_dirs=members), recorded,
environment=environment)
before = snapshot()
assert client.venv_is_current(extras=["provider-extra"], plugins=Members(members), **root_args)
assert client.venv_is_current(extras=[], plugins=Members(members), **root_args)
assert not client.venv_is_current(extras=["new-extra"], plugins=Members(members), **root_args)
assert not client.venv_is_current(extras=recorded, plugins=Members([]), **root_args)
assert snapshot() == before, "currency queries changed dependency state"
assert bool(acquisitions) is (route != "direct")
pins.set_pin("uv", "unrelated", {"any": {"url": "https://example.invalid/uv", "sha256": "3" * 64}})
pins.save()
unchanged = snapshot()
assert client.venv_is_current(extras=[], plugins=Members(members), **root_args)
assert snapshot() == unchanged
for version, digest in [("test.1", "2" * 64), ("test.2", "1" * 64)]:
pins.set_pin("python", version, {"any": {"url": "https://example.invalid/python", "sha256": digest}})
pins.save()
changed = snapshot()
assert not client.venv_is_current(plugins=Members(members), **root_args)
assert snapshot() == changed
pins.set_pin("python", "test.1", {"any": {"url": "https://example.invalid/python", "sha256": "1" * 64}})
pins.save()
marker = environment / "pyvenv.cfg"
contents = marker.read_bytes()
marker.unlink()
changed = snapshot()
assert not client.venv_is_current(plugins=Members(members), **root_args)
assert snapshot() == changed
marker.write_bytes(contents)
assert client.venv_is_current(plugins=Members(members), **root_args)
manifest.write_text('name: candidate\npython_dependencies: ["candidate-dep==2"]\n')
changed = snapshot()
assert not client.venv_is_current(extras=["provider-extra"], plugins=Members(members), **root_args)
assert snapshot() == changed
assert selected_venv(repo) == environment
# Corruption must not be mistaken for a missing or current environment.
data = json.loads(facts_path.read_text())
data["packages"]["venv"]["extras"] = "provider-extra"
facts_path.write_text(json.dumps(data))
malformed = snapshot()
with pytest.raises(ValueError, match="invalid recorded dependency state"):
client.venv_is_current(extras=[], plugins=Members(members), **root_args)
assert snapshot() == malformed
def _assert_worker_holds_lock(repo):
from pm.environments import install_state_dir
from pm.filesystem import lock_fd
with (install_state_dir(repo) / ".install.lock").open("a+b") as lock:
assert not lock_fd(lock.fileno(), wait=False), "callback escaped the worker's runtime lock"
@pytest.mark.parametrize("explicit", [True, False])
def test_sync_discovers_profile_members_after_worker_acquires_lock(client, tmp_path, monkeypatch, isolated_python, explicit):
from concurrent.futures import ThreadPoolExecutor
import time
from hermes_cli.runtime_state import runtime_lock
from tests.pm._fixtures import worker_toolchain
sibling = tmp_path / "home/profiles/sibling/plugins/dependency"
sibling.mkdir(parents=True)
(sibling / "plugin.yaml").write_text("name: dependency\npython_dependencies: [fixture-dep==1]\n")
repo = _current_environment(tmp_path, monkeypatch, [sibling])
ready = tmp_path / "waiting-for-lock"
worker_toolchain(client, monkeypatch, isolated_python,
"from contextlib import contextmanager\nimport hermes_cli.runtime_state as state\n"
"original = state.runtime_lock\n@contextmanager\ndef lock(project, **kwargs):\n"
f" Path({str(ready)!r}).touch()\n"
" with original(project, **kwargs) as held:\n yield held\nstate.runtime_lock = lock\n")
with ThreadPoolExecutor() as executor:
with runtime_lock(repo):
future = executor.submit(client.sync_venv, explicit=explicit, plugins=Selection({
"home": str(tmp_path / "home"), "enabled": ["plain"], "disabled": [],
}))
deadline = time.monotonic() + 15
while not ready.exists() and time.monotonic() < deadline:
time.sleep(0.01)
assert ready.exists(), "worker never reached the install lock"
assert not future.done(), "worker ignored the install lock"
(sibling.parent.parent / "config.yaml").write_text("plugins:\n enabled: [dependency]\n")
future.result(timeout=30)
assert client.venv_is_current()
@pytest.mark.parametrize("current", [True, False])
@pytest.mark.parametrize("tools_present", [False, True], ids=["cold-tools", "ready-tools"])
def test_lazy_disabled_sync_does_not_bootstrap_tools(client, tmp_path, monkeypatch, isolated_python, current, tools_present):
import json
from pm import receipt
repo = _current_environment(tmp_path, monkeypatch, [])
if not current:
(repo / "uv.lock").write_text("version = 2\n")
monkeypatch.setenv("HERMES_DISABLE_LAZY_INSTALLS", "1")
# Exercise runtime acquisition too: the other worker tests supply a ready
# interpreter, which hides an explicit tool install before worker refusal.
monkeypatch.setattr("pm.runtime.runtime_python", runtime_python)
from pm import _uv
original_toolchain = _uv._toolchain
def toolchain(**kwargs):
assert kwargs.get("realize") is False, "lazy-disabled sync bootstrapped tools"
if tools_present:
return tmp_path / "uv", isolated_python
return original_toolchain(**kwargs)
monkeypatch.setattr(_uv, "_toolchain", toolchain)
monkeypatch.setattr("pm.runtime_stage.stage_runtime",
lambda *a, **kw: pytest.fail("lazy-disabled sync prepared PM runtime"))
with receipt.worker_context("lazy-disabled-sync"):
with pytest.raises(InstallError, match="lazy installs are disabled") as caught:
client.sync_venv([], plugins=Members([]))
result = receipt.last_for_update("lazy-disabled-sync", consume=True)
assert caught.value.package == "pm-runtime"
assert result is not None
assert result["outcome"] == "failed"
receipts = list((tmp_path / "home" / "logs" / "update_receipts").glob("pm_*.json"))
assert len(receipts) == 1
assert json.loads(receipts[0].read_text()) == result
assert not paths.facts_path().exists()
def test_invalid_selection_waits_for_failed_receipt_and_lock_release(client, tmp_path, monkeypatch):
import json
from pm.environments import install_state_dir
from pm.filesystem import lock_fd
repo = _current_environment(tmp_path, monkeypatch, [])
home = tmp_path / "home"
(home / "config.yaml").write_text("plugins: []\n")
with pytest.raises(ValueError, match="plugins must be a mapping"):
client.sync_venv([], explicit=True, plugins=Selection({"home": str(home), "enabled": [], "disabled": []}))
receipts = list((home / "logs" / "update_receipts").glob("pm_*.json"))
assert len(receipts) == 1
assert json.loads(receipts[0].read_text())["outcome"] == "failed"
with (install_state_dir(repo) / ".install.lock").open("a+b") as lock:
assert lock_fd(lock.fileno(), wait=False)
def _node_archive(server, body=b"#!/bin/sh\nexit 0\n"):
import hashlib
import io
import zipfile
from pm.lock import Lockfile
from pm.registry import get_package
from pm.store import current_target
target = current_target()
package = get_package("node")
relative = package.binary(Path("."), target)
assert relative is not None
stream = io.BytesIO()
with zipfile.ZipFile(stream, "w") as archive:
info = zipfile.ZipInfo((Path("node-package") / relative).as_posix())
info.external_attr = 0o100755 << 16
archive.writestr(info, body)
payload = stream.getvalue()
RangeHandler.payloads["/node.zip"] = payload
lock = Lockfile(paths.lockfile_path())
lock.set_pin("node", "1", {target: {"url": url(server, "/node.zip"),
"sha256": hashlib.sha256(payload).hexdigest()}})
lock.save()
return target, relative, body
@pytest.mark.platforms("posix")
@pytest.mark.parametrize("operation", ["ensure", "stage_only"])
def test_worker_artifact_lifecycle_keeps_identity_and_relays_progress(client, dl_server, operation):
import hashlib
from pm.lock import Facts, Lockfile
from pm.store import tree_digest
target, relative, body = _node_archive(dl_server)
stages, downloads = [], []
def progress(*args):
stages.append(args)
return object() # Notifications never serialize caller-owned return values.
if operation == "stage_only":
entry = client.stage_only("node", target, progress=progress)
assert isinstance(entry, Path)
assert not paths.facts_path().exists()
else:
base = {"PATH": "/caller/bin", "CALLER": "kept", "PYTHONPATH": "/caller/dependencies"}
runner = client.ensure("node", explicit=True, base_env=base,
progress=lambda *args: stages.append(args),
download_progress=lambda *args: downloads.append(args))
fact = Facts(paths.facts_path()).get("node")
entry = paths.store_root() / fact["entry"]
assert runner.env["CALLER"] == "kept"
assert runner.env["PYTHONPATH"] == base["PYTHONPATH"]
assert runner.env["PATH"].endswith(base["PATH"])
assert downloads and downloads[-1][0] == downloads[-1][1]
assert all(isinstance(row, tuple) for rows in downloads[-1][2].values() for row in rows)
assert (entry / relative).read_bytes() == body
assert stages and stages[0][0] == "download"
def install():
if operation == "stage_only":
return client.stage_only("node", target)
client.ensure("node", explicit=True)
return entry
first_tree = tree_digest(entry)
RangeHandler.payloads.clear()
assert install() == entry
assert tree_digest(entry) == first_tree
_, _, replacement = _node_archive(dl_server, b"#!/bin/sh\nexit 0\n# repinned\n")
assert install() == entry
assert (entry / relative).read_bytes() == replacement
assert not list(paths.store_root().glob("fetch-*"))
good_tree = tree_digest(entry)
previous_facts = paths.facts_path().read_bytes() if paths.facts_path().exists() else None
lock = Lockfile(paths.lockfile_path())
RangeHandler.payloads["/node.zip"] = b"not an archive"
lock.set_pin("node", "1", {target: {"url": url(dl_server, "/node.zip"),
"sha256": hashlib.sha256(b"not an archive").hexdigest()}})
lock.save()
with pytest.raises(RuntimeError if operation == "stage_only" else InstallError, match="zip"):
install()
assert tree_digest(entry) == good_tree
if operation == "stage_only":
assert not paths.facts_path().exists(), "foreign staging cannot publish host identity"
else:
assert paths.facts_path().read_bytes() == previous_facts
@pytest.mark.platforms("posix")
@pytest.mark.parametrize("from_progress", [False, True])
def test_pause_event_reaches_running_worker_without_hanging(client, dl_server, monkeypatch, from_progress):
import threading
from pm.downloader import DownloadPaused
from pm.lock import Facts
_node_archive(dl_server, b"#!/bin/sh\nexit 0\n#" + b"x" * (8 << 20))
RangeHandler.slow_per_chunk = 0.03
pause, transferring = threading.Event(), threading.Event()
original = RangeHandler.do_GET
def get(handler):
if handler.headers.get("Range") not in (None, "bytes=0-0"):
transferring.set()
original(handler)
monkeypatch.setattr(RangeHandler, "do_GET", get)
def cancel():
assert transferring.wait(10), "worker never began transferring"
pause.set()
thread = None
progress = None
if from_progress:
def progress(stage, done, total, label):
if stage == "download" and done:
pause.set()
else:
thread = threading.Thread(target=cancel)
thread.start()
try:
with pytest.raises(DownloadPaused):
client.ensure("node", explicit=True, progress=progress, pause_event=pause)
finally:
if thread is not None:
thread.join(timeout=15)
assert not thread.is_alive()
assert Facts(paths.facts_path()).get("node") is None
@pytest.mark.platforms("posix")
def test_installed_tool_needs_no_pm_runtime(client, dl_server, monkeypatch):
_node_archive(dl_server)
client.ensure("node", explicit=True)
monkeypatch.setattr("pm.runtime.runtime_python", lambda: pytest.fail("hot path bootstrapped PM"))
assert client.ensure("node", base_env={"PATH": "caller"}).env["PATH"].endswith("caller")
def test_environment_probe_needs_no_pm_runtime(client, monkeypatch, tmp_path):
from pm import environment_python, python_tool
monkeypatch.setattr("pm.runtime.runtime_python", lambda: pytest.fail("probe bootstrapped PM"))
root = tmp_path / "not-created"
assert environment_python("test", root=root) is None
assert python_tool("test", "test", root=root) is None
assert not root.exists()
def test_worker_receipt_is_exact_even_if_latest_is_replaced(client, tmp_path, monkeypatch):
import json
from pm import receipt
_current_environment(tmp_path, monkeypatch, [])
original_accept = receipt.accept_worker_receipt
received = []
def accept(data, update_id):
# Simulate a different process publishing after this worker completes.
point = tmp_path / "home" / "logs" / "update_receipts" / "latest.json"
point.write_text(json.dumps({"update_id": "unrelated", "outcome": "failed"}))
received.append(data)
original_accept(data, update_id)
monkeypatch.setattr(receipt, "accept_worker_receipt", accept)
with receipt.worker_context("my-update"):
client.sync_venv([], explicit=True, plugins=Members([]))
result = receipt.last_for_update("my-update", consume=True)
assert received and result == received[0]
assert result["update_id"] == "my-update" and result["outcome"] == "ok"
def _patch_worker_apply(client, monkeypatch, isolated_python, body):
"""Fault injection at the build boundary, not an alternate transport/engine."""
import textwrap
worker = Path(client.__file__).with_name("worker.py")
script = (
"import os, runpy, sys\n"
f"sys.path.insert(0, {str(worker.parent.parent)!r})\n"
"from pm.packages import Venv\n"
"from pm.workspace import ResolutionConflict\n"
"def apply(self, *args, **kwargs):\n"
+ textwrap.indent(body, " ") + "\n"
"Venv.apply = apply\n"
f"runpy.run_path({str(worker)!r}, run_name='__main__')\n"
)
monkeypatch.setattr(client, "runtime_command", lambda path, **kwargs: [str(isolated_python), "-I", "-B", "-c", script])
def test_resolution_conflict_survives_worker_and_receipt(client, tmp_path, monkeypatch, isolated_python, capfd):
from pm import receipt
from pm.workspace import ResolutionConflict
repo = _current_environment(tmp_path, monkeypatch, [])
(repo / "uv.lock").write_text("version = 2\n")
_patch_worker_apply(client, monkeypatch, isolated_python,
"print('engine stdout', flush=True)\n"
"os.write(1, b'native stdout\\n')\n"
"raise ResolutionConflict('venv', 'impossible union', 'change member')")
with receipt.worker_context("conflict-update"):
with pytest.raises(ResolutionConflict) as caught:
client.sync_venv([], explicit=True, plugins=Members([]))
result = receipt.last_for_update("conflict-update", consume=True)
assert (caught.value.package, caught.value.cause, caught.value.remedy) == (
"venv", "impossible union", "change member")
assert result["outcome"] == "failed"
assert "engine stdout" in capfd.readouterr().err
def test_failed_facts_write_restores_exact_config_before_reporting(client, tmp_path, monkeypatch, isolated_python):
from tests.pm._fixtures import worker_toolchain
from pm.environments import install_state_dir
repo = _current_environment(tmp_path, monkeypatch, [])
home = tmp_path / "home"
config = home / "config.yaml"
config.write_text("# preserve me\nplugins: {enabled: [old]}\n")
previous = config.read_bytes()
facts = (install_state_dir(repo) / "facts.json").read_bytes()
(repo / "uv.lock").write_text("version = 2\n")
worker_toolchain(client, monkeypatch, isolated_python,
"from pm.packages import Venv\nfrom pm.lock import Facts\n"
"Venv.apply = lambda *args, **kwargs: {}\n"
"def fail(*args, **kwargs):\n"
f" assert b'new' in Path({str(config)!r}).read_bytes()\n"
" raise OSError('facts disk full')\nFacts.record_state = fail\n")
with pytest.raises(OSError, match="facts disk full"):
client.sync_venv(explicit=True, plugins=Selection({"home": str(home), "enabled": ["new"], "disabled": []}))
assert config.read_bytes() == previous
assert (install_state_dir(repo) / "facts.json").read_bytes() == facts
assert not (install_state_dir(repo) / "publication.json").exists()
def test_invalid_arguments_keep_the_engine_exception_type(client):
with pytest.raises(ValueError, match="repair restores"):
client.sync_venv([], repair=True)
with pytest.raises(ValueError, match="environment name"):
client.ensure_environment("../escape", ["example==1"], explicit=True)
with pytest.raises(KeyError):
client.ensure("no-such-package", explicit=True)
def test_worker_death_reports_transport_failure(client, monkeypatch, isolated_python):
monkeypatch.setattr(client, "runtime_command", lambda path, **kwargs: [str(isolated_python), "-I", "-c", "import os; os._exit(7)"])
with pytest.raises(InstallError, match="worker.*result"):
client.ensure("node", explicit=True)
def test_foreign_checkout_sync_uses_its_own_pm_generation(client, tmp_path, monkeypatch, isolated_python):
from pm import venv_is_current
from pm.environments import selected_venv, runtime_facts_path
from tests.pm._fixtures import worker_toolchain
worker_toolchain(client, monkeypatch, isolated_python)
foreign = tmp_path / "other checkout"
foreign.mkdir()
(foreign / "pyproject.toml").write_text(
'[project]\nname="foreign-proof"\nversion="1"\nrequires-python=">=3.11"\n'
'[tool.uv]\npackage=false\n', encoding="utf-8")
client.lock_project(foreign, offline=True, explicit=True)
assert not venv_is_current(project_root=foreign)
original_root = paths.repo_root()
client.sync_venv([], project_root=foreign, plugins=Members([]), explicit=True)
selected = selected_venv(foreign)
assert selected != foreign / "venv"
assert selected.is_relative_to(runtime_facts_path(foreign).parent)
assert runtime_facts_path(foreign).is_file()
assert venv_is_current(project_root=foreign)
assert paths.repo_root() == original_root
assert not runtime_facts_path(original_root).exists()
def test_cold_manager_build_does_not_bootstrap_a_worker(client, tmp_path, monkeypatch, locked_wheelhouse):
import pm
wheels, _ = locked_wheelhouse
uv = shutil.which("uv")
assert uv
monkeypatch.setattr(client, "runtime_command", lambda *a, **kw: pytest.fail("manager build requested itself"))
monkeypatch.setattr("pm._uv._toolchain", lambda **kwargs: (Path(uv), Path(sys.executable))
if kwargs == {"realize": False} else pytest.fail("bootstrap tried to install tools"))
project = Path(__file__).resolve().parents[2] / "pm"
python = pm.stage_manager_runtime(python=Path(sys.executable), destination=tmp_path / "manager",
project=project, wheelhouse=wheels, offline=True)
result = subprocess.run([str(python), "-I", "-c", "import packaging, tomli_w, truststore; from ruamel.yaml import YAML"],
capture_output=True, text=True, timeout=30)
assert result.returncode == 0, result.stderr
with pytest.raises(FileExistsError):
pm.stage_manager_runtime(python=Path(sys.executable), destination=python.parent.parent)
def test_worker_side_environment_reuses_and_keeps_selection_on_failed_tool(client, tmp_path, monkeypatch, isolated_python):
import zipfile
from pm import environment_python, python_tool
from tests.pm._fixtures import worker_toolchain
worker_toolchain(client, monkeypatch, isolated_python)
wheel = _wheel(tmp_path, "side_dep")
with zipfile.ZipFile(wheel, "a") as archive:
archive.writestr("side_dep/cli.py", "def main():\n print('real side dependency')\n")
archive.writestr("side_dep-1.0.dist-info/entry_points.txt", "[console_scripts]\nside-proof = side_dep.cli:main\n")
requirements = [f"side-dep @ {wheel.as_uri()}"]
root = tmp_path / "side environment"
assert environment_python("proof", root=root) is None
executable = client.ensure_python_tool("proof", requirements, "side-proof", root=root, explicit=True)
assert python_tool("proof", "side-proof", root=root) == executable
result = subprocess.run([str(executable)], capture_output=True, text=True, timeout=30)
assert result.returncode == 0, result.stderr
assert result.stdout.strip() == "real side dependency"
selected = environment_python("proof", root=root)
selection = (root / "active.json").read_bytes()
generations = set(root.glob("gen-*"))
assert client.ensure_environment("proof", requirements, root=root) == selected
assert set(root.glob("gen-*")) == generations
with pytest.raises(InstallError, match="do not provide"):
client.ensure_python_tool("proof", requirements, "missing-command", root=root, explicit=True)
assert (root / "active.json").read_bytes() == selection
assert set(root.glob("gen-*")) == generations
assert python_tool("proof", "side-proof", root=root) == executable
monkeypatch.setenv("HERMES_DISABLE_LAZY_INSTALLS", "1")
with pytest.raises(InstallError, match="lazy installs are disabled"):
client.ensure_environment("proof", ["absent-dependency==0"], root=root)
assert (root / "active.json").read_bytes() == selection
@pytest.mark.parametrize("operation", ["activate", "stage_manager_runtime"])
@pytest.mark.parametrize("route", ["client", "wire"])
def test_unknown_worker_operation_is_not_dispatched(client, monkeypatch, tmp_path, isolated_python, operation, route):
import json
arguments = {"destination": str(tmp_path / "unused")}
if route == "client":
monkeypatch.setattr(client, "runtime_command", lambda *a, **kw: pytest.fail("unsupported operation acquired PM"))
with pytest.raises(KeyError, match=operation):
client._request(operation, arguments)
else:
request = {"id": "unsupported", "operation": operation, "arguments": arguments,
"callbacks": [], "packages": [],
"context": {"repo": str(paths.repo_root()), "lockfile": str(paths.lockfile_path())}}
worker = Path(client.__file__).with_name("worker.py")
result = subprocess.run(client.runtime_command(worker), input=json.dumps(request) + "\n",
capture_output=True, text=True, encoding="utf-8", timeout=30,
env=client.runtime_environment())
assert result.returncode == 0, result.stderr
response = json.loads(result.stdout)
assert response["error"]["type"] == "KeyError", response
assert operation in response["error"]["message"]
assert not paths.facts_path().exists()