为视频生成 VR 双眼字幕的单体实现:FastAPI 后端、调度器与全部节点 (提音/转写/翻译/ASS/抽帧/OCR/LLM 过滤)在单进程内运行。 - 节点协议(wov_sdk 数据模型)与分布式版保持一致,预留回退桥梁 - 工作流即数据:DAG 存于 workflows/*.json,模型/链路改动只改数据 - 调度器:拓扑顺序执行、断点续跑(产物重建)、任务暂停/继续 - 抽帧按帧间隔(select 按帧号精确取帧),VLM OCR 与 LLM 过滤使用 自适应线程池弹性并发,并打印数据处理速度进度日志 - 100% 行覆盖率(pytest --cov-fail-under=100)
117 lines
4.1 KiB
Python
117 lines
4.1 KiB
Python
"""进程内节点注册表测试。
|
|
|
|
覆盖节点注册、全量注册、查询、进程内调用以及未注册节点的报错路径,
|
|
验证注册表作为调度器唯一调用入口的正确性。
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from wov_app import registry
|
|
from wov_sdk.models import InvokeRequest, InvokeResponse, NodeManifest
|
|
|
|
|
|
def _echo_manifest() -> NodeManifest:
|
|
"""构造最小合法 echo 节点清单。"""
|
|
return NodeManifest(
|
|
id="echo",
|
|
name="Echo",
|
|
version="1.0.0",
|
|
capability="echo",
|
|
command=["python", "-m", "echo"],
|
|
repo_dir="nodes",
|
|
)
|
|
|
|
|
|
def test_register_and_list() -> None:
|
|
"""验证注册后可查询与列出节点,且按 ID 排序。"""
|
|
registry.register(_echo_manifest(), lambda request: InvokeResponse(status="completed"))
|
|
registry.register(
|
|
NodeManifest(
|
|
id="z-node",
|
|
name="Z",
|
|
version="1",
|
|
capability="x",
|
|
command=["python", "-m", "z"],
|
|
repo_dir="nodes",
|
|
),
|
|
lambda request: InvokeResponse(status="completed"),
|
|
)
|
|
assert [node.id for node in registry.list_nodes()] == ["echo", "z-node"]
|
|
assert registry.get_node("echo").capability == "echo"
|
|
assert registry.get_node("missing") is None
|
|
|
|
|
|
def test_register_validation() -> None:
|
|
"""验证非法 manifest 注册会被协议校验拒绝。"""
|
|
invalid = _echo_manifest()
|
|
invalid.id = ""
|
|
with pytest.raises(ValueError):
|
|
registry.register(invalid, lambda request: InvokeResponse(status="completed"))
|
|
|
|
|
|
def test_register_all_loads_builtin_nodes() -> None:
|
|
"""验证 register_all 会加载 manifests/ 下全部内置节点。"""
|
|
registry.register_all()
|
|
ids = {node.id for node in registry.list_nodes()}
|
|
assert {
|
|
"echo",
|
|
"ffmpeg-extract",
|
|
"faster-whisper",
|
|
"llm-translate",
|
|
"vlm-ocr",
|
|
"srt-to-dual-eye-ass",
|
|
} <= ids
|
|
|
|
|
|
def test_invoke_calls_handler() -> None:
|
|
"""验证 invoke 会把请求转发给注册的进程内处理器。"""
|
|
captured = {}
|
|
|
|
def handler(request: InvokeRequest) -> InvokeResponse:
|
|
captured["run_id"] = request.run_id
|
|
return InvokeResponse(status="completed", outputs={"text": "ok"})
|
|
|
|
registry.register(_echo_manifest(), handler)
|
|
response = registry.invoke("echo", InvokeRequest(run_id="run_1", node_instance_id=""))
|
|
assert response.status == "completed"
|
|
assert response.outputs == {"text": "ok"}
|
|
assert captured["run_id"] == "run_1"
|
|
|
|
|
|
def test_invoke_unknown_node() -> None:
|
|
"""验证调用未注册节点时抛出 ValueError。"""
|
|
with pytest.raises(ValueError, match="not registered"):
|
|
registry.invoke("missing", InvokeRequest(run_id="run_1", node_instance_id=""))
|
|
|
|
|
|
def test_invoke_logs_node_lifecycle(caplog) -> None:
|
|
"""验证 invoke 会记录节点的开始/完成/耗时日志(主进程可见)。"""
|
|
registry.register(
|
|
_echo_manifest(), lambda request: InvokeResponse(status="completed", outputs={"text": "ok"})
|
|
)
|
|
with caplog.at_level("INFO", logger="vrsub.node"):
|
|
registry.invoke("echo", InvokeRequest(run_id="run_1", node_instance_id=""))
|
|
assert any("节点 echo 开始" in record.message for record in caplog.records)
|
|
assert any("节点 echo 完成" in record.message for record in caplog.records)
|
|
|
|
|
|
def test_invoke_logs_node_failure(caplog) -> None:
|
|
"""验证节点返回 failed 时记录失败日志。"""
|
|
registry.register(
|
|
_echo_manifest(), lambda request: InvokeResponse(status="failed", error="boom")
|
|
)
|
|
with caplog.at_level("INFO", logger="vrsub.node"):
|
|
registry.invoke("echo", InvokeRequest(run_id="run_1", node_instance_id=""))
|
|
assert any("节点 echo 失败" in record.message for record in caplog.records)
|
|
|
|
|
|
def test_get_logger_idempotent() -> None:
|
|
"""验证日志器重复获取不会重复附加控制台处理器。"""
|
|
from wov_app.logging import get_logger
|
|
|
|
logger = get_logger("idempotent")
|
|
handler_count = len(logger.handlers)
|
|
again = get_logger("idempotent")
|
|
assert again is logger
|
|
assert len(again.handlers) == handler_count
|