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.
271 lines
9.2 KiB
Python
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()
|