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
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
96 lines
2.9 KiB
Python
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()
|