feat: VRSub 单体应用(WOV 单机版)初始提交
为视频生成 VR 双眼字幕的单体实现:FastAPI 后端、调度器与全部节点 (提音/转写/翻译/ASS/抽帧/OCR/LLM 过滤)在单进程内运行。 - 节点协议(wov_sdk 数据模型)与分布式版保持一致,预留回退桥梁 - 工作流即数据:DAG 存于 workflows/*.json,模型/链路改动只改数据 - 调度器:拓扑顺序执行、断点续跑(产物重建)、任务暂停/继续 - 抽帧按帧间隔(select 按帧号精确取帧),VLM OCR 与 LLM 过滤使用 自适应线程池弹性并发,并打印数据处理速度进度日志 - 100% 行覆盖率(pytest --cov-fail-under=100)
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
"""进程内节点注册表测试。
|
||||
|
||||
覆盖节点注册、全量注册、查询、进程内调用以及未注册节点的报错路径,
|
||||
验证注册表作为调度器唯一调用入口的正确性。
|
||||
"""
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user