mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-29 06:31:32 +00:00
Merge a844ff0ddc into 14b27bb68c
This commit is contained in:
commit
f4d3067e36
2 changed files with 163 additions and 0 deletions
|
|
@ -1437,6 +1437,17 @@ def get_python_path() -> str:
|
|||
venv_python = venv / "bin" / "python"
|
||||
if venv_python.exists():
|
||||
return str(venv_python)
|
||||
|
||||
# Fallback: use PROJECT_ROOT/venv/bin/python (same logic as generate_systemd_unit)
|
||||
# This is more reliable than sys.executable when running under uv.
|
||||
fallback_venv = PROJECT_ROOT / "venv"
|
||||
if is_windows():
|
||||
fallback_python = fallback_venv / "Scripts" / "python.exe"
|
||||
else:
|
||||
fallback_python = fallback_venv / "bin" / "python"
|
||||
if fallback_python.exists():
|
||||
return str(fallback_python)
|
||||
|
||||
return sys.executable
|
||||
|
||||
|
||||
|
|
|
|||
152
tests/hermes_cli/test_gateway_python_path.py
Normal file
152
tests/hermes_cli/test_gateway_python_path.py
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
"""Test for get_python_path() fallback to PROJECT_ROOT/venv/bin/python.
|
||||
|
||||
Issue: #9201 - systemd service ExecStart uses uv Python instead of venv Python
|
||||
when uv manages the Python environment.
|
||||
|
||||
Root cause: get_python_path() fell back to sys.executable when _detect_venv_dir()
|
||||
returned None, but this returns uv's Python path which has no packages installed.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
|
||||
class TestGetPythonPathFallback:
|
||||
"""Test that get_python_path() correctly falls back to PROJECT_ROOT/venv."""
|
||||
|
||||
def test_fallback_to_project_root_venv_when_detect_fails(self):
|
||||
"""When _detect_venv_dir() returns None, should use PROJECT_ROOT/venv."""
|
||||
from hermes_cli.gateway import get_python_path, PROJECT_ROOT
|
||||
|
||||
# Mock _detect_venv_dir to return None (simulate uv environment)
|
||||
with patch("hermes_cli.gateway._detect_venv_dir", return_value=None):
|
||||
# Mock the venv python to exist
|
||||
with patch.object(Path, "exists", return_value=True):
|
||||
result = get_python_path()
|
||||
|
||||
# Should be PROJECT_ROOT/venv/bin/python, not sys.executable
|
||||
expected = str(PROJECT_ROOT / "venv" / "bin" / "python")
|
||||
assert result == expected
|
||||
|
||||
def test_detect_venv_dir_returns_valid_venv(self):
|
||||
"""When _detect_venv_dir() returns a valid venv, should use it."""
|
||||
from hermes_cli.gateway import get_python_path
|
||||
|
||||
# Mock _detect_venv_dir to return a venv
|
||||
mock_venv = MagicMock(spec=Path)
|
||||
mock_venv.is_dir.return_value = True
|
||||
|
||||
with patch("hermes_cli.gateway._detect_venv_dir", return_value=mock_venv):
|
||||
# Mock the venv python to exist
|
||||
mock_python = MagicMock(spec=Path)
|
||||
mock_python.exists.return_value = True
|
||||
|
||||
with patch.object(Path, "__truediv__", return_value=mock_python):
|
||||
result = get_python_path()
|
||||
|
||||
# Should return the venv python path
|
||||
assert result is not None
|
||||
|
||||
def test_fallback_chain_order(self):
|
||||
"""Fallback order should be: detected_venv -> PROJECT_ROOT/venv -> sys.executable."""
|
||||
from hermes_cli.gateway import get_python_path, PROJECT_ROOT
|
||||
|
||||
# Test 1: detected_venv has priority
|
||||
mock_venv = MagicMock(spec=Path)
|
||||
mock_venv_python = MagicMock(spec=Path)
|
||||
mock_venv_python.exists.return_value = True
|
||||
|
||||
with patch("hermes_cli.gateway._detect_venv_dir", return_value=mock_venv):
|
||||
with patch.object(Path, "__truediv__", return_value=mock_venv_python):
|
||||
result = get_python_path()
|
||||
# Should use detected_venv, not PROJECT_ROOT fallback
|
||||
|
||||
# Test 2: PROJECT_ROOT/venv fallback when detect fails but venv exists
|
||||
with patch("hermes_cli.gateway._detect_venv_dir", return_value=None):
|
||||
with patch.object(Path, "exists", return_value=True):
|
||||
result = get_python_path()
|
||||
expected = str(PROJECT_ROOT / "venv" / "bin" / "python")
|
||||
assert result == expected
|
||||
|
||||
# Test 3: sys.executable when all fallbacks fail
|
||||
with patch("hermes_cli.gateway._detect_venv_dir", return_value=None):
|
||||
with patch.object(Path, "exists", return_value=False):
|
||||
result = get_python_path()
|
||||
assert result == sys.executable
|
||||
|
||||
def test_consistency_with_venv_dir_in_systemd_unit(self):
|
||||
"""python_path should be consistent with venv_dir in generate_systemd_unit."""
|
||||
from hermes_cli.gateway import (
|
||||
get_python_path,
|
||||
generate_systemd_unit,
|
||||
PROJECT_ROOT,
|
||||
_detect_venv_dir,
|
||||
)
|
||||
|
||||
# Simulate scenario where _detect_venv_dir returns None
|
||||
with patch("hermes_cli.gateway._detect_venv_dir", return_value=None):
|
||||
with patch.object(Path, "exists", return_value=True):
|
||||
python_path = get_python_path()
|
||||
unit = generate_systemd_unit(system=False)
|
||||
|
||||
# python_path should match venv/bin/python in ExecStart
|
||||
assert "venv/bin/python" in python_path
|
||||
assert python_path in unit
|
||||
|
||||
def test_windows_fallback_uses_scripts_python_exe(self):
|
||||
"""On Windows, fallback should use venv/Scripts/python.exe."""
|
||||
from hermes_cli.gateway import get_python_path, PROJECT_ROOT
|
||||
|
||||
with patch("hermes_cli.gateway._detect_venv_dir", return_value=None):
|
||||
with patch("hermes_cli.gateway.is_windows", return_value=True):
|
||||
with patch.object(Path, "exists", return_value=True):
|
||||
result = get_python_path()
|
||||
expected = str(PROJECT_ROOT / "venv" / "Scripts" / "python.exe")
|
||||
assert result == expected
|
||||
|
||||
def test_execstart_not_uv_python_path(self):
|
||||
"""ExecStart should NOT contain uv Python path patterns."""
|
||||
from hermes_cli.gateway import generate_systemd_unit
|
||||
|
||||
with patch("hermes_cli.gateway._detect_venv_dir", return_value=None):
|
||||
with patch.object(Path, "exists", return_value=True):
|
||||
unit = generate_systemd_unit(system=False)
|
||||
|
||||
# Should NOT have uv-specific paths
|
||||
assert ".local/share/uv/python" not in unit
|
||||
assert "cpython-" not in unit
|
||||
# Should have venv path
|
||||
assert "venv/bin/python" in unit
|
||||
|
||||
|
||||
class TestSystemdUnitGeneration:
|
||||
"""Test systemd unit file generation with correct Python path."""
|
||||
|
||||
def test_system_unit_execstart_uses_venv_python(self):
|
||||
"""System unit ExecStart should use venv/bin/python."""
|
||||
from hermes_cli.gateway import generate_systemd_unit, PROJECT_ROOT
|
||||
|
||||
with patch("hermes_cli.gateway._detect_venv_dir", return_value=None):
|
||||
with patch.object(Path, "exists", return_value=True):
|
||||
unit = generate_systemd_unit(system=True, run_as_user="root")
|
||||
|
||||
# ExecStart should contain venv/bin/python
|
||||
assert "venv/bin/python" in unit
|
||||
assert "ExecStart=" in unit
|
||||
|
||||
def test_user_unit_execstart_uses_venv_python(self):
|
||||
"""User unit ExecStart should use venv/bin/python."""
|
||||
from hermes_cli.gateway import generate_systemd_unit, PROJECT_ROOT
|
||||
|
||||
with patch("hermes_cli.gateway._detect_venv_dir", return_value=None):
|
||||
with patch.object(Path, "exists", return_value=True):
|
||||
unit = generate_systemd_unit(system=False)
|
||||
|
||||
# ExecStart should contain venv/bin/python
|
||||
assert "venv/bin/python" in unit
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
Loading…
Add table
Add a link
Reference in a new issue