mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-29 06:31:32 +00:00
126 lines
4.2 KiB
Python
126 lines
4.2 KiB
Python
"""Regression tests for the Android psutil compatibility installer."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import io
|
|
import shutil
|
|
import tarfile
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from hermes_cli.psutil_android import (
|
|
MARKER,
|
|
REPLACEMENT,
|
|
PSUTIL_URL,
|
|
PsutilAndroidInstallError,
|
|
prepare_patched_psutil_sdist,
|
|
)
|
|
|
|
|
|
def _add_dir(tf: tarfile.TarFile, name: str) -> None:
|
|
info = tarfile.TarInfo(name)
|
|
info.type = tarfile.DIRTYPE
|
|
info.mode = 0o755
|
|
tf.addfile(info)
|
|
|
|
|
|
def _add_file(tf: tarfile.TarFile, name: str, content: str) -> None:
|
|
payload = content.encode("utf-8")
|
|
info = tarfile.TarInfo(name)
|
|
info.size = len(payload)
|
|
info.mode = 0o644
|
|
tf.addfile(info, io.BytesIO(payload))
|
|
|
|
|
|
def _build_psutil_archive(archive: Path, *, malicious_symlink: bool) -> None:
|
|
with tarfile.open(archive, "w:gz") as tf:
|
|
_add_dir(tf, "psutil-7.2.2")
|
|
if malicious_symlink:
|
|
link = tarfile.TarInfo("psutil-7.2.2/psutil")
|
|
link.type = tarfile.SYMTYPE
|
|
link.linkname = "../../outside"
|
|
tf.addfile(link)
|
|
else:
|
|
_add_dir(tf, "psutil-7.2.2/psutil")
|
|
_add_file(
|
|
tf,
|
|
"psutil-7.2.2/psutil/_common.py",
|
|
f"{MARKER}\n",
|
|
)
|
|
|
|
|
|
def test_prepare_patched_psutil_sdist_rejects_symlink_member(tmp_path):
|
|
"""A symlink member must be rejected before any file payload is written."""
|
|
archive = tmp_path / "evil.tar.gz"
|
|
_build_psutil_archive(archive, malicious_symlink=True)
|
|
|
|
destination = tmp_path / "extract"
|
|
with pytest.raises(PsutilAndroidInstallError, match="Unsupported archive member type"):
|
|
prepare_patched_psutil_sdist(archive, destination)
|
|
|
|
assert not (tmp_path / "outside" / "_common.py").exists()
|
|
|
|
|
|
def test_install_psutil_android_compat_uses_patched_tree(tmp_path):
|
|
"""Updater path should install from the patched temporary sdist tree."""
|
|
archive = tmp_path / "psutil.tar.gz"
|
|
_build_psutil_archive(archive, malicious_symlink=False)
|
|
|
|
from hermes_cli import main as hermes_main
|
|
|
|
captured: dict[str, object] = {}
|
|
|
|
def fake_urlretrieve(url: str, dest: Path):
|
|
assert url == PSUTIL_URL
|
|
shutil.copyfile(archive, dest)
|
|
return str(dest), None
|
|
|
|
def fake_run_install(cmd: list[str], *, env=None):
|
|
src_root = Path(cmd[-1])
|
|
captured["cmd"] = cmd
|
|
captured["env"] = env
|
|
captured["common_py"] = (src_root / "psutil" / "_common.py").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
|
|
with patch("urllib.request.urlretrieve", side_effect=fake_urlretrieve), \
|
|
patch.object(hermes_main, "_run_install_with_heartbeat", side_effect=fake_run_install):
|
|
hermes_main._install_psutil_android_compat(
|
|
["uv", "pip"],
|
|
env={"HERMES_TEST": "1"},
|
|
)
|
|
|
|
assert captured["cmd"][:4] == ["uv", "pip", "install", "--no-build-isolation"]
|
|
assert captured["env"] == {"HERMES_TEST": "1"}
|
|
assert REPLACEMENT in str(captured["common_py"])
|
|
|
|
|
|
def test_install_psutil_android_script_uses_patched_tree(tmp_path, monkeypatch, capsys):
|
|
"""Standalone installer script should reuse the same safe patched tree."""
|
|
archive = tmp_path / "psutil.tar.gz"
|
|
_build_psutil_archive(archive, malicious_symlink=False)
|
|
|
|
import scripts.install_psutil_android as installer
|
|
|
|
def fake_urlretrieve(url: str, dest: Path):
|
|
assert url == PSUTIL_URL
|
|
shutil.copyfile(archive, dest)
|
|
return str(dest), None
|
|
|
|
def fake_subprocess_run(cmd: list[str]):
|
|
src_root = Path(cmd[-1])
|
|
patched = (src_root / "psutil" / "_common.py").read_text(encoding="utf-8")
|
|
assert REPLACEMENT in patched
|
|
return type("RunResult", (), {"returncode": 0})()
|
|
|
|
monkeypatch.setattr(installer.sys, "argv", ["install_psutil_android.py"])
|
|
monkeypatch.setattr(installer, "_resolve_install_cmd", lambda *_args: ["python", "-m", "pip"])
|
|
|
|
with patch("urllib.request.urlretrieve", side_effect=fake_urlretrieve), \
|
|
patch.object(installer.subprocess, "run", side_effect=fake_subprocess_run):
|
|
assert installer.main() == 0
|
|
|
|
captured = capsys.readouterr()
|
|
assert "psutil installed via Android compatibility shim" in captured.out
|