Files
timmy-config/tests/test_hermes_cleanup.py
Alexander Whitestone 19db78bbf0
Some checks failed
Architecture Lint / Linter Tests (pull_request) Successful in 6m45s
Smoke Test / smoke (pull_request) Failing after 8s
Validate Config / YAML Lint (pull_request) Failing after 8s
Validate Config / JSON Validate (pull_request) Successful in 11s
Validate Config / Python Syntax & Import Check (pull_request) Failing after 43s
Validate Config / Python Test Suite (pull_request) Has been skipped
Validate Config / Shell Script Lint (pull_request) Failing after 36s
Validate Config / Cron Syntax Check (pull_request) Successful in 8s
Validate Config / Deploy Script Dry Run (pull_request) Successful in 9s
Validate Config / Playbook Schema Validation (pull_request) Successful in 15s
PR Checklist / pr-checklist (pull_request) Successful in 2m45s
Architecture Lint / Lint Repository (pull_request) Failing after 20s
feat: stale hermes process cleanup script (#829)
bin/hermes_cleanup.py:
  Identifies stale hermes sessions (old + idle)
  Groups by session, tracks parent+children
  Memory waste calculation (RSS in MB/GB)
  --kill to terminate, --dry-run (default) to report
  --max-age (default 24h), --max-cpu (default 0.5%)
  --json output, human-readable table

tests/test_hermes_cleanup.py: 8 tests
  process age, child PIDs, kill session,
  dry run, report generation

Usage:
  python3 bin/hermes_cleanup.py              # report
  python3 bin/hermes_cleanup.py --kill       # terminate
  python3 bin/hermes_cleanup.py --max-age 12 # 12h threshold
  python3 bin/hermes_cleanup.py --json       # JSON
2026-04-20 20:38:20 -04:00

96 lines
2.9 KiB
Python

"""
Tests for bin/hermes_cleanup.py — Stale process detection and cleanup.
"""
import unittest
from unittest.mock import patch, MagicMock
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent / "bin"))
from hermes_cleanup import (
get_process_age_hours,
get_child_pids,
identify_stale_sessions,
kill_session,
generate_report,
)
class TestGetProcessAgeHours(unittest.TestCase):
@patch("hermes_cleanup.subprocess.run")
def test_returns_age(self, mock_run):
mock_run.return_value = MagicMock(returncode=0, stdout="3600\n")
age = get_process_age_hours(1234)
self.assertAlmostEqual(age, 1.0, delta=0.01)
@patch("hermes_cleanup.subprocess.run")
def test_returns_none_on_error(self, mock_run):
mock_run.return_value = MagicMock(returncode=1, stdout="")
age = get_process_age_hours(9999)
self.assertIsNone(age)
class TestGetChildPids(unittest.TestCase):
@patch("hermes_cleanup.subprocess.run")
def test_returns_child_pids(self, mock_run):
mock_run.return_value = MagicMock(returncode=0, stdout="1001\n1002\n")
pids = get_child_pids(1234)
self.assertEqual(pids, [1001, 1002])
@patch("hermes_cleanup.subprocess.run")
def test_returns_empty_on_no_children(self, mock_run):
mock_run.return_value = MagicMock(returncode=1, stdout="")
pids = get_child_pids(1234)
self.assertEqual(pids, [])
class TestKillSession(unittest.TestCase):
def test_dry_run_does_not_kill(self):
session = {
"session_key": "test",
"main_pid": 99999, # unlikely to exist
"children": [],
}
result = kill_session(session, dry_run=True)
self.assertTrue(result["dry_run"])
self.assertIn(99999, result["killed"])
@patch("hermes_cleanup.os.kill")
def test_kill_terminates_process(self, mock_kill):
session = {
"session_key": "test",
"main_pid": 1234,
"children": [1235],
}
result = kill_session(session, dry_run=False)
self.assertFalse(result["dry_run"])
self.assertEqual(mock_kill.call_count, 2)
class TestGenerateReport(unittest.TestCase):
def test_empty_report(self):
report = generate_report([])
self.assertIn("No stale sessions", report)
def test_report_with_stale(self):
stale = [{
"session_key": "test",
"main_pid": 1234,
"age_hours": 48.5,
"cpu_percent": 0.1,
"total_rss_kb": 20480,
"total_rss_mb": 20.0,
"process_count": 2,
"command": "python3 -m hermes.cli chat",
"children": [1235],
}]
report = generate_report(stale)
self.assertIn("48.5h", report)
self.assertIn("20.0 MB", report)
if __name__ == "__main__":
unittest.main()