Files
hermes-agent/tests/scripts/test_desktop_update_target.py
ethernet f99d780006 test: finish the CI python-tests backlog against a CI-shaped environment
Verified with a build_environment test venv (no editable finder, no committed
PM selection) plus real uv on PATH — the shape CI runs.

- test_old_updater_takeover: the fake NEW checkout carries update_handoff.py;
  the test never relied on the source being importable from an editable venv.
- test_update_launch_completion: record the completion-tail child instead of
  running the checkout's (nonexistent) source_completion.py.
- test_update_missing_configured_deps: source_launch already stubs
  hermes_cli/; link the remaining modules beside the stub.
- test_worker: the injected runtime_lock stub accepts the timeout kwarg the
  worker now passes (it raised TypeError before reaching the lock).
- test_plugin_guard / test_plugin_install_ref: PM publishes plugins only under
  the active home's plugins/ and does so in the worker — install into the
  sandboxed home, and fail the metadata write inside the worker.
- test_pm_build_consumers: setup_toolchain deliberately puts uv on PATH after
  the interpreter (c8bbac5c6e); assert that ordering instead of its absence.
- test_icon_flavors: tile_color reads the most chromatic lower-half pixel (the
  inward contrasting border and LANCZOS smear defeat a fixed coordinate); the
  SHA glyph readback skips cells the portrait covers (da9f6d2dcd renders her in
  front of the badge) and requires a majority of each glyph.
- test_desktop_update_target: the fixture CLI reports the checkout as its
  install directory again (parents[1] inside the string was bumped with the
  file move in cb7c688171).
- test_termux_python_linkage: skip when the host has no shared libpython or no
  patchelf (relocatable python-build-standalone in CI).
- test_source_build_env: smoke-env.mjs is dependency-free (main removed the
  Playwright entry it imported through); pm/_fixtures.stage_host_python copies
  the stdlib beside the interpreter for payload tests.

test_api_server_runs::test_events_stream_forwards_interim_commentary is an
upstream flake (identical file on origin/main fails 2/3 locally).
2026-09-20 12:40:38 -04:00

270 lines
9.0 KiB
Python

"""Run the real handoffs against a disposable CLI, never an installed updater."""
from __future__ import annotations
import json
import os
from pathlib import Path
import shlex
import subprocess
import sys
import pytest
from tests.installation_launcher_fixture import publish_fixture_launcher
SCRIPTS = Path(__file__).resolve().parents[2] / "scripts" / "desktop-update"
FAKE_CLI = """
import json
import os
from pathlib import Path
import sys
def main():
if '--version' in sys.argv:
print('Install directory: ' + os.environ.get('HANDOFF_FOREIGN', str(Path(__file__).resolve().parents[1]))); return 0
if '--help' in sys.argv:
print('update options')
sys.exit(0)
receipt = Path(os.environ['HANDOFF_CAPTURE'])
previous = receipt.read_text(encoding='utf-8') if receipt.exists() else ''
with receipt.open('a', encoding='utf-8') as stream:
stream.write(json.dumps({'argv': sys.argv[1:], 'home': os.environ.get('HERMES_HOME'),
'install_root': os.environ.get('HERMES_INSTALL_ROOT'),
'cwd': os.getcwd()}) + '\\n')
print('Desktop build failed') if os.environ.get('HANDOFF_EXIT') else None
sys.exit(int(os.environ['HANDOFF_EXIT']) if 'HANDOFF_EXIT' in os.environ else (1 if not previous else 0))
if __name__ == '__main__':
main()
"""
def _run_handoff(tmp_path, target, *, windows=False, inherited_home=True, modern=False, code=0, userbin_only=False, foreign=False):
install = tmp_path / "checkout with spaces"
if windows and not modern:
subprocess.run(
[sys.executable, "-m", "venv", "--without-pip", str(install / "venv")],
check=True,
capture_output=True,
timeout=60,
)
package = (
install / "venv" / "Lib" / "site-packages" if windows and not modern else install
) / "hermes_cli"
package.mkdir(parents=True)
(package / "__init__.py").touch()
(package / "main.py").write_text(FAKE_CLI, encoding="utf-8")
if modern:
launcher = publish_fixture_launcher(install, FAKE_CLI)
if userbin_only:
userbin = tmp_path / '.local/bin'
userbin.mkdir(parents=True)
launcher.rename(userbin / launcher.name)
capture = tmp_path / "calls.jsonl"
home = tmp_path / "profile home" if inherited_home else tmp_path
home.mkdir(exist_ok=True)
env = {
**os.environ,
"HOME": str(tmp_path),
"TMPDIR": str(tmp_path),
"HERMES_INSTALL_ROOT": str(install),
"HANDOFF_CAPTURE": str(capture),
}
env.pop("PYTHONPATH", None)
env.pop("PYTHONHOME", None)
env.pop("HERMES_HOME", None)
if inherited_home:
env["HERMES_HOME"] = str(home)
if modern:
env["HANDOFF_EXIT"] = str(code)
if foreign:
env["HANDOFF_FOREIGN"] = str(tmp_path)
env["HERMES_RUNTIME_DIR"] = str(tmp_path / "empty-store")
if windows:
# The disposable runtime has only the fixture CLI; verification is outside
# this transport contract and runs its own harmless fixture implementation.
(package / "desktop_update_verify.py").write_text(
"def verify_windows_desktop_update(): pass\n",
encoding="utf-8",
)
command = [
"powershell",
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
str(SCRIPTS / "windows.ps1"),
"-InstallRoot",
str(install),
"-NoUi",
]
else:
if not modern:
bin_dir = install / "venv" / "bin"
bin_dir.mkdir(parents=True)
(bin_dir / "python3").symlink_to(sys.executable)
hermes = bin_dir / "hermes"
hermes.write_text(
f'#!/usr/bin/env bash\nexec {shlex.quote(sys.executable)} -m hermes_cli.main "$@"\n',
encoding="utf-8",
)
hermes.chmod(0o755)
command = [
"bash",
str(SCRIPTS / "posix.sh"),
"--install-root",
str(install),
"--daemonized",
"--no-ui",
]
result = subprocess.run(
[*command, *target],
env=env,
cwd=tmp_path,
capture_output=True,
text=True,
encoding="utf-8",
timeout=90,
)
calls = (
[json.loads(line) for line in capture.read_text(encoding="utf-8").splitlines()]
if capture.exists()
else []
)
return result, calls, home, install
def _assert_forwarded(
tmp_path, target, expected, *, windows=False, inherited_home=True
):
result, calls, home, install = _run_handoff(
tmp_path,
target,
windows=windows,
inherited_home=inherited_home,
)
assert result.returncode == 0, result.stdout + result.stderr
assert len(calls) == 2, calls
expected_args = ["update", "--yes", "--gateway"]
if windows:
expected_args += ["--force"]
expected_args += expected
assert (
calls
== [
{
"argv": expected_args,
"home": str(home),
"cwd": str(install),
"install_root": str(install),
}
]
* 2
)
receipt = json.loads(
(home / ".hermes-update-result.json").read_text(encoding="utf-8-sig")
)
assert receipt["ok"]
assert receipt["channel"] == (expected[1] if expected[0] == "--channel" else "")
assert not (home / ".hermes-update-in-progress").exists()
@pytest.mark.platforms("posix")
@pytest.mark.parametrize("channel", ["stable", "canary", "main"])
def test_posix_channel_survives_retry_in_active_profile(tmp_path, channel):
_assert_forwarded(tmp_path, ["--channel", channel], ["--channel", channel])
@pytest.mark.platforms("windows")
@pytest.mark.parametrize("channel", ["stable", "canary", "main"])
def test_windows_channel_survives_retry_in_active_profile(tmp_path, channel):
_assert_forwarded(
tmp_path, ["-Channel", channel], ["--channel", channel], windows=True
)
@pytest.mark.platforms("posix")
@pytest.mark.parametrize(
"target, expected",
[
([], ["--branch", "main"]),
(["--branch", "feature/target"], ["--branch", "feature/target"]),
],
)
def test_posix_legacy_branch_and_default_home(tmp_path, target, expected):
_assert_forwarded(tmp_path, target, expected, inherited_home=False)
@pytest.mark.platforms("windows")
@pytest.mark.parametrize(
"target, expected",
[
([], ["--branch", "main"]),
(["-Branch", "feature/target"], ["--branch", "feature/target"]),
],
)
def test_windows_legacy_branch_and_default_home(tmp_path, target, expected):
_assert_forwarded(tmp_path, target, expected, windows=True, inherited_home=False)
def _assert_rejected(tmp_path, target, *, windows=False):
result, calls, home, _ = _run_handoff(tmp_path, target, windows=windows)
assert result.returncode != 0, result.stdout + result.stderr
assert not calls
assert not (home / ".hermes-update-in-progress").exists()
assert not (home / ".hermes-update-result.json").exists()
@pytest.mark.platforms("posix")
@pytest.mark.parametrize(
"target",
[
["--channel", "nightly"],
["--channel", ""],
["--channel"],
["--branch", "main", "--channel", "stable"],
["--channel", "canary", "--branch", "main"],
],
)
def test_posix_rejects_invalid_or_conflicting_target_before_update(tmp_path, target):
_assert_rejected(tmp_path, target)
@pytest.mark.platforms("windows")
@pytest.mark.parametrize(
"target",
[
["-Channel", "nightly"],
["-Channel", ""],
["-Channel"],
["-Branch", "main", "-Channel", "stable"],
["-Channel", "canary", "-Branch", "main"],
],
)
def test_windows_rejects_invalid_or_conflicting_target_before_update(tmp_path, target):
_assert_rejected(tmp_path, target, windows=True)
@pytest.mark.platforms("posix")
@pytest.mark.parametrize("code", [0, 1, 2])
def test_pm_handoff_uses_published_launcher_and_does_not_retry(tmp_path, code):
result, calls, home, install = _run_handoff(tmp_path, ["--channel", "canary"], modern=True, code=code)
assert result.returncode == code, result.stdout + result.stderr
assert calls == [{"argv": ["update", "--yes", "--gateway", "--channel", "canary"],
"home": str(home), "cwd": str(install), "install_root": str(install)}]
receipt = json.loads((home / ".hermes-update-result.json").read_text())
assert receipt["ok"] == (code == 0)
assert not (install / "venv").exists()
@pytest.mark.platforms("posix")
@pytest.mark.parametrize("foreign", [False, True])
def test_earlier_pm_userbin_publication_requires_exact_source_identity(tmp_path, foreign):
result, calls, home, install = _run_handoff(tmp_path, [], modern=True, userbin_only=True, foreign=foreign)
assert result.returncode == (3 if foreign else 0), result.stdout + result.stderr
assert len(calls) == (0 if foreign else 1)
assert not (install / '.hermes/bin/hermes').exists()
assert not (install / 'venv').exists()