This commit is contained in:
root
2026-05-17 12:09:14 +00:00
parent a10ffe332d
commit ffdc887b8a
30 changed files with 728 additions and 111 deletions

View File

@@ -6,7 +6,13 @@ import unittest
from pathlib import Path
from unittest.mock import AsyncMock, patch
from src.web_process_backend import WebProcessBackend, build_pm2_start_command
from src.web_process_backend import (
WebProcessBackend,
_ffmpeg_pid_matches_process,
build_pm2_start_command,
pm2_process_entry_needs_recreate,
preferred_python_interpreter,
)
class WebProcessBackendTests(unittest.TestCase):
@@ -17,6 +23,7 @@ class WebProcessBackendTests(unittest.TestCase):
venv_python = base_dir / ".venv" / "bin" / "python"
venv_python.parent.mkdir(parents=True, exist_ok=True)
venv_python.write_text("#!/usr/bin/env python3\n", encoding="utf-8")
venv_python.chmod(0o755)
script.write_text("print('ok')\n", encoding="utf-8")
cmd = build_pm2_start_command(base_dir, "youtube2__lane1", "youtube.py")
@@ -29,6 +36,62 @@ class WebProcessBackendTests(unittest.TestCase):
self.assertIn(str(script), cmd)
self.assertIn(str(venv_python), cmd)
def test_preferred_python_interpreter_falls_back_to_system_python_when_venv_is_missing(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
base_dir = Path(tmp)
with patch("src.web_process_backend.shutil_which", side_effect=lambda name: f"/usr/bin/{name}" if name == "python3" else None):
with patch("src.web_process_backend.sys.executable", "/definitely/missing/python"):
chosen = preferred_python_interpreter(base_dir)
self.assertEqual(chosen, "/usr/bin/python3")
def test_pm2_process_entry_needs_recreate_when_python_interpreter_is_missing(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
base_dir = Path(tmp)
script = base_dir / "youtube.py"
script.write_text("print('ok')\n", encoding="utf-8")
item = {
"pm_exec_path": str(script),
"pm_cwd": str(base_dir),
"pm2_env": {
"exec_interpreter": str(base_dir / ".venv" / "bin" / "python"),
},
}
self.assertTrue(pm2_process_entry_needs_recreate(base_dir, "youtube", "youtube.py", item))
def test_start_recreates_stale_pm2_entry_instead_of_reusing_broken_interpreter(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
base_dir = Path(tmp)
script = base_dir / "youtube.py"
script.write_text("print('ok')\n", encoding="utf-8")
backend = WebProcessBackend(base_dir, base_dir / ".pm2" / "logs")
backend._use_pm2 = True
stale_item = {
"pm_exec_path": str(script),
"pm_cwd": str(base_dir),
"pm2_env": {
"exec_interpreter": str(base_dir / ".venv" / "bin" / "python"),
},
}
with patch.object(backend, "ensure_pm2_runtime", AsyncMock()):
with patch.object(backend, "_pm2_jlist_index", AsyncMock(return_value={"youtube": stale_item})):
with patch.object(backend, "clear_process_logs", AsyncMock()):
with patch("src.web_process_backend.build_pm2_start_command", return_value="pm2 start fresh") as build_cmd:
with patch("src.web_process_backend.run_pm2_result", new=AsyncMock(return_value=(0, "deleted"))) as run_pm2_result:
with patch("src.web_process_backend._shell_out", new=AsyncMock(return_value=(0, "created"))) as shell_out:
with patch.object(backend, "_set_pm2_desired_running", AsyncMock()) as set_desired:
with patch.object(backend, "_pm2_save", AsyncMock()):
text = asyncio.run(backend.start("youtube", "youtube.py"))
self.assertEqual(text, "created")
build_cmd.assert_called_once_with(base_dir, "youtube", "youtube.py")
run_pm2_result.assert_awaited_once_with("delete youtube", timeout=20.0)
shell_out.assert_awaited_once_with("pm2 start fresh", timeout=45.0)
set_desired.assert_awaited_once_with("youtube", True)
def test_parse_pm2_describe_marks_missing_jlist_process_as_not_found(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
base_dir = Path(tmp)
@@ -156,6 +219,19 @@ class WebProcessBackendTests(unittest.TestCase):
self.assertEqual(pids, [9876])
finder.assert_called_once_with(4321, root_pgid=6789)
def test_ffmpeg_pid_matches_process_uses_exact_inherited_env_name(self) -> None:
env_map = {
"LIVE_PM2_PROCESS_NAME": "youtube2__lane1",
"PM2_SERVICE_NAME": "",
}
def fake_env(pid: int, key: str) -> str:
return env_map.get(key, "")
with patch("src.web_process_backend._read_proc_env_value", side_effect=fake_env):
self.assertTrue(_ffmpeg_pid_matches_process(1234, "youtube2__lane1"))
self.assertFalse(_ffmpeg_pid_matches_process(1234, "youtube"))
if __name__ == "__main__":
unittest.main()