Production: - agent/bedrock_adapter.py, agent/vertex_adapter.py: pm.ensure_import ran at module import. In any process that imports these modules without a committed PM selection (CI's build_environment test venv, a fresh checkout) that sync rebuilt the dependency environment mid-process and replaced sys.path with a generation missing the caller's own packages (anthropic, aiohttp vanished). The extra is now ensured at first client build / credential request. - plugins/platforms/matrix/adapter.py: a complete install needs no ensure_and_bind round trip; only a partial one syncs. - tools/browser_tool.py: drop the facade's duplicate warm_agent_browser_npx_cache shim; the compat pointer already resolves to browser_tool_install. Test harness: - tests/home_io_guard.py: PATH-entry probes (shutil.which) and the running interpreter's own installation (stdlib reads, realpath ancestry, fixture symlinks into it) are not Hermes state; a patched Path.expanduser must not crash the guard. run_tests.sh no longer filters PATH — the guard owns it. - tests/tui_gateway/conftest.py: import hermes_bootstrap before any file opens a MagicMock hermes_constants window (6 files exited the process at boot). - tests/hermes_cli/conftest.py probe_root: scratch checkouts the import guard probes need hermes_bootstrap.py (the launcher imports it). - tests/pm/_fixtures.py stage_host_python: a copied relocatable python needs its stdlib beside it (No module named 'encodings' on CI). - tests/install/e2e-assets/smoke-env.mjs: dependency-free env shaping so the source-build-env probe runs under bare node (main deleted the Playwright entry it was imported through). - adapt main's new tests to branch seams (model_metadata_http, launch completion tail, CI toolchain exports uv after python, source_launch hermes_cli stub, systemd_notify single marker).
99 lines
4.7 KiB
Python
99 lines
4.7 KiB
Python
"""PM Bundle shares preparation, not the desktop product dependency closure."""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
|
|
import pytest
|
|
from tests.pm._fixtures import stage_host_python
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
SCRIPT = ROOT / "scripts/bundles/native_build.py"
|
|
|
|
|
|
def test_payload_transport_does_not_require_or_export_desktop_inputs(tmp_path):
|
|
from scripts.ci.desktop_build_cache import describe_cache
|
|
|
|
source = tmp_path / "source"
|
|
source.mkdir()
|
|
cache = tmp_path / "cache"
|
|
description = describe_cache(source, cache, "payload-test")
|
|
assert set(description["paths"]) == {str(cache / name) for name in ("tools", "python/runtime", "native")}
|
|
assert "payload-test" in description["key"]
|
|
assert not cache.exists()
|
|
|
|
|
|
def test_native_worker_passes_shared_toolchain_to_native_owner(tmp_path, monkeypatch):
|
|
from scripts.bundles import native_build
|
|
from scripts.bundles import desktop_toolchain, native
|
|
|
|
request = {name: str(tmp_path / name) for name in ("source", "work", "cache", "out")}
|
|
request["ref"] = "a" * 40
|
|
selected = {"UV_CACHE_DIR": str(tmp_path / "native-wheel-identity"), "CC": "prepared-compiler"}
|
|
monkeypatch.setattr("scripts.bundles.desktop_prepare.require_source", lambda *args: None)
|
|
prepared_tools = []
|
|
def tools(source, work, cache, inherited):
|
|
prepared_tools.append((source, work, cache))
|
|
return Path("python"), Path("node"), selected
|
|
monkeypatch.setattr(desktop_toolchain, "prepare_tools", tools)
|
|
calls = []
|
|
def prepare_native(**kwargs):
|
|
calls.append(kwargs)
|
|
return tmp_path / "out.prepared.json"
|
|
monkeypatch.setattr(native, "prepare_native", prepare_native)
|
|
assert native_build.prepare_in_worker(request) == tmp_path / "out.prepared.json"
|
|
assert prepared_tools == [(tmp_path / "source", tmp_path / "work", tmp_path / "cache")]
|
|
assert calls == [{"source": tmp_path / "source", "out": tmp_path / "out", "ref": request["ref"],
|
|
"cache": Path(selected["UV_CACHE_DIR"]), "tools": tmp_path / "cache/tools", "env": selected}]
|
|
|
|
|
|
@pytest.mark.platforms("posix")
|
|
def test_native_cli_consumes_real_minimal_preparation_without_bootstrap(tmp_path):
|
|
from scripts.build.inputs import AgentInputs, RESOURCE_ENV
|
|
from scripts.bundles.native_prepared import publish_prepared
|
|
from pm.store import current_target
|
|
|
|
out = tmp_path / "payload"
|
|
code = out / "hermes-agent"
|
|
code.mkdir(parents=True)
|
|
(code / "pyproject.toml").write_text('[project]\nname="native-fixture"\nversion="1.0"\n[project.scripts]\nprobe="entry:main"\n')
|
|
(code / "entry.py").write_text('def main():\n print("native fixture")\n return 0\n')
|
|
for name in RESOURCE_ENV:
|
|
(code / name).mkdir()
|
|
python = stage_host_python(out / "tools/python/bin/python3")
|
|
site = out / "venv/lib/site-packages"
|
|
site.mkdir(parents=True)
|
|
runtime = out / "pm-runtime"
|
|
(runtime / "lib/site-packages").mkdir(parents=True)
|
|
(runtime / "pm-runtime.json").write_text(json.dumps({"python": "../tools/python/bin/python3", "sitePackages": "lib/site-packages"}))
|
|
features = out / "enabled-features.json"
|
|
features.write_text('{"extras":[]}')
|
|
(out / "uv-cache").mkdir()
|
|
prepared = publish_prepared(out, ROOT, "a" * 40, AgentInputs(
|
|
project=code / "pyproject.toml", code=code, repo="hermes-agent", placement="contained",
|
|
target=current_target(), python=python, site_packages=site, environment=out / "venv",
|
|
tools=out / "tools", pm_runtime=runtime, features=features, ref="fixture",
|
|
resources={name: code / name for name in RESOURCE_ENV},
|
|
))
|
|
env = {**os.environ, "UV_OFFLINE": "1", "HERMES_HOME": str(tmp_path / "private")}
|
|
command = [sys.executable, "-S", "-B", str(SCRIPT), "--prepared", str(prepared)]
|
|
result = subprocess.run(command, cwd=tmp_path, env=env, capture_output=True, text=True, timeout=30)
|
|
assert result.returncode == 0, result.stderr
|
|
manifest = json.loads((out / "manifest.json").read_text())
|
|
assert manifest["runtime"]["commands"] == {"probe": "bin/probe"}
|
|
assert not (code / "hermes_cli/tui_dist").exists()
|
|
assert not (code / "hermes_cli/web_dist").exists()
|
|
probe = subprocess.run([str(out / "bin/probe")], cwd=tmp_path, env=env, capture_output=True, text=True, timeout=30)
|
|
assert probe.returncode == 0, probe.stderr
|
|
assert probe.stdout.strip() == "native fixture"
|
|
assert not (tmp_path / "private").exists()
|
|
(site / "changed.py").write_text("changed = True")
|
|
result = subprocess.run(command, cwd=tmp_path, env=env, capture_output=True, text=True, timeout=30)
|
|
assert result.returncode != 0
|
|
assert "prepare" in result.stderr
|
|
assert not (out / "manifest.json").exists()
|