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()
|