Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
"""Uninstall must not leave a dangling ``hermes`` command on Windows.
|
||||
|
||||
Every uninstall mode deletes the code checkout, but the launchers install.ps1
|
||||
staged in the managed binary dir (the default Hermes root's ``bin``, shared
|
||||
with the managed uv) live outside it. A surviving launcher makes ``hermes``
|
||||
in a new terminal resolve and then error on its missing venv target — worse
|
||||
than command-not-found. The managed uv next to them must survive keep-data
|
||||
uninstalls, so the PATH sweep takes the ``bin`` entry only on a full wipe.
|
||||
|
||||
Platform verdicts are injected parameters (input→output, not host fakes).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from hermes_cli import uninstall
|
||||
from hermes_cli._install_repair import _WINDOWS_BIN_LAUNCHERS
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def managed_bin(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||
"""Default-root ``bin`` holding launchers of both forms plus managed uv."""
|
||||
home = tmp_path / "hermes"
|
||||
bin_dir = home / "bin"
|
||||
bin_dir.mkdir(parents=True)
|
||||
(bin_dir / "hermes.exe").write_bytes(b"MZ launcher")
|
||||
(bin_dir / "hermes-acp.cmd").write_text("@echo off\r\n", encoding="ascii")
|
||||
(bin_dir / "uv.exe").write_bytes(b"MZ managed uv")
|
||||
(bin_dir / "uvx.exe").write_bytes(b"MZ managed uvx")
|
||||
monkeypatch.setenv("HERMES_HOME", str(home))
|
||||
return bin_dir
|
||||
|
||||
|
||||
def test_removes_both_launcher_forms_and_keeps_managed_uv(managed_bin: Path):
|
||||
removed = uninstall.remove_windows_bin_launchers(windows=True)
|
||||
|
||||
assert sorted(p.name for p in removed) == ["hermes-acp.cmd", "hermes.exe"]
|
||||
assert not (managed_bin / "hermes.exe").exists()
|
||||
assert not (managed_bin / "hermes-acp.cmd").exists()
|
||||
# The managed uv stays — keep-data reinstalls still need it.
|
||||
assert (managed_bin / "uv.exe").exists()
|
||||
assert (managed_bin / "uvx.exe").exists()
|
||||
|
||||
|
||||
def test_anchors_on_default_root_not_profile_home(
|
||||
managed_bin: Path, monkeypatch: pytest.MonkeyPatch
|
||||
):
|
||||
"""The launcher dir is per-machine; a profile HERMES_HOME must not
|
||||
redirect the sweep into ``profiles/<name>/bin``."""
|
||||
home = managed_bin.parent
|
||||
monkeypatch.setenv("HERMES_HOME", str(home / "profiles" / "work"))
|
||||
|
||||
removed = uninstall.remove_windows_bin_launchers(windows=True)
|
||||
|
||||
assert sorted(p.name for p in removed) == ["hermes-acp.cmd", "hermes.exe"]
|
||||
assert not (managed_bin / "hermes.exe").exists()
|
||||
|
||||
|
||||
def test_noop_on_posix(managed_bin: Path):
|
||||
assert uninstall.remove_windows_bin_launchers(windows=False) == []
|
||||
assert (managed_bin / "hermes.exe").exists()
|
||||
|
||||
|
||||
def test_noop_when_no_launchers_staged(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
|
||||
home = tmp_path / "hermes"
|
||||
home.mkdir()
|
||||
monkeypatch.setenv("HERMES_HOME", str(home))
|
||||
|
||||
assert uninstall.remove_windows_bin_launchers(windows=True) == []
|
||||
|
||||
|
||||
def test_launcher_names_stay_in_lockstep_with_install_ps1():
|
||||
"""The sweep must cover exactly the names install.ps1 stages, and no
|
||||
generic name it could clobber. Reads the real installer list so the two
|
||||
sides cannot drift apart silently."""
|
||||
import re
|
||||
|
||||
install_ps1 = (
|
||||
Path(uninstall.__file__).resolve().parents[1] / "scripts" / "install.ps1"
|
||||
).read_text(encoding="ascii")
|
||||
match = re.search(r"foreach \(\$launcher in @\(([^)]*)\)\)", install_ps1)
|
||||
assert match, "launcher staging loop not found in install.ps1"
|
||||
staged = set(re.findall(r'"([^"]+)"', match.group(1)))
|
||||
|
||||
assert staged == set(_WINDOWS_BIN_LAUNCHERS)
|
||||
for name in _WINDOWS_BIN_LAUNCHERS:
|
||||
assert name.startswith("hermes") # never a generic name it could clobber
|
||||
|
||||
|
||||
class TestManagedBinPathMarker:
|
||||
"""The managed ``bin`` PATH entry goes only when the dir itself goes.
|
||||
|
||||
Markers match against Windows registry PATH entries, so the inputs here
|
||||
are Windows-shaped path strings regardless of the host — feeding
|
||||
``tmp_path`` would make the test pass only on Windows hosts.
|
||||
"""
|
||||
|
||||
HOME = r"C:\Users\me\AppData\Local\hermes"
|
||||
BIN_ENTRY = r"C:\Users\me\AppData\Local\hermes\bin"
|
||||
|
||||
def test_keep_data_markers_spare_the_managed_bin(self):
|
||||
markers = [m.lower() for m in uninstall._hermes_path_markers(Path(self.HOME))]
|
||||
|
||||
assert not any(self.BIN_ENTRY.lower().startswith(m) for m in markers)
|
||||
|
||||
def test_full_wipe_markers_take_the_managed_bin(self):
|
||||
markers = [
|
||||
m.lower()
|
||||
for m in uninstall._hermes_path_markers(
|
||||
Path(self.HOME), include_managed_bin=True
|
||||
)
|
||||
]
|
||||
|
||||
assert any(self.BIN_ENTRY.lower().startswith(m) for m in markers)
|
||||
Reference in New Issue
Block a user