Files
hermes-agent/tests/scripts/test_desktop_update_target.py
ethernet 427d4936c2 test: retarget merge-fallout tests at pm-clean's seams; drop tests of retired code
The merges from main kept or added about 45 test files written against the
legacy updater and installer seams that pm-clean replaced. Each failing test
was checked against the current code:

- Deleted, because every test targets code that pm-clean removed or that is
  only reachable from dead or frozen old-updater code:
  update_orphan/handoff_backend_reap, handoff_desktop_rebuild,
  interrupted_recovery (it also launched a real completion against the live
  checkout), update_stale_virtualenv, update_venv_health,
  verify_core_dependencies, lazy_refresh_venv_repair,
  certifi_repair (already dropped in 2f20ceb6f7; the merge resurrected it),
  banner_git_state (covered by test_source_check).
- Retargeted where production reads now:
  - version identity via version_info.get_code_identity
  - update receipts via a ContextVar scope
  - pm.extras / pm.installed_package / pm.ensure_import for matrix,
    computer_use, fal and tirith
  - install hints via pm.install_hint
  - the lock pins in pm/lock.json
  - probe_root for the critical-module probe
- Fixture repairs:
  - git-committed trees for source stamps
  - activate's real `--runtime-only` argument
  - a semver install stamp for requires_hermes gates
  - the Windows gateway restart after a verified update
  - snowflake-length ids that cannot collide with the runner uid
- Collection fix: one platforms() marker per test in gateway_proc_fallback.
2026-09-23 16:55:09 -04:00

271 lines
9.2 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
expected_args = ["update", "--yes", "--gateway"]
if windows:
expected_args += ["--force"]
expected_args += expected
expected_argvs = [expected_args] * 2
if windows:
# Desktop stopped the local gateways before handing off; a verified
# update restores the whole fleet in the same home and install.
expected_argvs.append(["gateway", "start", "--all"])
assert calls == [
{
"argv": argv,
"home": str(home),
"cwd": str(install),
"install_root": str(install),
}
for argv in expected_argvs
], calls
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()