Files
olivv-cs-ComfyUI-FunPack/movie_editor/tests/test_bridge.py
T
digital-garbageandClaude Opus 5 f426b260b5 fix(editor): keep the log when the backend dies
The panel replaced the whole log with the fetch error — destroying the
last lines the server sent at the exact moment they matter, and what
Copy would hand over. It now holds them and backfills across a restart
from ComfyUI's own log file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 04:38:58 +03:00

146 lines
5.4 KiB
Python

"""Bridge import helpers and parse error formatting."""
import pytest
from movie_editor.backend import bridge
def test_format_funpack_error_with_message():
assert bridge.format_funpack_error(ValueError("bad trigger")) == "ValueError: bad trigger"
def test_format_funpack_error_empty_message():
assert bridge.format_funpack_error(KeyError()) == "KeyError (no message)"
def test_ensure_funpack_path_adds_repo_root():
bridge._FUNPACK_PATH_ENSURED = False
import sys
root = str(bridge._FUNPACK_ROOT)
sys.path[:] = [p for p in sys.path if p != root]
bridge._ensure_funpack_path()
assert root in sys.path
def _q_item(prompt_id, client_id, funpack=None):
extra = {"client_id": client_id}
if funpack is not None:
extra["funpack"] = funpack
return [1, prompt_id, {"graph": True}, extra, ["9"]]
def test_select_active_picks_editor_running_job_with_metadata():
state = {
"queue_running": [_q_item("p1", bridge.EDITOR_CLIENT_ID,
{"pid": "proj-7", "scene_ids": ["a", "b"], "only_scene": None})],
"queue_pending": [_q_item("p2", bridge.EDITOR_CLIENT_ID, {"pid": "proj-7"})],
}
out = bridge._select_active(state)
assert out["running"] is True
assert out["prompt_id"] == "p1"
assert out["pid"] == "proj-7"
assert out["scene_ids"] == ["a", "b"]
assert out["pending"] == 1
def test_select_active_ignores_foreign_client_jobs():
state = {
"queue_running": [_q_item("other", "some-comfy-tab")],
"queue_pending": [_q_item("other2", "some-comfy-tab")],
}
out = bridge._select_active(state)
assert out["running"] is False
assert out["prompt_id"] is None
assert out["pending"] == 0
def test_select_active_empty_queue():
out = bridge._select_active({})
assert out == {"running": False, "prompt_id": None, "pid": None,
"scene_ids": [], "only_scene": None, "pending": 0}
def test_select_active_running_without_funpack_meta():
state = {"queue_running": [_q_item("p1", bridge.EDITOR_CLIENT_ID)]}
out = bridge._select_active(state)
assert out["running"] is True
assert out["prompt_id"] == "p1"
assert out["pid"] is None
assert out["scene_ids"] == []
def test_rating_labels_hide_internal_editor_values(monkeypatch):
def _fake_attr(_mod, name):
table = {
"V2_RATING_LABELS": ["Perfect", "__funpack_continue__", "__funpack_fresh_prompt__"],
"MOVIE_EDITOR_CONTINUE_RATING": "__funpack_continue__",
"MOVIE_EDITOR_FRESH_PROMPT_RATING": "__funpack_fresh_prompt__",
}
return table[name]
monkeypatch.setattr(bridge, "_funpack_attr", _fake_attr)
labels = bridge.rating_labels().get("labels") or []
assert "__funpack_continue__" not in labels
assert "__funpack_fresh_prompt__" not in labels
assert all(not str(l).startswith("__funpack_") for l in labels)
assert "Perfect" in labels
# ── the log panel has to survive a restart ────────────────────────────────────
# The in-memory buffer only holds what THIS process printed, so a crash-and-restart left the
# panel empty — exactly when the lines before the crash are the ones worth reading.
@pytest.fixture
def logfile(tmp_path, monkeypatch):
path = tmp_path / "comfyui.log"
monkeypatch.setattr(bridge, "_comfy_log_file", lambda: path if path.is_file() else None)
bridge._LOG_FILE_CACHE.update({"at": 0.0, "lines": []})
with bridge._LOG_LOCK:
bridge._LOG.clear()
return path
def test_a_short_buffer_is_backfilled_from_comfyuis_own_log(logfile):
logfile.write_text("older 1\nolder 2\nolder 3\n")
assert bridge.recent_log(10) == ["older 1", "older 2", "older 3"]
def test_the_seam_is_not_shown_twice(logfile):
"""The file contains what the buffer holds. Without cutting the overlap the panel shows
the same lines once from the file and once from the buffer."""
logfile.write_text("old\nlive 1\nlive 2\n")
with bridge._LOG_LOCK:
bridge._LOG.extend(["live 1", "live 2"])
assert bridge.recent_log(10) == ["old", "live 1", "live 2"]
def test_a_full_buffer_never_touches_the_file(logfile, monkeypatch):
"""Polled every 1.5s — once this process has enough of its own output, reading the file
every time would be a file read per poll for nothing."""
logfile.write_text("should not be read\n")
monkeypatch.setattr(bridge, "_log_file_tail",
lambda n: pytest.fail("read the file with a full buffer"))
with bridge._LOG_LOCK:
bridge._LOG.extend(["a", "b", "c"])
assert bridge.recent_log(3) == ["a", "b", "c"]
def test_no_log_file_is_not_an_error(logfile):
with bridge._LOG_LOCK:
bridge._LOG.extend(["only live"])
assert bridge.recent_log(10) == ["only live"]
def test_an_unreadable_log_file_is_not_an_error(logfile, monkeypatch):
logfile.write_text("x\n")
monkeypatch.setattr(bridge, "_comfy_log_file",
lambda: (_ for _ in ()).throw(OSError("nope")))
assert isinstance(bridge.recent_log(10), list)
def test_only_the_tail_of_a_huge_log_is_read(logfile):
"""A log can be hundreds of MB on a long-running box; the panel wants the last lines."""
logfile.write_text("\n".join(f"line {i}" for i in range(200_000)) + "\n")
out = bridge.recent_log(5)
assert len(out) == 5 and out[-1] == "line 199999"