Files
fidelity-ai-workspace/scripts/aiw/test_services.py

168 lines
6.0 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
import importlib.util
import io
import socket
import sys
import tempfile
import unittest
import warnings
from pathlib import Path
from contextlib import redirect_stdout
from unittest.mock import patch
SERVICES_PATH = Path(__file__).with_name("services.py")
SPEC = importlib.util.spec_from_file_location("aiw_services", SERVICES_PATH)
services = importlib.util.module_from_spec(SPEC)
assert SPEC.loader is not None
sys.modules[SPEC.name] = services
SPEC.loader.exec_module(services)
def sample_manifest() -> dict:
return {
"services": {
"alpha": {
"enabled": True,
"kind": "process",
"command": ["python3", "-c", "import time; time.sleep(60)"],
"groups": ["core"],
},
"beta": {
"enabled": True,
"kind": "process",
"command": ["python3", "-c", "import time; time.sleep(60)"],
"groups": ["capture"],
"depends_on": ["alpha"],
},
"disabled": {
"enabled": False,
"kind": "process",
"command": ["python3", "-c", "import time; time.sleep(60)"],
"groups": ["core"],
},
}
}
class ServiceManagerTests(unittest.TestCase):
def test_select_services_excludes_disabled_by_default(self) -> None:
selected = services.select_services(sample_manifest(), names=[], group=None)
self.assertEqual([item.name for item in selected], ["alpha", "beta"])
def test_select_services_can_include_disabled_for_status(self) -> None:
selected = services.select_services(sample_manifest(), names=[], group=None, include_disabled=True)
self.assertEqual([item.name for item in selected], ["alpha", "beta", "disabled"])
def test_select_services_filters_by_group(self) -> None:
selected = services.select_services(sample_manifest(), names=[], group="capture")
self.assertEqual([item.name for item in selected], ["beta"])
def test_health_ok_tcp_reports_open_port(self) -> None:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.bind(("127.0.0.1", 0))
server.listen(1)
port = server.getsockname()[1]
ok, detail = services.health_ok({"health": {"type": "tcp", "host": "127.0.0.1", "port": port}})
self.assertTrue(ok)
self.assertIn(f"127.0.0.1:{port}", detail)
def test_validate_manifest_reports_bad_dependencies(self) -> None:
manifest = {
"services": {
"bad": {
"enabled": True,
"kind": "process",
"command": ["python3", "-c", "pass"],
"depends_on": ["missing"],
}
}
}
errors = services.validate_manifest(manifest)
self.assertTrue(any("depends on unknown service" in error for error in errors))
def test_validate_manifest_reports_missing_command_for_enabled_service(self) -> None:
manifest = {"services": {"bad": {"enabled": True, "kind": "process"}}}
errors = services.validate_manifest(manifest)
self.assertTrue(any("non-empty command" in error for error in errors))
def test_command_exists_supports_relative_executable_paths(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
script = root / "bin" / "tool.sh"
script.parent.mkdir(parents=True)
script.write_text("#!/usr/bin/env bash\n", encoding="utf-8")
script.chmod(0o755)
with patch.object(services, "ROOT", root):
self.assertTrue(services.command_exists("bin/tool.sh"))
def test_rotate_log_if_needed_keeps_backups(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
log = Path(tmp) / "service.log"
log.write_text("old", encoding="utf-8")
services.rotate_log_if_needed(log, max_bytes=1, backups=2)
self.assertFalse(log.exists())
self.assertEqual(log.with_suffix(".log.1").read_text(encoding="utf-8"), "old")
def test_read_pid_ignores_invalid_pid_file(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
pid_dir = Path(tmp) / "pids"
target = pid_dir / "fidelity"
target.mkdir(parents=True)
(target / "alpha.pid").write_text("not-a-pid\n", encoding="utf-8")
with patch.object(services, "PID_DIR", pid_dir):
self.assertIsNone(services.read_pid("fidelity", "alpha"))
def test_start_and_stop_managed_process(self) -> None:
manifest = {
"services": {
"sleeper": {
"enabled": True,
"kind": "process",
"command": [sys.executable, "-c", "import time; time.sleep(60)"],
"restart": "never",
}
}
}
ref = services.ServiceRef("sleeper", manifest["services"]["sleeper"])
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
with patch.object(services, "PID_DIR", root / "pids"), \
patch.object(services, "LOG_DIR", root / "logs"), \
patch.object(services, "STATE_DIR", root / "state"):
started: set[str] = set()
services.ensure_runtime()
with warnings.catch_warnings():
warnings.simplefilter("ignore", ResourceWarning)
with redirect_stdout(io.StringIO()):
services.start_service("test", ref, manifest, started)
pid = services.read_pid("test", "sleeper")
self.assertTrue(services.is_running(pid))
self.assertIn("sleeper", started)
with redirect_stdout(io.StringIO()):
services.stop_service("test", ref)
self.assertFalse(services.is_running(pid))
if __name__ == "__main__":
unittest.main()