s
This commit is contained in:
@@ -29,41 +29,56 @@ class WebProcessBackendTests(unittest.TestCase):
|
||||
self.assertIn(str(script), cmd)
|
||||
self.assertIn(str(venv_python), cmd)
|
||||
|
||||
def test_parse_pm2_describe_marks_unknown_unregistered_process_as_not_found(self) -> None:
|
||||
def test_parse_pm2_describe_marks_missing_jlist_process_as_not_found(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
base_dir = Path(tmp)
|
||||
backend = WebProcessBackend(base_dir, base_dir / ".pm2" / "logs")
|
||||
backend._use_pm2 = True
|
||||
backend.ensure_mode = AsyncMock()
|
||||
backend._pm2_registered_names = AsyncMock(return_value=set())
|
||||
warn = "[PM2][WARN] Current process list is not synchronized with saved list. App youtube differs."
|
||||
with patch("src.web_process_backend.run_pm2", new=AsyncMock(return_value=warn)):
|
||||
info = asyncio.run(backend.parse_pm2_describe("youtube"))
|
||||
backend.process_infos = AsyncMock(return_value={"youtube": {"process_status": "not_found", "out_path": None, "err_path": None, "pid": 0}}) # type: ignore[method-assign]
|
||||
|
||||
info = asyncio.run(backend.parse_pm2_describe("youtube"))
|
||||
|
||||
self.assertEqual(info["process_status"], "not_found")
|
||||
self.assertIsNone(info["out_path"])
|
||||
self.assertIsNone(info["err_path"])
|
||||
self.assertEqual(info["pid"], 0)
|
||||
|
||||
def test_parse_pm2_describe_supports_spaced_log_path_keys(self) -> None:
|
||||
def test_parse_pm2_describe_reads_status_and_log_paths_from_jlist_item(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
base_dir = Path(tmp)
|
||||
backend = WebProcessBackend(base_dir, base_dir / ".pm2" / "logs")
|
||||
backend._use_pm2 = True
|
||||
backend.ensure_mode = AsyncMock()
|
||||
backend._pm2_registered_names = AsyncMock(return_value={"youtube"})
|
||||
describe = "\n".join(
|
||||
[
|
||||
"│ status │ online │",
|
||||
"│ out log path │ /home/live/.pm2/logs/youtube-out.log │",
|
||||
"│ error log path │ /home/live/.pm2/logs/youtube-error.log │",
|
||||
]
|
||||
backend.process_infos = AsyncMock( # type: ignore[method-assign]
|
||||
return_value={
|
||||
"youtube": {
|
||||
"process_status": "online",
|
||||
"out_path": "/home/live/.pm2/logs/youtube-out.log",
|
||||
"err_path": "/home/live/.pm2/logs/youtube-error.log",
|
||||
"pid": 4567,
|
||||
}
|
||||
}
|
||||
)
|
||||
with patch("src.web_process_backend.run_pm2", new=AsyncMock(return_value=describe)):
|
||||
info = asyncio.run(backend.parse_pm2_describe("youtube"))
|
||||
|
||||
info = asyncio.run(backend.parse_pm2_describe("youtube"))
|
||||
|
||||
self.assertEqual(info["process_status"], "online")
|
||||
self.assertEqual(info["out_path"], "/home/live/.pm2/logs/youtube-out.log")
|
||||
self.assertEqual(info["err_path"], "/home/live/.pm2/logs/youtube-error.log")
|
||||
self.assertEqual(info["pid"], 4567)
|
||||
|
||||
def test_ensure_mode_resurrects_pm2_when_dump_exists_and_jlist_is_empty(self) -> None:
|
||||
def test_pm2_jlist_items_uses_short_ttl_cache(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
base_dir = Path(tmp)
|
||||
backend = WebProcessBackend(base_dir, base_dir / ".pm2" / "logs")
|
||||
shell_out = AsyncMock(return_value=(0, '[{"name":"youtube","pm2_env":{"status":"online"}}]'))
|
||||
with patch("src.web_process_backend.resolve_pm2_bin", return_value="/usr/bin/pm2"):
|
||||
with patch("src.web_process_backend._shell_out", new=shell_out):
|
||||
first = asyncio.run(backend._pm2_jlist_items())
|
||||
second = asyncio.run(backend._pm2_jlist_items())
|
||||
|
||||
self.assertEqual(first[0]["name"], "youtube")
|
||||
self.assertEqual(second[0]["name"], "youtube")
|
||||
shell_out.assert_awaited_once()
|
||||
|
||||
def test_ensure_pm2_runtime_resurrects_pm2_when_dump_exists_and_jlist_is_empty(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
base_dir = Path(tmp)
|
||||
pm2_home = base_dir / ".pm2-home"
|
||||
@@ -75,16 +90,16 @@ class WebProcessBackendTests(unittest.TestCase):
|
||||
backend._use_pm2 = True
|
||||
|
||||
shell_out = AsyncMock(return_value=(0, "[]"))
|
||||
resurrect = AsyncMock(return_value="ok")
|
||||
resurrect = AsyncMock(return_value=(0, "ok"))
|
||||
with patch.dict("os.environ", {"PM2_HOME": str(pm2_home)}, clear=False):
|
||||
with patch("src.web_process_backend.resolve_pm2_bin", return_value="/usr/bin/pm2"):
|
||||
with patch("src.web_process_backend._shell_out", new=shell_out):
|
||||
with patch("src.web_process_backend.run_pm2", new=resurrect):
|
||||
asyncio.run(backend.ensure_mode())
|
||||
with patch("src.web_process_backend.run_pm2_result", new=resurrect):
|
||||
asyncio.run(backend.ensure_pm2_runtime())
|
||||
|
||||
resurrect.assert_awaited_once_with("resurrect", timeout=45.0)
|
||||
|
||||
def test_ensure_mode_skips_resurrect_without_saved_dump(self) -> None:
|
||||
def test_ensure_pm2_runtime_skips_resurrect_without_saved_dump(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
base_dir = Path(tmp)
|
||||
pm2_home = base_dir / ".pm2-home"
|
||||
@@ -95,15 +110,52 @@ class WebProcessBackendTests(unittest.TestCase):
|
||||
backend._use_pm2 = True
|
||||
|
||||
shell_out = AsyncMock(return_value=(0, "[]"))
|
||||
resurrect = AsyncMock(return_value="ok")
|
||||
resurrect = AsyncMock(return_value=(0, "ok"))
|
||||
with patch.dict("os.environ", {"PM2_HOME": str(pm2_home)}, clear=False):
|
||||
with patch("src.web_process_backend.resolve_pm2_bin", return_value="/usr/bin/pm2"):
|
||||
with patch("src.web_process_backend._shell_out", new=shell_out):
|
||||
with patch("src.web_process_backend.run_pm2", new=resurrect):
|
||||
asyncio.run(backend.ensure_mode())
|
||||
with patch("src.web_process_backend.run_pm2_result", new=resurrect):
|
||||
asyncio.run(backend.ensure_pm2_runtime())
|
||||
|
||||
resurrect.assert_not_awaited()
|
||||
|
||||
def test_start_does_not_mark_desired_running_when_shell_command_fails(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
|
||||
|
||||
with patch.object(backend, "ensure_mode", AsyncMock()):
|
||||
with patch.object(backend, "_pm2_registered_names", AsyncMock(return_value=set())):
|
||||
with patch.object(backend, "clear_process_logs", AsyncMock()):
|
||||
with patch("src.web_process_backend.build_pm2_start_command", return_value="pm2 start ..."):
|
||||
with patch("src.web_process_backend._shell_out", new=AsyncMock(return_value=(1, "boom"))):
|
||||
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, "boom")
|
||||
set_desired.assert_not_awaited()
|
||||
|
||||
def test_live_ffmpeg_pids_uses_process_tree_of_target_process(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
base_dir = Path(tmp)
|
||||
backend = WebProcessBackend(base_dir, base_dir / ".pm2" / "logs")
|
||||
backend.ensure_mode = AsyncMock()
|
||||
backend.process_infos = AsyncMock( # type: ignore[method-assign]
|
||||
return_value={"youtube": {"pid": 4321, "process_status": "online"}}
|
||||
)
|
||||
|
||||
with patch("src.web_process_backend.os.getpgid", return_value=6789):
|
||||
with patch("src.web_process_backend._linux_ffmpeg_pids_for_process_tree", return_value=[9876]) as finder:
|
||||
pids = asyncio.run(backend.live_ffmpeg_pids("youtube"))
|
||||
|
||||
self.assertEqual(pids, [9876])
|
||||
finder.assert_called_once_with(4321, root_pgid=6789)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user