mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-29 06:31:32 +00:00
Remove unused imports (F401) and duplicate/shadowed import redefinitions (F811) across the codebase using ruff's safe autofixes. No behavioral changes -- imports only. - ~1400 safe autofixes applied across 644 files (net -1072 lines) - __init__.py re-exports preserved (excluded from F401 removal so public re-export surfaces stay intact) - Re-exports that are imported or monkeypatched by tests but look unused in their defining module are kept with explicit # noqa: F401 (gateway/run.py load_dotenv; run_agent re-exports from agent.message_sanitization, agent.context_compressor, agent.retry_utils, agent.prompt_builder, agent.process_bootstrap, agent.codex_responses_adapter) - Unsafe F841 (unused-variable) fixes deliberately skipped -- those can change behavior when the RHS has side effects - ruff lints remain disabled in pyproject.toml (only PLW1514 is selected); this is a one-time cleanup, not a config change Verification: - python -m compileall: clean - pytest --collect-only: all 27161 tests collect (zero import errors) - core entry points import clean (run_agent, model_tools, cli, toolsets, hermes_state, batch_runner, gateway) - static scan: every name any test imports directly from an edited module still resolves
274 lines
10 KiB
Python
274 lines
10 KiB
Python
"""Tests for the Command Installation check in hermes doctor."""
|
|
|
|
import sys
|
|
import types
|
|
from argparse import Namespace
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
import hermes_cli.doctor as doctor_mod
|
|
|
|
|
|
def _setup_doctor_env(monkeypatch, tmp_path, venv_name="venv"):
|
|
"""Create a minimal HERMES_HOME + PROJECT_ROOT for doctor tests."""
|
|
home = tmp_path / ".hermes"
|
|
home.mkdir(parents=True, exist_ok=True)
|
|
(home / "config.yaml").write_text("memory: {}\n", encoding="utf-8")
|
|
|
|
project = tmp_path / "project"
|
|
project.mkdir(exist_ok=True)
|
|
|
|
# Create a fake venv entry point
|
|
venv_bin_dir = project / venv_name / "bin"
|
|
venv_bin_dir.mkdir(parents=True, exist_ok=True)
|
|
hermes_bin = venv_bin_dir / "hermes"
|
|
hermes_bin.write_text("#!/usr/bin/env python\n# entry point\n")
|
|
hermes_bin.chmod(0o755)
|
|
|
|
monkeypatch.setattr(doctor_mod, "HERMES_HOME", home)
|
|
monkeypatch.setattr(doctor_mod, "PROJECT_ROOT", project)
|
|
monkeypatch.setattr(doctor_mod, "_DHH", str(home))
|
|
|
|
# Stub model_tools so doctor doesn't fail on import
|
|
fake_model_tools = types.SimpleNamespace(
|
|
check_tool_availability=lambda *a, **kw: ([], []),
|
|
TOOLSET_REQUIREMENTS={},
|
|
)
|
|
monkeypatch.setitem(sys.modules, "model_tools", fake_model_tools)
|
|
|
|
# Stub auth checks
|
|
try:
|
|
from hermes_cli import auth as _auth_mod
|
|
monkeypatch.setattr(_auth_mod, "get_nous_auth_status", lambda: {})
|
|
monkeypatch.setattr(_auth_mod, "get_codex_auth_status", lambda: {})
|
|
except Exception:
|
|
pass
|
|
|
|
# Stub httpx.get to avoid network calls
|
|
try:
|
|
import httpx
|
|
monkeypatch.setattr(httpx, "get", lambda *a, **kw: types.SimpleNamespace(status_code=200))
|
|
except Exception:
|
|
pass
|
|
|
|
return home, project, hermes_bin
|
|
|
|
|
|
def _run_doctor(fix=False):
|
|
"""Run doctor and capture stdout."""
|
|
import io
|
|
import contextlib
|
|
|
|
buf = io.StringIO()
|
|
with contextlib.redirect_stdout(buf):
|
|
doctor_mod.run_doctor(Namespace(fix=fix))
|
|
return buf.getvalue()
|
|
|
|
|
|
class TestDoctorCommandInstallation:
|
|
"""Tests for the ◆ Command Installation section."""
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32", reason="Symlink check is Unix-only")
|
|
def test_correct_symlink_shows_ok(self, monkeypatch, tmp_path):
|
|
home, project, hermes_bin = _setup_doctor_env(monkeypatch, tmp_path)
|
|
|
|
# Create the command link dir with correct symlink
|
|
cmd_link_dir = tmp_path / ".local" / "bin"
|
|
cmd_link_dir.mkdir(parents=True)
|
|
cmd_link = cmd_link_dir / "hermes"
|
|
cmd_link.symlink_to(hermes_bin)
|
|
|
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
|
|
out = _run_doctor(fix=False)
|
|
assert "Command Installation" in out
|
|
assert "Venv entry point exists" in out
|
|
assert "correct target" in out
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32", reason="Symlink check is Unix-only")
|
|
def test_missing_symlink_shows_fail(self, monkeypatch, tmp_path):
|
|
home, project, hermes_bin = _setup_doctor_env(monkeypatch, tmp_path)
|
|
|
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
# Don't create the symlink — it should be missing
|
|
|
|
out = _run_doctor(fix=False)
|
|
assert "Command Installation" in out
|
|
assert "Venv entry point exists" in out
|
|
assert "not found" in out
|
|
assert "hermes doctor --fix" in out
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32", reason="Symlink check is Unix-only")
|
|
def test_fix_creates_missing_symlink(self, monkeypatch, tmp_path):
|
|
home, project, hermes_bin = _setup_doctor_env(monkeypatch, tmp_path)
|
|
|
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
|
|
out = _run_doctor(fix=True)
|
|
assert "Command Installation" in out
|
|
assert "Created symlink" in out
|
|
|
|
# Verify the symlink was actually created
|
|
cmd_link = tmp_path / ".local" / "bin" / "hermes"
|
|
assert cmd_link.is_symlink()
|
|
assert cmd_link.resolve() == hermes_bin.resolve()
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32", reason="Symlink check is Unix-only")
|
|
def test_wrong_target_symlink_shows_warn(self, monkeypatch, tmp_path):
|
|
home, project, hermes_bin = _setup_doctor_env(monkeypatch, tmp_path)
|
|
|
|
# Create a symlink pointing to the wrong target
|
|
cmd_link_dir = tmp_path / ".local" / "bin"
|
|
cmd_link_dir.mkdir(parents=True)
|
|
cmd_link = cmd_link_dir / "hermes"
|
|
wrong_target = tmp_path / "wrong_hermes"
|
|
wrong_target.write_text("#!/usr/bin/env python\n")
|
|
cmd_link.symlink_to(wrong_target)
|
|
|
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
|
|
out = _run_doctor(fix=False)
|
|
assert "Command Installation" in out
|
|
assert "wrong target" in out
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32", reason="Symlink check is Unix-only")
|
|
def test_fix_repairs_wrong_symlink(self, monkeypatch, tmp_path):
|
|
home, project, hermes_bin = _setup_doctor_env(monkeypatch, tmp_path)
|
|
|
|
# Create a symlink pointing to wrong target
|
|
cmd_link_dir = tmp_path / ".local" / "bin"
|
|
cmd_link_dir.mkdir(parents=True)
|
|
cmd_link = cmd_link_dir / "hermes"
|
|
wrong_target = tmp_path / "wrong_hermes"
|
|
wrong_target.write_text("#!/usr/bin/env python\n")
|
|
cmd_link.symlink_to(wrong_target)
|
|
|
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
|
|
out = _run_doctor(fix=True)
|
|
assert "Fixed symlink" in out
|
|
|
|
# Verify the symlink now points to the correct target
|
|
assert cmd_link.is_symlink()
|
|
assert cmd_link.resolve() == hermes_bin.resolve()
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32", reason="Symlink check is Unix-only")
|
|
def test_missing_venv_entry_point_shows_warn(self, monkeypatch, tmp_path):
|
|
home = tmp_path / ".hermes"
|
|
home.mkdir(parents=True, exist_ok=True)
|
|
(home / "config.yaml").write_text("memory: {}\n", encoding="utf-8")
|
|
|
|
project = tmp_path / "project"
|
|
project.mkdir(exist_ok=True)
|
|
# Do NOT create any venv entry point
|
|
|
|
monkeypatch.setattr(doctor_mod, "HERMES_HOME", home)
|
|
monkeypatch.setattr(doctor_mod, "PROJECT_ROOT", project)
|
|
monkeypatch.setattr(doctor_mod, "_DHH", str(home))
|
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
|
|
fake_model_tools = types.SimpleNamespace(
|
|
check_tool_availability=lambda *a, **kw: ([], []),
|
|
TOOLSET_REQUIREMENTS={},
|
|
)
|
|
monkeypatch.setitem(sys.modules, "model_tools", fake_model_tools)
|
|
try:
|
|
from hermes_cli import auth as _auth_mod
|
|
monkeypatch.setattr(_auth_mod, "get_nous_auth_status", lambda: {})
|
|
monkeypatch.setattr(_auth_mod, "get_codex_auth_status", lambda: {})
|
|
except Exception:
|
|
pass
|
|
try:
|
|
import httpx
|
|
monkeypatch.setattr(httpx, "get", lambda *a, **kw: types.SimpleNamespace(status_code=200))
|
|
except Exception:
|
|
pass
|
|
|
|
out = _run_doctor(fix=False)
|
|
assert "Command Installation" in out
|
|
assert "Venv entry point not found" in out
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32", reason="Symlink check is Unix-only")
|
|
def test_dot_venv_dir_is_found(self, monkeypatch, tmp_path):
|
|
"""The check finds entry points in .venv/ as well as venv/."""
|
|
home, project, _ = _setup_doctor_env(monkeypatch, tmp_path, venv_name=".venv")
|
|
|
|
# Create the command link with correct symlink
|
|
hermes_bin = project / ".venv" / "bin" / "hermes"
|
|
cmd_link_dir = tmp_path / ".local" / "bin"
|
|
cmd_link_dir.mkdir(parents=True)
|
|
cmd_link = cmd_link_dir / "hermes"
|
|
cmd_link.symlink_to(hermes_bin)
|
|
|
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
|
|
out = _run_doctor(fix=False)
|
|
assert "Venv entry point exists" in out
|
|
assert ".venv/bin/hermes" in out
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32", reason="Symlink check is Unix-only")
|
|
def test_non_symlink_regular_file_shows_ok(self, monkeypatch, tmp_path):
|
|
"""If ~/.local/bin/hermes is a regular file (not symlink), accept it."""
|
|
home, project, hermes_bin = _setup_doctor_env(monkeypatch, tmp_path)
|
|
|
|
cmd_link_dir = tmp_path / ".local" / "bin"
|
|
cmd_link_dir.mkdir(parents=True)
|
|
cmd_link = cmd_link_dir / "hermes"
|
|
cmd_link.write_text("#!/bin/sh\nexec python -m hermes_cli.main \"$@\"\n")
|
|
|
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
|
|
out = _run_doctor(fix=False)
|
|
assert "non-symlink" in out
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32", reason="Symlink check is Unix-only")
|
|
def test_termux_uses_prefix_bin(self, monkeypatch, tmp_path):
|
|
"""On Termux, the command link dir is $PREFIX/bin."""
|
|
prefix_dir = tmp_path / "termux_prefix"
|
|
prefix_bin = prefix_dir / "bin"
|
|
prefix_bin.mkdir(parents=True)
|
|
|
|
home, project, hermes_bin = _setup_doctor_env(monkeypatch, tmp_path)
|
|
|
|
monkeypatch.setenv("TERMUX_VERSION", "0.118.3")
|
|
monkeypatch.setenv("PREFIX", str(prefix_dir))
|
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
|
|
out = _run_doctor(fix=False)
|
|
assert "Command Installation" in out
|
|
assert "$PREFIX/bin" in out
|
|
|
|
def test_windows_skips_check(self, monkeypatch, tmp_path):
|
|
"""On Windows, the Command Installation section is skipped."""
|
|
home = tmp_path / ".hermes"
|
|
home.mkdir(parents=True, exist_ok=True)
|
|
(home / "config.yaml").write_text("memory: {}\n", encoding="utf-8")
|
|
|
|
project = tmp_path / "project"
|
|
project.mkdir(exist_ok=True)
|
|
|
|
monkeypatch.setattr(doctor_mod, "HERMES_HOME", home)
|
|
monkeypatch.setattr(doctor_mod, "PROJECT_ROOT", project)
|
|
monkeypatch.setattr(doctor_mod, "_DHH", str(home))
|
|
monkeypatch.setattr(sys, "platform", "win32")
|
|
|
|
fake_model_tools = types.SimpleNamespace(
|
|
check_tool_availability=lambda *a, **kw: ([], []),
|
|
TOOLSET_REQUIREMENTS={},
|
|
)
|
|
monkeypatch.setitem(sys.modules, "model_tools", fake_model_tools)
|
|
try:
|
|
from hermes_cli import auth as _auth_mod
|
|
monkeypatch.setattr(_auth_mod, "get_nous_auth_status", lambda: {})
|
|
monkeypatch.setattr(_auth_mod, "get_codex_auth_status", lambda: {})
|
|
except Exception:
|
|
pass
|
|
try:
|
|
import httpx
|
|
monkeypatch.setattr(httpx, "get", lambda *a, **kw: types.SimpleNamespace(status_code=200))
|
|
except Exception:
|
|
pass
|
|
|
|
out = _run_doctor(fix=False)
|
|
assert "Command Installation" not in out
|