mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-12 13:52:15 +00:00
tests: pin ink engine in _make_tui_argv npm-bootstrap tests (post-merge semantic fix)
Main's rewritten test_tui_npm_install.py tests call _make_tui_argv expecting the Ink/npm flow unconditionally; with the dual-engine dispatch merged in, _resolve_tui_engine() auto-selects opentui whenever ui-opentui/dist is built in the repo, routing the call away from the path under test (first subprocess became 'node --version' instead of 'npm run build'). Pin the engine to ink via an autouse fixture, mirroring the existing pinning precedent in test_tui_resume_flow.py.
This commit is contained in:
parent
ab37440ce6
commit
e1067dbbe5
756 changed files with 79874 additions and 19585 deletions
|
|
@ -70,7 +70,7 @@ def test_gui_installs_packages_and_launches_desktop_app(tmp_path, monkeypatch):
|
|||
cli_main.cmd_gui(_ns())
|
||||
|
||||
assert exc.value.code == 0
|
||||
mock_install.assert_called_once_with("/usr/bin/npm", root, capture_output=False)
|
||||
mock_install.assert_called_once_with("/usr/bin/npm", root, capture_output=False, env=None)
|
||||
assert mock_run.call_args_list[0].args[0] == ["/usr/bin/npm", "run", "pack"]
|
||||
assert mock_run.call_args_list[0].kwargs["cwd"] == desktop_dir
|
||||
assert mock_run.call_args_list[1].args[0] == [str(packaged_exe)]
|
||||
|
|
@ -498,11 +498,42 @@ def test_gui_retries_pack_once_after_purging_build_cache(tmp_path, monkeypatch):
|
|||
assert mock_run.call_args_list[2].args[0] == [str(packaged_exe)]
|
||||
|
||||
|
||||
def test_gui_does_not_retry_when_purge_finds_nothing(tmp_path, monkeypatch, capsys):
|
||||
"""If the purge clears nothing, there's no point retrying — fail fast."""
|
||||
def test_gui_falls_back_to_mirror_when_purge_finds_nothing(tmp_path, monkeypatch, capsys):
|
||||
"""Purge clears nothing (not a cache problem) → fall back to an Electron
|
||||
mirror once before failing, so a GitHub-blocked download self-heals."""
|
||||
root = _make_desktop_tree(tmp_path)
|
||||
monkeypatch.setattr(cli_main, "PROJECT_ROOT", root)
|
||||
_make_packaged_executable(root, monkeypatch, platform="linux")
|
||||
monkeypatch.delenv("ELECTRON_MIRROR", raising=False)
|
||||
|
||||
install_ok = subprocess.CompletedProcess(["npm", "ci"], 0)
|
||||
pack_fail = subprocess.CompletedProcess(["npm", "run", "pack"], 1)
|
||||
|
||||
with patch("hermes_cli.main.shutil.which", return_value="/usr/bin/npm"), \
|
||||
patch("hermes_cli.main._run_npm_install_deterministic", return_value=install_ok), \
|
||||
patch("hermes_cli.main._desktop_macos_relaunchable_fixup"), \
|
||||
patch("hermes_cli.main._purge_electron_build_cache", return_value=[]) as mock_purge, \
|
||||
patch("hermes_cli.main.subprocess.run", side_effect=[pack_fail, pack_fail]) as mock_run, \
|
||||
pytest.raises(SystemExit) as exc:
|
||||
cli_main.cmd_gui(_ns())
|
||||
|
||||
assert exc.value.code == 1
|
||||
mock_purge.assert_called_once()
|
||||
# pack(fail) → purge(nothing) → pack via mirror(fail) = 2 subprocess.run calls
|
||||
assert mock_run.call_count == 2
|
||||
# The retry runs the same build but with ELECTRON_MIRROR injected.
|
||||
assert "ELECTRON_MIRROR" not in (mock_run.call_args_list[0].kwargs.get("env") or {})
|
||||
assert mock_run.call_args_list[1].kwargs["env"]["ELECTRON_MIRROR"]
|
||||
assert "Desktop GUI build failed" in capsys.readouterr().out
|
||||
|
||||
|
||||
def test_gui_does_not_override_user_electron_mirror(tmp_path, monkeypatch, capsys):
|
||||
"""A user-pinned ELECTRON_MIRROR is respected: no extra mirror fallback
|
||||
attempt (and we never swap in our default mirror)."""
|
||||
root = _make_desktop_tree(tmp_path)
|
||||
monkeypatch.setattr(cli_main, "PROJECT_ROOT", root)
|
||||
_make_packaged_executable(root, monkeypatch, platform="linux")
|
||||
monkeypatch.setenv("ELECTRON_MIRROR", "https://mirror.example/electron/")
|
||||
|
||||
install_ok = subprocess.CompletedProcess(["npm", "ci"], 0)
|
||||
pack_fail = subprocess.CompletedProcess(["npm", "run", "pack"], 1)
|
||||
|
|
@ -518,4 +549,80 @@ def test_gui_does_not_retry_when_purge_finds_nothing(tmp_path, monkeypatch, caps
|
|||
assert exc.value.code == 1
|
||||
mock_purge.assert_called_once()
|
||||
assert mock_run.call_count == 1
|
||||
assert mock_run.call_args_list[0].kwargs["env"]["ELECTRON_MIRROR"] == "https://mirror.example/electron/"
|
||||
assert "Desktop GUI build failed" in capsys.readouterr().out
|
||||
|
||||
|
||||
class _FakeProc:
|
||||
"""Minimal psutil.Process stand-in for the lock-breaker tests."""
|
||||
|
||||
def __init__(self, pid: int, exe: str | None):
|
||||
self.pid = pid
|
||||
self.info = {"pid": pid, "exe": exe}
|
||||
self.terminated = False
|
||||
self.killed = False
|
||||
|
||||
def terminate(self):
|
||||
self.terminated = True
|
||||
|
||||
def kill(self):
|
||||
self.killed = True
|
||||
|
||||
|
||||
def test_stop_desktop_build_lock_noop_off_windows(tmp_path, monkeypatch):
|
||||
"""POSIX can unlink a running binary, so the helper is a no-op there."""
|
||||
desktop_dir = tmp_path / "apps" / "desktop"
|
||||
exe = desktop_dir / "release" / "linux-unpacked" / "hermes"
|
||||
exe.parent.mkdir(parents=True)
|
||||
exe.write_text("", encoding="utf-8")
|
||||
monkeypatch.setattr(cli_main.sys, "platform", "linux")
|
||||
|
||||
proc = _FakeProc(4321, str(exe))
|
||||
with patch("psutil.process_iter", return_value=[proc]) as it:
|
||||
assert cli_main._stop_desktop_processes_locking_build(desktop_dir) == []
|
||||
it.assert_not_called()
|
||||
assert proc.terminated is False
|
||||
|
||||
|
||||
def test_stop_desktop_build_lock_terminates_only_release_procs(tmp_path, monkeypatch):
|
||||
desktop_dir = tmp_path / "apps" / "desktop"
|
||||
release = desktop_dir / "release" / "win-unpacked"
|
||||
release.mkdir(parents=True)
|
||||
locker_exe = release / "Hermes.exe"
|
||||
locker_exe.write_text("", encoding="utf-8")
|
||||
other_exe = tmp_path / "elsewhere" / "Hermes.exe"
|
||||
other_exe.parent.mkdir(parents=True)
|
||||
other_exe.write_text("", encoding="utf-8")
|
||||
|
||||
monkeypatch.setattr(cli_main.sys, "platform", "win32")
|
||||
monkeypatch.setattr(cli_main.os, "getpid", lambda: 999)
|
||||
|
||||
locker = _FakeProc(101, str(locker_exe))
|
||||
unrelated = _FakeProc(102, str(other_exe))
|
||||
selfish = _FakeProc(999, str(locker_exe)) # our own PID — never killed
|
||||
no_exe = _FakeProc(103, None)
|
||||
|
||||
captured = {}
|
||||
|
||||
def _wait(procs, timeout=None):
|
||||
captured["waited"] = list(procs)
|
||||
return procs, []
|
||||
|
||||
with patch("psutil.process_iter", return_value=[locker, unrelated, selfish, no_exe]), \
|
||||
patch("psutil.wait_procs", side_effect=_wait):
|
||||
stopped = cli_main._stop_desktop_processes_locking_build(desktop_dir)
|
||||
|
||||
assert stopped == [101]
|
||||
assert locker.terminated is True
|
||||
assert unrelated.terminated is False
|
||||
assert selfish.terminated is False
|
||||
assert captured["waited"] == [locker]
|
||||
|
||||
|
||||
def test_stop_desktop_build_lock_no_release_dir(tmp_path, monkeypatch):
|
||||
desktop_dir = tmp_path / "apps" / "desktop"
|
||||
desktop_dir.mkdir(parents=True)
|
||||
monkeypatch.setattr(cli_main.sys, "platform", "win32")
|
||||
with patch("psutil.process_iter") as it:
|
||||
assert cli_main._stop_desktop_processes_locking_build(desktop_dir) == []
|
||||
it.assert_not_called()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue