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
144 lines
4.9 KiB
Python
144 lines
4.9 KiB
Python
"""Tests for service-singleton lifecycle: atexit handler, idempotent shutdown.
|
|
|
|
These cover the exit-cleanup behavior added to plug the language-server
|
|
process leak — without the atexit hook, ``hermes chat`` exits while
|
|
pyright/gopls/etc. are still alive on the host.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import atexit
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
|
|
from agent import lsp as lsp_module
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _reset_singleton():
|
|
"""Force a clean module state before each test.
|
|
|
|
Tests in this file share process-global state (the lazy
|
|
singleton + atexit registration flag); reset both before and
|
|
after every test so order doesn't matter.
|
|
"""
|
|
lsp_module._service = None
|
|
lsp_module._atexit_registered = False
|
|
yield
|
|
lsp_module._service = None
|
|
lsp_module._atexit_registered = False
|
|
|
|
|
|
def test_get_service_registers_atexit_handler_once(monkeypatch):
|
|
"""First call to ``get_service`` must register an atexit handler;
|
|
subsequent calls must NOT register another one (Python's ``atexit``
|
|
runs every registered callable, so a duplicate would shutdown
|
|
twice — harmless but wasteful)."""
|
|
fake_svc = MagicMock()
|
|
fake_svc.is_active.return_value = True
|
|
monkeypatch.setattr(
|
|
lsp_module.LSPService, "create_from_config", classmethod(lambda cls: fake_svc)
|
|
)
|
|
|
|
registrations = []
|
|
|
|
def fake_register(fn):
|
|
registrations.append(fn)
|
|
|
|
monkeypatch.setattr(atexit, "register", fake_register)
|
|
|
|
a = lsp_module.get_service()
|
|
b = lsp_module.get_service()
|
|
c = lsp_module.get_service()
|
|
|
|
assert a is fake_svc
|
|
assert b is fake_svc
|
|
assert c is fake_svc
|
|
assert len(registrations) == 1
|
|
# The registered callable must be our internal shutdown wrapper.
|
|
assert registrations[0] is lsp_module._atexit_shutdown
|
|
|
|
|
|
def test_atexit_shutdown_calls_shutdown_service(monkeypatch):
|
|
"""The atexit-registered wrapper invokes ``shutdown_service`` and
|
|
swallows any exception — by the time atexit fires, the user has
|
|
already seen the response and a noisy traceback would be clutter."""
|
|
called = []
|
|
monkeypatch.setattr(
|
|
lsp_module, "shutdown_service", lambda: called.append("shutdown")
|
|
)
|
|
lsp_module._atexit_shutdown()
|
|
assert called == ["shutdown"]
|
|
|
|
|
|
def test_atexit_shutdown_swallows_exceptions(monkeypatch):
|
|
def boom():
|
|
raise RuntimeError("server already dead")
|
|
|
|
monkeypatch.setattr(lsp_module, "shutdown_service", boom)
|
|
# Must not raise.
|
|
lsp_module._atexit_shutdown()
|
|
|
|
|
|
def test_shutdown_service_idempotent(monkeypatch):
|
|
"""Calling shutdown twice must be safe — first call cleans up,
|
|
second call no-ops (nothing to shut down)."""
|
|
fake_svc = MagicMock()
|
|
fake_svc.is_active.return_value = True
|
|
fake_svc.shutdown = MagicMock()
|
|
monkeypatch.setattr(
|
|
lsp_module.LSPService, "create_from_config", classmethod(lambda cls: fake_svc)
|
|
)
|
|
monkeypatch.setattr(atexit, "register", lambda fn: None)
|
|
|
|
lsp_module.get_service()
|
|
lsp_module.shutdown_service()
|
|
lsp_module.shutdown_service() # must not raise
|
|
|
|
assert fake_svc.shutdown.call_count == 1
|
|
|
|
|
|
def test_shutdown_service_no_op_when_never_started():
|
|
"""Calling shutdown without ever creating the service is safe."""
|
|
lsp_module.shutdown_service() # must not raise
|
|
|
|
|
|
def test_shutdown_service_swallows_exception(monkeypatch):
|
|
"""An exception during ``svc.shutdown()`` must not propagate —
|
|
the caller (often atexit) has nothing useful to do with it."""
|
|
fake_svc = MagicMock()
|
|
fake_svc.is_active.return_value = True
|
|
fake_svc.shutdown = MagicMock(side_effect=RuntimeError("kill -9 already"))
|
|
monkeypatch.setattr(
|
|
lsp_module.LSPService, "create_from_config", classmethod(lambda cls: fake_svc)
|
|
)
|
|
monkeypatch.setattr(atexit, "register", lambda fn: None)
|
|
|
|
lsp_module.get_service()
|
|
lsp_module.shutdown_service() # must not raise
|
|
|
|
|
|
def test_get_service_returns_none_for_inactive_service(monkeypatch):
|
|
"""A service whose ``is_active()`` returns False is treated as
|
|
not running — callers see ``None`` and fall back."""
|
|
fake_svc = MagicMock()
|
|
fake_svc.is_active.return_value = False
|
|
monkeypatch.setattr(
|
|
lsp_module.LSPService, "create_from_config", classmethod(lambda cls: fake_svc)
|
|
)
|
|
monkeypatch.setattr(atexit, "register", lambda fn: None)
|
|
|
|
assert lsp_module.get_service() is None
|
|
# Subsequent call returns None too — but the inactive instance is
|
|
# cached so we don't re-build it on every check.
|
|
assert lsp_module.get_service() is None
|
|
|
|
|
|
def test_get_service_returns_none_when_create_fails(monkeypatch):
|
|
"""Service factory returning ``None`` (no config, etc.) propagates."""
|
|
monkeypatch.setattr(
|
|
lsp_module.LSPService, "create_from_config", classmethod(lambda cls: None)
|
|
)
|
|
monkeypatch.setattr(atexit, "register", lambda fn: None)
|
|
|
|
assert lsp_module.get_service() is None
|