mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
Centralize container detection in hermes_constants.is_container() with process-lifetime caching, matching existing is_wsl()/is_termux() patterns. Dedup _is_inside_container() in config.py to delegate to the new function. Add _run_systemctl() wrapper that converts FileNotFoundError to RuntimeError for defense-in-depth — all 10 bare subprocess.run(_systemctl_cmd(...)) call sites now route through it. Make supports_systemd_services() return False in containers and when systemctl binary is absent (shutil.which check). Add Docker-specific guidance in gateway_command() for install/uninstall/start subcommands — exit 0 with helpful instructions instead of crashing. Make 'hermes status' show 'Manager: docker (foreground)' and 'hermes dump' show 'running (docker, pid N)' inside containers. Fix setup_gateway() to use supports_systemd instead of _is_linux for all systemd-related branches, and show Docker restart policy instructions in containers. Replace inline /.dockerenv check in voice_mode.py with is_container(). Fixes #7420 Co-authored-by: teknium1 <teknium1@users.noreply.github.com>
113 lines
5 KiB
Python
113 lines
5 KiB
Python
"""Tests for hermes_constants module."""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
import hermes_constants
|
|
from hermes_constants import get_default_hermes_root, is_container
|
|
|
|
|
|
class TestGetDefaultHermesRoot:
|
|
"""Tests for get_default_hermes_root() — Docker/custom deployment awareness."""
|
|
|
|
def test_no_hermes_home_returns_native(self, tmp_path, monkeypatch):
|
|
"""When HERMES_HOME is not set, returns ~/.hermes."""
|
|
monkeypatch.delenv("HERMES_HOME", raising=False)
|
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
assert get_default_hermes_root() == tmp_path / ".hermes"
|
|
|
|
def test_hermes_home_is_native(self, tmp_path, monkeypatch):
|
|
"""When HERMES_HOME = ~/.hermes, returns ~/.hermes."""
|
|
native = tmp_path / ".hermes"
|
|
native.mkdir()
|
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
monkeypatch.setenv("HERMES_HOME", str(native))
|
|
assert get_default_hermes_root() == native
|
|
|
|
def test_hermes_home_is_profile(self, tmp_path, monkeypatch):
|
|
"""When HERMES_HOME is a profile under ~/.hermes, returns ~/.hermes."""
|
|
native = tmp_path / ".hermes"
|
|
profile = native / "profiles" / "coder"
|
|
profile.mkdir(parents=True)
|
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
monkeypatch.setenv("HERMES_HOME", str(profile))
|
|
assert get_default_hermes_root() == native
|
|
|
|
def test_hermes_home_is_docker(self, tmp_path, monkeypatch):
|
|
"""When HERMES_HOME points outside ~/.hermes (Docker), returns HERMES_HOME."""
|
|
docker_home = tmp_path / "opt" / "data"
|
|
docker_home.mkdir(parents=True)
|
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
monkeypatch.setenv("HERMES_HOME", str(docker_home))
|
|
assert get_default_hermes_root() == docker_home
|
|
|
|
def test_hermes_home_is_custom_path(self, tmp_path, monkeypatch):
|
|
"""Any HERMES_HOME outside ~/.hermes is treated as the root."""
|
|
custom = tmp_path / "my-hermes-data"
|
|
custom.mkdir()
|
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
monkeypatch.setenv("HERMES_HOME", str(custom))
|
|
assert get_default_hermes_root() == custom
|
|
|
|
def test_docker_profile_active(self, tmp_path, monkeypatch):
|
|
"""When a Docker profile is active (HERMES_HOME=<root>/profiles/<name>),
|
|
returns the Docker root, not the profile dir."""
|
|
docker_root = tmp_path / "opt" / "data"
|
|
profile = docker_root / "profiles" / "coder"
|
|
profile.mkdir(parents=True)
|
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
monkeypatch.setenv("HERMES_HOME", str(profile))
|
|
assert get_default_hermes_root() == docker_root
|
|
|
|
|
|
class TestIsContainer:
|
|
"""Tests for is_container() — Docker/Podman detection."""
|
|
|
|
def _reset_cache(self, monkeypatch):
|
|
"""Reset the cached detection result before each test."""
|
|
monkeypatch.setattr(hermes_constants, "_container_detected", None)
|
|
|
|
def test_detects_dockerenv(self, monkeypatch, tmp_path):
|
|
"""/.dockerenv triggers container detection."""
|
|
self._reset_cache(monkeypatch)
|
|
monkeypatch.setattr(os.path, "exists", lambda p: p == "/.dockerenv")
|
|
assert is_container() is True
|
|
|
|
def test_detects_containerenv(self, monkeypatch, tmp_path):
|
|
"""/run/.containerenv triggers container detection (Podman)."""
|
|
self._reset_cache(monkeypatch)
|
|
monkeypatch.setattr(os.path, "exists", lambda p: p == "/run/.containerenv")
|
|
assert is_container() is True
|
|
|
|
def test_detects_cgroup_docker(self, monkeypatch, tmp_path):
|
|
"""/proc/1/cgroup containing 'docker' triggers detection."""
|
|
import builtins
|
|
self._reset_cache(monkeypatch)
|
|
monkeypatch.setattr(os.path, "exists", lambda p: False)
|
|
cgroup_file = tmp_path / "cgroup"
|
|
cgroup_file.write_text("12:memory:/docker/abc123\n")
|
|
_real_open = builtins.open
|
|
monkeypatch.setattr("builtins.open", lambda p, *a, **kw: _real_open(str(cgroup_file), *a, **kw) if p == "/proc/1/cgroup" else _real_open(p, *a, **kw))
|
|
assert is_container() is True
|
|
|
|
def test_negative_case(self, monkeypatch, tmp_path):
|
|
"""Returns False on a regular Linux host."""
|
|
import builtins
|
|
self._reset_cache(monkeypatch)
|
|
monkeypatch.setattr(os.path, "exists", lambda p: False)
|
|
cgroup_file = tmp_path / "cgroup"
|
|
cgroup_file.write_text("12:memory:/\n")
|
|
_real_open = builtins.open
|
|
monkeypatch.setattr("builtins.open", lambda p, *a, **kw: _real_open(str(cgroup_file), *a, **kw) if p == "/proc/1/cgroup" else _real_open(p, *a, **kw))
|
|
assert is_container() is False
|
|
|
|
def test_caches_result(self, monkeypatch):
|
|
"""Second call uses cached value without re-probing."""
|
|
monkeypatch.setattr(hermes_constants, "_container_detected", True)
|
|
assert is_container() is True
|
|
# Even if we make os.path.exists return False, cached value wins
|
|
monkeypatch.setattr(os.path, "exists", lambda p: False)
|
|
assert is_container() is True
|