批量引擎改为「分块流水线」:视频按 WOV_BATCH_STAGE_GROUP_SIZE(默认 8)分组, 组内按 DAG 拓扑序跑完全部视频(全部 extract → 全部 ASR → 全部翻译 → 全部 ASS) 再进入下一组,本地模型每组只加载一次、卸载一次,而不是每个视频来回加载卸载; 产物仍按组增量落到视频旁。调度器新增 execute_run(run_id, stop_after=节点): 该节点完成后任务保持 RUNNING 不收尾,下一次调用从产物表跳过已完成节点继续, 用于实现阶段边界。 - nodes/llm.py:翻译节点结束释放本机 Ollama 显存(node 参数 unload_after > LLM_UNLOAD_AFTER > 本机 loopback 端点默认卸载,云端端点不卸载;卸载失败只告警), 新增 keep_model.flag 语义(阶段内保持常驻)与 release_local_model(); 新增节点内暂停(按批 20 行检查 paused.flag,抛 PauseRequested,调度器保持 PAUSED)。 - src/wov_app/batch.py:分组阶段执行与阶段末统一释放显存;失败视频只在它失败 节点的那个阶段重试(避免 LLM 已常驻时重跑 ASR 抢显存);任务没有明细时保持 QUEUED 等登记完成、仍有未完成视频时置回 QUEUED 自愈(原先留 RUNNING 会卡死: 引擎只拾取 QUEUED,任务停在“运行中但没人推进”);无失败视频时删除任务级空目录; 每个阶段开始前清理 paused.flag / keep_model.flag,避免强杀残留影响后续阶段。 - src/wov_app/config.py:新增 WOV_BATCH_STAGE_GROUP_SIZE(设为 1 即旧的每视频全链路)。 - 任务列表与批量页分工:GET /api/runs 默认排除 source=batch(一个批量任务会产生 N 条单视频 run,会把 20 条窗口占满;且任务管理页的暂停/重试/删除对批量 run 语义不成立),需要排查时用 include_batch=1;作为补偿批量页详情新增阶段列 (阶段 i/N · 中文标签,由该视频 run 的 current_node_id 在 DAG 拓扑序中的位置 推导,节点类型映射中文标签)。阶段只有节点边界粒度,句级进度不落库、只在日志。 - 顺带纳入此前未提交的批量僵尸状态恢复:recover_interrupted_batch_jobs 除 RUNNING 外也把「COMPLETED 但仍含未结束视频」的任务置回 QUEUED;fix_zombie_batch_jobs.py 改为按条件扫描并支持 --apply 预览;批量页明细只列本批真正处理过的视频。 测试新增/更新:分块流水线调用顺序(组内按节点跑完再下一组)、每组只释放一次模型、 阶段内保持常驻标志、翻译按批暂停、失败视频不跨阶段推进、任务无明细/中途登记视频时 置回 QUEUED、任务工作空间与残留信号清理、任务列表默认过滤批量 run、详情阶段字段、 前端阶段列渲染;全量 507 passed(唯一失败为既有素材缺失的 integration 用例)。
13 KiB
测试
测试代码位于 tests/,运行方式:
uv run pytest # 全部测试
uv run pytest tests/nodes/test_whisper # 只跑某个模块目录
uv run pytest tests/app/test_routers # 只跑某组 API 测试
uv run pytest --cov=src --cov=nodes # 需要覆盖率时手动追加
测试的规则要求写在 ../AGENTS.md:模块边界、 按模块组织、数据/过程/验证三段结构、功能覆盖优先、真实数据与真实生产代码。
目录结构与模块对应
测试按模块组织:被测代码的层级与模块名直接映射到 tests/ 下的目录。
一个模块 = 一个模块目录;单文件过长时在模块目录内部按行为拆文件。
tests/
├── nodes/ # 对应 nodes/
│ ├── test_srt/ # SRT 解析/序列化(parsing + data/)
│ ├── test_whisper/ # 转写:模型解析/分块/时间轴/幻觉清洗
│ ├── test_ass/ # SRT → VR 双目 ASS
│ ├── test_ffmpeg/ # ffmpeg 定位 + 提音(真实视频)
│ ├── test_frame_extract/ # 抽帧/crop/帧号排序(含 frames_boundary 回归)
│ ├── test_vlm/ # 单帧 OCR(真实图片 + Ollama 集成)
│ ├── test_subtitle_ocr/ # 逐帧 OCR 汇总、断点、暂停(14236 帧夹具)
│ ├── test_llm/ # 翻译:ID 对齐/重试/专名注入
│ ├── test_llm_filter/ # 规则层 + LLM 层(真实 OCR 回归数据)
│ ├── test_subtitle_cleanup/ # 幻觉删除 + 短呻吟过滤
│ ├── test_subtitle_correction/# 领域纠错(上下文/误听泛化)
│ ├── test_proper_nouns/ # 专名/隐语规则表
│ ├── test_adaptive_pool/ # 自适应并发额度
│ ├── test_vad_profiler/ # VAD 信号分析与评分
│ └── test_echo/ # 示例节点
├── app/ # 对应 src/wov_app/
│ ├── test_db/ # SQLite Repository
│ ├── test_scheduler/ # DAG 调度、断点、暂停
│ ├── test_batch/ # 批量扫描/放置/引擎
│ ├── test_maintenance/ # 孤儿清理
│ ├── test_registry/ # 节点注册表(唯一调用入口)
│ ├── test_seed/ # 内置工作流加载
│ ├── test_storage/ # 原子复制
│ ├── test_config/ # 环境变量与路径(子进程验证)
│ ├── test_logging/ # 日志配置
│ ├── test_main/ # 应用装配与生命周期
│ └── test_routers/ # 三组 API(apps / workflows / batch)
├── sdk/test_models/ # 对应 src/wov_sdk/(协议数据模型)
├── web/test_crop/ # 对应 web/assets/(框选几何换算)
├── web/test_batch/ # 对应 web/assets/(批量页渲染)
├── scripts/test_fix_zombie_batch_jobs/ # 对应 scripts/(僵尸批量任务修复)
└── shared/ # 跨模块公共设施
├── realdata_contract.py # 真实数据契约与对齐量化
├── srt_entries.py # 按秒解析 SRT
├── env_isolation.py # 环境/临时目录隔离
├── data/alignment/ # 跨模块共享的真实素材 + 参考字幕
├── test_srt_entries/ # 公共设施自身的模块测试
└── test_alignment/ # 真实语音时间对齐集成测试
规则要点(完整表述见 ../AGENTS.md):
- 每个模块目录含
__init__.py,因此不同模块下的同名测试文件可共存。 - 每个测试写成数据 → 测试过程 → 验证结果三段,可单独运行。
- 不保留全局
conftest.py(已删除):隔离由模块自己的 fixture 完成 (tests/shared/env_isolation.py或模块内fixture)。 - 测试过程调用真实生产代码(
nodes/、wov_app、wov_sdk), 只自行构建测试数据并验证输出;mock 仅用于 I/O 边界与模型推理。
测试数据
测试媒体一次性准备后随模块目录入库,测试直接复用,禁止在执行时现场生成; 缺失时跳过而非现场生成。小体量数据(JSON 片段、期望输出)直接写在测试代码内。
| 素材 | 内容 | 归属模块 |
|---|---|---|
data/speech_60s.wav |
真实语音(合法 16kHz 单声道 WAV) | tests/nodes/test_whisper/ |
data/clip_with_audio.mp4 |
10 秒真实视频 + 真实语音音轨(提音用例) | tests/nodes/test_ffmpeg/ |
data/subtitle_10s.mp4 |
烧录 SUB 001/SUB 002 的 10s 视频(无音轨,验证失败路径) | tests/nodes/test_ffmpeg/、tests/nodes/test_frame_extract/ |
data/frames_boundary/*.png |
跨越 9999 帧边界的真实帧文件名(14236 帧事故回归) | tests/nodes/test_frame_extract/ |
data/ocr_text.png / ocr_notext.png |
有文字帧 / 无文字帧 | tests/nodes/test_vlm/ |
data/test_real_hav_sub.png |
真实视频字幕截图(期望识别出"还有没有什么困扰 或者奇怪的地方吗") | tests/nodes/test_vlm/ |
data/ocr_srt_run_ac7f480a3ccb.srt |
真实任务 1666 条 OCR 输出(规则层回归基线) | tests/nodes/test_llm_filter/ |
data/frames_manifest_full.json + ocr_frames_full.json |
真实任务 全部 14236 帧清单与逐帧 OCR 文本 | tests/nodes/test_subtitle_ocr/ |
data/sample.reference.srt |
真实参考字幕(5 条,含装饰行) | tests/nodes/test_srt/ |
data/alignment/*.mp4 + *.reference.srt |
真实音视频 + 人工校对参考字幕(大体积 mp4 已 gitignored) | tests/shared/data/alignment/ |
scripts/data/translate_eval/*.jsonl |
翻译模型评测集(评测脚本专用) | scripts/ |
真实模型集成测试
使用真实模型/服务 + 真实素材验证端到端行为;本地缺模型或素材时跳过, 有则必须执行,作为对替身单测的校准。
外部环境状态的两种处理(都不算代码缺陷):
| 状态 | 处理 |
|---|---|
| 未配置密钥、账户余额/配额、限流(HTTP 401/402/403/429)、服务不可达 | tests/shared/llm_service.py 统一识别并跳过 |
| GPU 显存不足 / CUDA OOM | tests/shared/gpu_memory.py 运行时探测并跳过 |
gpu_memory 的两层防护:运行前按权重体量估算(实测 V2 权重 2.87GB → 峰值
增量约 4.1GB,系数 1.45)判断能否跑完;运行中若仍发生 CUDA OOM(临界卡上
的分配碎片化)则转为跳过。fits_with_margin() 进一步区分"显存充裕"与"临界":
充裕时走生产默认的分块路径,临界时退化为整段单次推理,避免整组跳过。
权重版本:测试只使用 V2 权重。V3 已全面停用(均改用 V2),即使留在
盘上也不得被测试使用;判据是 preprocessor_config.json 的 feature_size
(V2=80,V3=128),不依赖目录名。
| 模块目录 | 覆盖内容 |
|---|---|
tests/nodes/test_whisper/ |
真实 faster-whisper V2 模型端到端转写 |
tests/nodes/test_vlm/ |
真实 Ollama glm-ocr(有文字帧 + 无文字帧) |
tests/nodes/test_subtitle_ocr/ |
真实 14236 帧清单重组装一致性 |
tests/nodes/test_llm/ |
真实 LLM 翻译(专名不硬译) |
tests/nodes/test_llm_filter/ |
真实 OCR 输出规则层回归(保留 863 / 删除 803 基线) |
tests/nodes/test_subtitle_correction/ |
真实 LLM 误听泛化 |
tests/shared/test_alignment/ |
真实语音转写 vs 人工参考字幕的时间对齐量化 |
功能模块覆盖清单
规则要求追求功能性代码覆盖率 100%、每个独立功能模块必须被测试覆盖 (不追求行覆盖率 100%,大模块内辅助函数由主功能用例顺带覆盖即可, 完整表述见 ../AGENTS.md)。 本表是新增/修改模块时的核对清单:新增独立功能模块必须同时补测试并更新本表。
| 模块 | 职责 | 覆盖测试 | 状态 |
|---|---|---|---|
nodes/echo.py |
示例回显节点 | tests/nodes/test_echo/ |
已覆盖 |
nodes/ffmpeg.py |
提音与 ffmpeg 定位 | tests/nodes/test_ffmpeg/(真实视频) |
已覆盖 |
nodes/whisper.py |
转写、分块、模型解析、清洗入口 | tests/nodes/test_whisper/(含真实模型)+ tests/shared/test_alignment/ |
已覆盖 |
nodes/llm.py |
LLM 翻译节点 | tests/nodes/test_llm/(含真实 LLM) |
已覆盖 |
nodes/ass.py |
SRT → VR 双目 ASS | tests/nodes/test_ass/ |
已覆盖 |
nodes/vlm.py |
单帧 OCR(Ollama) | tests/nodes/test_vlm/(含真实模型) |
已覆盖 |
nodes/frame_extract.py |
抽帧与 crop | tests/nodes/test_frame_extract/(真实视频 + 帧号边界) |
已覆盖 |
nodes/subtitle_ocr.py |
逐帧 OCR → 汇总 SRT、断点/暂停 | tests/nodes/test_subtitle_ocr/(14236 帧) |
已覆盖 |
nodes/adaptive_pool.py |
自适应并发线程池 | tests/nodes/test_adaptive_pool/ |
已覆盖 |
nodes/llm_filter.py |
字幕规则/LLM 两级过滤 | tests/nodes/test_llm_filter/ |
已覆盖 |
nodes/subtitle_cleanup.py |
幻觉与短呻吟清洗 | tests/nodes/test_subtitle_cleanup/ |
已覆盖 |
nodes/subtitle_correction.py |
字幕领域纠错 | tests/nodes/test_subtitle_correction/ |
已覆盖 |
nodes/proper_nouns.py |
专名/隐语规则表 | tests/nodes/test_proper_nouns/ |
已覆盖 |
nodes/srt.py |
SRT 解析/序列化/时间戳 | tests/nodes/test_srt/ |
已覆盖 |
nodes/vad_profiler.py |
VAD 信号分析与评分 | tests/nodes/test_vad_profiler/ |
已覆盖 |
wov_app/registry.py |
节点注册表(唯一调用入口) | tests/app/test_registry/ |
已覆盖 |
wov_app/scheduler.py |
DAG 调度与断点续跑 | tests/app/test_scheduler/ |
已覆盖 |
wov_app/batch.py |
文件夹批量引擎 | tests/app/test_batch/ |
已覆盖 |
wov_app/db.py |
SQLite Repository | tests/app/test_db/ |
已覆盖 |
wov_app/main.py |
应用装配与生命周期 | tests/app/test_main/ |
已覆盖 |
wov_app/routers/apps.py |
用户端 API | tests/app/test_routers/test_apps_api.py |
已覆盖 |
wov_app/routers/workflows.py |
管理端工作流 API | tests/app/test_routers/test_workflows_api.py |
已覆盖 |
wov_app/routers/batch.py |
批量任务 API | tests/app/test_routers/test_batch_api.py |
已覆盖 |
wov_app/seed.py |
内置工作流加载 | tests/app/test_seed/ |
已覆盖 |
wov_app/maintenance.py |
孤儿数据清理 | tests/app/test_maintenance/ |
已覆盖 |
wov_app/storage.py |
原子复制 | tests/app/test_storage/ |
已覆盖 |
wov_app/config.py |
路径与环境配置 | tests/app/test_config/(子进程验证真实解析) |
已覆盖 |
wov_app/logging.py |
日志装配 | tests/app/test_logging/ |
已覆盖 |
wov_app/schemas.py |
请求模型(Pydantic) | tests/app/test_main/(schema 用例) |
已覆盖 |
wov_sdk/models.py |
协议数据模型 | tests/sdk/test_models/ |
已覆盖 |
web/assets/crop.js |
框选几何换算 | tests/web/test_crop/(真实 node 执行) |
已覆盖 |
web/assets/batch.js |
批量页明细/进度渲染 | tests/web/test_batch/(真实 node 执行) |
已覆盖 |
scripts/fix_zombie_batch_jobs.py |
僵尸批量任务诊断与修复 | tests/scripts/test_fix_zombie_batch_jobs/ |
已覆盖 |
tests/shared/srt_entries.py |
按秒解析 SRT(测试公共设施) | tests/shared/test_srt_entries/ |
已覆盖 |
tests/shared/realdata_contract.py |
真实数据契约与对齐量化 | tests/shared/test_alignment/ |
已覆盖 |
tests/shared/env_isolation.py |
环境/临时目录隔离 | 被 tests/app/test_config 等间接覆盖 |
已覆盖(间接) |
核对方式(查看某模块是否真被执行):
uv run pytest tests/ -q --cov=wov_app --cov-report=term-missing
重构历史(已完成)
按新规则对 tests/ 做了一次完整重写:旧平铺测试(40 个文件、约 10900 行)
已删除,旧内容仅作为参考与测试数据来源;现有测试全部按模块目录重写。
重构期间发现并修复的真实缺陷:
| 缺陷 | 发现方式 | 修复 |
|---|---|---|
parse_srt 在相邻条目缺少空行时把下一条时间轴吞进正文(静默错位) |
tests/nodes/test_srt/ 的红灯用例 |
正文行遇时间戳行即报错(nodes/srt.py) |
scheduler._file_size 只捕获 OSError,含 \\x00 的产物 URI 抛 ValueError 导致任务误判失败 |
tests/app/test_batch/ 端到端用例 |
同时捕获 ValueError(src/wov_app/scheduler.py) |
生产节点 subtitle_correction 依赖测试包 tests.realdata_contract 解析 SRT |
模块化拆分时发现 | 改用生产模块 nodes/srt.py 的严格解析器 |
相关文档
- 测试规则(模块边界、三段结构、功能覆盖、TDD):../AGENTS.md
- 缺陷回归要求与验收标准:代码审查问题跟踪.md
- 模型/参数选择的实验依据:decisions.md