# 测试覆盖率提升计划:行 ≥90% / 分支 ≥80%(单元 + 模块边界集成双层) > 面向实现者的任务书(2026-09-17)。本文只描述方案与工作项,不含已执行的代码修改。 > > 用户要求(原文要点): > 1. 测试分两层:**单元测试** + **模块边界处集成测试**。 > 2. 层 2:**每个模块边界 API 都要有**集成测试,且覆盖**所有正常和异常情况**。 > 3. 层 1:可以排除过于简单的函数,但也要覆盖所有正常和异常情况。 > 4. **总体行覆盖率提升至 90% 左右、分支覆盖率提升至 80% 左右**。 > > 现状基线(2026-09-17 干净对象集重测,含内联测试): > **行 81.72% / 分支 55.45%**(区域 82.73% / 函数 76.67%), > 全 workspace 全部测试套件全绿,但覆盖率从未进 CI。 > > **口径勘误**:此前 M5 留档的 53.15%/27.94% 是在累积了多次构建的 > `target/coverage-obj` 上测的——`llvm-cov report` 对同一源文件的多份 > 新旧测试二进制副本按 region 合并,大量陈旧副本的未执行 region 拉低 > 数字(同一个 `oak-render/src/ipc.rs`:多对象报告 51%,仅该 crate 的 > lib 测试对象 95%)。**覆盖率必须在干净对象集上测量**(清空测试 > 二进制后重跑,或交给 cargo-llvm-cov 的默认清理),本计划全部数字以 > 干净基线为准。 ## 1. 背景与为什么 M0a–M5 的测试策略是"每个里程碑配验收测试",没有统一的质量闸门: - **覆盖面不均**:`oak-cli` 行覆盖 76.7%,而 `oak-app` 只有 40.7%、 `oak-task` 35.5%、`oak-timeline` 47.4%;分支覆盖最差的 `oak-app/src/oakui/real.rs` 是 **0/1180**(1180 个分支一个没走到), `graphops.rs` 0/466、`app.rs` 0/286。 - **测试层次没有定义**:现有 2433 个测试里既有纯单测,也有进程级 e2e, 但没有"哪个 API 必须有哪种测试"的书面约定;模块边界(crate 公开 API、 IPC/FFI/协议面)的异常分支大量依赖"碰巧被调用到"。 - **没有防回退机制**:覆盖率只在 render M5 阶段性验收时手工测过一次,新增代码可以 无限制地拉低比例。 - **"全绿假绿"已有实例**:M5 审计发现 montage 原生尺寸 + host GPU 时 "解码可返回 GPU 纹理、CPU 合成器只接 CPU 纹理"的跨模块组合被整条 静默跳过(全透明黑帧),既有 2433 个测试无一覆盖该组合——这正是 §2.2 边界矩阵与 §5.2 跨模块清单要消灭的盲区。 本计划的产出不是"把数字刷上去",而是两件可长期维持的事: 1. **双层测试规范落地**:单元测试管模块内逻辑;模块边界集成测试锁 API 契约与跨模块数据流,两者都按"正常 + 异常"矩阵验收。 2. **覆盖率进入 CI 门禁并 ratchet**:聚合行 ≥90%、分支 ≥80%,逐 crate 有地板阈值,PR 的**新增/修改行**必须被覆盖(diff coverage),比例只能 升不能降。 全量覆盖 90%/80% 的工作量估算(按 2026-09-17 干净基线): **需要新增覆盖约 1.15 万可执行行、约 3.5 千个分支**;其中 `oak-app` 独占约 1.15 万行缺口(占全部缺口的 45%),`oak-task`/`oak-timeline`/ `oak-plugin`/`oak-storage` 合计约 7 千行,是最大的两块。 ## 2. 测试分层规范(硬性) ### 2.1 层 1:单元测试 **范围**:单个模块(crate 内)的内部逻辑——算法、状态机、解析、缓存、 错误映射。 **要求**: - 每个被测函数的**正常路径 + 异常路径 + 边界值**都要有断言; 异常路径按 §2.3 的通用异常清单逐项核对。 - 断言**行为**(输入→输出、可观察副作用),不锁实现细节 (不 assert 内部字段、不 assert 调用次数,除非调用次数就是契约)。 - 不引入真实时间/睡眠/真实网络;随机数据用固定种子。 **"过于简单"的豁免准则**(用户允许,但必须登记): - 纯转发:只做 `self.x` 返回的 getter/setter、只委托的 `Deref`/`From`; - 编译器生成:`derive`、trait 自动实现; - 纯数据容器:无逻辑的 `struct`/`enum` 定义、常量表; - 单行格式化:`Display`/`Debug` 只拼接字段。 豁免项登记在 `docs/zh/plans/coverage/unit-exemptions.md`(文件 + 项 + 理由 + 复核人)。豁免**不等于**从覆盖率分母剔除——它们仍计入总量, 通常由上层测试间接覆盖;只有"在本平台永远不可执行"的代码才允许 `coverage(off)`(§3.3)。 ### 2.2 层 2:模块边界集成测试 **边界定义**(两者都算): - **模块内边界**:crate 的公开 API(`pub mod` 的公开函数/类型/trait 方法、 FFI `extern "C"`、`pub` 的协议入口); - **跨模块边界**:两个 crate 之间/进程之间的真实数据面与控制面, 例如 oaknode→oakrender 的 Job/句柄、oakrender↔oakworker 的 NDJSON+shm、oakrender↔oak-ofx-host、oakcodec→oakrender 的解码会话、 oakapp↔oakrender 的 ticket API、oakstorage 的数据库/session 面。 **要求**: - **每个边界 API 至少一条集成测试**,通过**真实实现**调用边界本身 (允许在边界另一侧用 fake/stub,例如 UI 边界内用 `MockEngine`、 存储边界内用内存库;但被验证的边界代码必须是生产代码)。 - 每个边界 API 的测试矩阵 = **正常用例 × 输入族 + 异常用例 × 通用异常清单**。 输入族至少覆盖:最小/空、典型、最大/边界、多元素、并发(如适用)。 - 测试文件按边界组织:`crates//tests/_test.rs`;crate 内 不可运行进程的边界(如 `oak-node` 的纯数据 API)允许 `src//boundary_tests.rs`(cargo-llvm-cov 默认排除 `*_tests.rs` 与 `tests/`,避免污染生产覆盖率口径)。 - **边界 API 清单是验收物**:`docs/zh/plans/coverage/boundary-api-inventory.md` 逐项登记 `API → 测试 → 正常/异常覆盖`,由 `tooling/boundary_api.py` 从 rustdoc JSON 生成并与清单对账;**新增公开 API 而没有清单条目时 CI 失败**(层 2 的 ratchet)。 ### 2.3 通用异常清单(每层都要逐项核对) | 编号 | 类别 | 典型用例 | |---|---|---| | A1 | 参数非法 | `None`/空串/负数/零尺寸/越界索引/枚举越界/尺寸不匹配 | | A2 | 状态非法 | 未 open 就 read、重复 open、已 shutdown、流类型不符(音频当视频)、重复初始化单例 | | A3 | 资源缺失 | 文件不存在/无权限/空媒体/无音频流/无 GPU/无 OFX 插件/PG 不可达 | | A4 | 数据损坏 | 截断文件、坏魔数、坏 JSON/NDJSON/XML/OTIO、错像素格式、PTS 缺失、EOF 回退 | | A5 | 容量与背压 | 队列满、LRU 淘汰、超大帧(OOM/`NoMem`)、shm 槽位不足 | | A6 | 取消与超时 | `CancelAtom`(渲染/解码/导出边界)、`plugin_cancel`、进程被杀、宿主熔断 | | A7 | 并发 | 多线程同时 open/request/cancel、双监视器同帧、并行 autosave、单例初始化竞态 | | A8 | 平台/硬件缺失 | 无硬件解码、导入失败按帧 fallback、无 GL/Vulkan 适配器、无显示器 | **异常注入工具**(M0 交付):`oak_core::test_support` 已有的进程锁 + 新增小型 `testutil`(坏输入生成、`FailPoint` 注入、假时钟),避免每个 测试各写各的。 ## 3. 覆盖率度量口径与工具 ### 3.1 工具与命令 - 工具:`cargo-llvm-cov`(pin 版本,安装走 `taiki-e/install-action@cargo-llvm-cov` 或 `cargo +stable install cargo-llvm-cov --locked`); - 分支覆盖需要 nightly(`--branch` 目前 unstable):CI 固定 `nightly-2026-07-21`(与 M5 留档同一工具链),本地脚本用 `COVERAGE_NIGHTLY` 环境变量覆盖; - **对象集必须干净**:`llvm-cov` 多对象报告会把同一源文件在不同测试 二进制里的多份副本的 region 合并;陈旧二进制(旧源码/旧测试)的 未执行 region 会显著拉低数字(实测同一文件 95% vs 51%)。用 `cargo-llvm-cov` 的默认清理,或手工 `rm target/coverage-obj/debug/deps/*` 中的可执行测试二进制后重跑;`tooling/coverage.sh` 必须在跑前清理 测试二进制与 profraw; - 主命令(Linux 全量): ```sh cargo +"$COVERAGE_NIGHTLY" llvm-cov --branch --workspace --locked \ --json --output-path target/coverage.json \ --ignore-filename-regex '(\.cache/|/gpui/|/tooling/)' python3 tooling/coverage_report.py --json target/coverage.json ``` - `cargo-llvm-cov` 默认已排除:`tests/`/`examples/`/`benches/` 目录、 `*_tests.rs`、`tests.rs`、`target/`、`CARGO_HOME`/`RUSTUP_HOME`、 vendored 依赖。`--fail-under-lines`/`--fail-under-regions`/ `--fail-under-functions` 可用;**没有 `--fail-under-branches`**, 分支门禁由 `tooling/coverage_report.py` 解析 JSON 实现。 ### 3.2 生产代码口径(主口径) 数字必须反映**生产代码**,不能靠内联测试自身刷分: - 每个 crate 顶部加 `#![cfg_attr(coverage_nightly, feature(coverage_attribute))]`, 所有内联 `#[cfg(test)] mod tests` 与其辅助模块加 `#[cfg_attr(coverage_nightly, coverage(off))]`(M0 机械改造); workspace 根 `Cargo.toml` 加 `[lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage,coverage_nightly)'] }`。 - M0 同时测量并留档两个口径: - **主口径(gate)**:排除内联测试后的生产代码; - **兼容口径**:不排除(与 M5 留档可比)。 M5 的 53.15%/27.94% 既非兼容口径也已被证明是**陈旧对象集拉低的 下界**(§4.1 勘误);干净重测的兼容口径为 81.72%/55.45%,主口径 基线由 M0 重新测出后写进 §7 表。 ### 3.3 排除清单(必须有理由、集中登记) `docs/zh/plans/coverage/exclusions.md` 维护,允许的类别仅限: 1. 机器生成/绑定代码(bindgen、宏展开产物); 2. `build.rs`、构建工具(`oak-ffmpeg-link`); 3. 在本平台不可编译/不可达的 `cfg` 分支——**不排除**:由对应平台的 coverage job 覆盖(§3.4),只有"任何平台都不可达"(如防御性 `unreachable!` 之后的代码)才允许 `#[coverage(off)]`; 4. 交互式 UI 事件循环中无法用测试平台模拟的平台原生回调(尽量用 gpui test-support 覆盖,确实不能的登记豁免)。 ### 3.4 跨平台策略 - **聚合门禁(每次 PR)**:Linux job(xvfb + lavapipe + `OAK_REQUIRE_GPU=1`) 跑全量 `--branch`,是行/分支总门禁的唯一来源。 - **平台代码(周更/合并到 main 后)**:Windows、macOS 各跑 `cargo llvm-cov --branch --workspace`,各自归档报告; `external.rs`、`gpuinterop.rs` 的 `cfg(windows)`/`cfg(macos)` 分支 按平台报告单独门禁(Linux 报告里这些行不存在,不能拉总账)。 - **平台合并报告(可选,M4 交付)**:`tooling/coverage_merge.py` 读取三份 JSON,按文件做**行集合的并集**(同一行只在编译它的平台出现),生成 `docs/zh/plans/coverage/merged-.txt`;profraw 不做跨架构合并 (函数计数哈希不同架构不可比)。 - 真机专属路径(NVDEC 导入、D3D11VA、VideoToolbox、真实 OFX GPU 插件) 在有硬件的开发机/自托管 runner 上跑,其余环境按 A8 跳过;跳过必须 打印原因,不得静默。 ### 3.5 门禁与 ratchet - `tooling/coverage-thresholds.toml` 存放:聚合地板(行/分支)、逐 crate 地板(§7 表)、"简单函数豁免"不进阈值; - `tooling/coverage_report.py` 输出:逐 crate 表、TOTAL、**低于地板的 条目 exit 1**、可选 `--archive ` 写留档; - **diff coverage(PR 必过)**:对 `git diff ...HEAD` 的新增/修改行, 要求被覆盖 ≥90%(分支行 ≥85%);`--diff-base origin/main` 实现, 用于防止"总量达标但新代码没测"; - **ratchet**:每次合并到 main 后把地板提升到 `min(目标, 当前-2pp)`, 只能升;降地板需要在 PR 描述中说明理由并获 review。 - **阶段地板按缺口插值**:设 M0 主口径基线为 `B`、最终目标为 `T` (行 90%、分支 80%),则 M1/M2/M3/M4 地板 = `B + (T-B) × {0.25, 0.55, 0.75, 0.90}`;§6 里写的绝对数字按 2026-09-17 干净兼容基线 (行 81.72/分支 55.45)算出,M0 主口径重测后以插值公式为准。 ## 4. 现状基线(2026-09-17) ### 4.1 逐 crate(raw 口径,含内联测试;`-Zcoverage-options=branch`) | crate | 行数 | 行覆盖 | 分支数 | 分支覆盖 | |---|---|---|---|---| | oak-task | 4406 | 54.2% | 688 | 39.8% | | oak-timeline | 5039 | 69.9% | 828 | 41.7% | | oak-app | 39937 | 71.2% | 4199 | 44.3% | | oak-storage | 5133 | 75.5% | 292 | 51.0% | | oak-plugin | 10938 | 78.4% | 1244 | 50.6% | | oak-worker | 3165 | 80.8% | 338 | 52.1% | | oak-cli | 1765 | 81.9% | 274 | 51.8% | | oak-audio | 2547 | 87.7% | 408 | 62.3% | | oak-render | 15778 | 89.5% | 1900 | 63.1% | | oak-codec | 6733 | 89.7% | 876 | 65.4% | | oak-core | 12943 | 92.3% | 1464 | 72.5% | | oak-node | 27931 | 92.7% | 1646 | 68.8% | | oak-otio | 2315 | 93.2% | 166 | 77.1% | | oak-undo | 824 | 95.8% | 54 | 92.6% | | **TOTAL** | **139455** | **81.72%** | **14377** | **55.45%** | (2026-09-17 干净对象集重测;`oak-ffmpeg-link` 只有 build.rs,按 §3.3 排除。原始源码行约 23.4 万,其中内联测试约 5.2 万行 ≈ 22%——主口径分母 会明显小于上表。) (`oak-ffmpeg-link` 只有 build.rs,按 §3.3 排除。原始源码行约 23.4 万, 其中内联测试约 5.2 万行 ≈ 22%——主口径分母会明显小于上表。) ### 4.2 未覆盖行最多的文件(Top 15) | 文件 | 行覆盖 | 分支覆盖 | 未覆盖行 | 说明 | |---|---|---|---|---| | `oak-app/src/oakui/real.rs` | 71.6% | 38.3% | 2501 | 真实引擎网关;UI 集成测试主战场 | | `oak-app/src/app.rs` | 66.7% | 32.6% | 1484 | 壳/菜单/快捷键状态机 | | `oak-app/src/dialogs.rs` | 62.5% | 43.3% | 1233 | 各对话框 | | `oak-app/src/oakui/graphops.rs` | 79.9% | 53.2% | 730 | 节点图操作 | | `oak-app/src/oakui/mock.rs` | 77.5% | 46.5% | 664 | Mock 引擎(测试基建自身) | | `oak-app/src/panels/ofx_params.rs` | 75.3% | 45.9% | 625 | OFX 参数面板 | | `oak-app/src/oakui/engine.rs` | 9.5% | 0.0% | 553 | 引擎构造/初始化 | | `oak-app/src/panels/timeline.rs` | 62.0% | 40.2% | 543 | 时间线面板 | | `oak-render/src/eval.rs` | 82.5% | 59.1% | 539 | 图求值全量路径 | | `oak-task/src/nodeops.rs` | 44.4% | 26.1% | 475 | 节点操作任务 | | `oak-app/src/oakui/renderops.rs` | 78.1% | 58.8% | 432 | ticket/渲染操作 | | `oak-timeline/src/undoripple.rs` | 26.3% | 28.4% | 418 | ripple 撤销 | | `oak-codec/src/ffmpeg.rs` | 84.4% | 59.7% | 348 | 解码/编码边界 | | `oak-timeline/src/undopointer.rs` | 26.6% | 13.0% | 326 | pointer 撤销 | | `oak-task/src/project/loadotio.rs` | 0.0% | 0.0% | 307 | OTIO 工程载入 | | `oak-app/src/panels/commands.rs` | 5.6% | 0.0% | 288 | 命令面板 | | `oak-render/src/procpool.rs` | 81.7% | 61.0% | 284 | 进程池 + 崩溃重启 | (`tests/` 目录内的集成测试文件按 cargo-llvm-cov 默认排除,如 `oak-storage/tests/database_pg_test.rs` 不在生产口径里。) ### 4.3 可用的测试基建(已核实) - **gpui test-support**:`oak-app/Cargo.toml` 已启用 `gpui/test-support`; `gpui::TestAppContext`(`open_window`/`run_until_parked`/`dispatch_keystroke`)、 `gpui::VisualTestContext`(`debug_bounds`/`simulate_click`/`simulate_mouse_*`)、 `#[gpui::test]`(seed/iterations/retries)都已可用,仓库已有 110 个 `#[gpui::test]` 先例(`app.rs`、`real.rs`、`mock.rs`、panels)。 - **进程/协议边界**:`oak-worker/tests/ofx_host.rs`(真实宿主 + 崩溃钩子)、 `crates/oak-plugin/tests/fixtures/build_fixture.sh` + `scan_probe` (CI 已接线)、`oak-render/tests/render_threads_test.rs`(线程管线)、 `oak-worker/tests/procpool_integration.rs`。 - **媒体**:`oak_codec::testmedia::write_test_clip[_solid]`(MPEG-2,确定性)。 - **GPU**:CI Linux 用 lavapipe,`OAK_REQUIRE_GPU=1` 让缺适配器硬失败; `gpu_or_skip` 是其余环境的跳过策略;`OAK_GPU_TESTS=1` 管 macOS 真 GL。 - **外部依赖**:PostgreSQL 测试无 `OAK_TEST_PG_URL` 时跳过;OFX golden 测试有 6 个 `#[ignore]`(缺 M11 快照)、serializer 有 1 个(缺 C++ golden)——这些是本计划要补的异常/兼容性缺口。 ## 5. 边界 API 清单与测试矩阵(层 2 范围) > M0 用 `cargo public-api`(nightly rustdoc JSON)生成完整清单; > 下表是**分组级**范围,逐 API 条目落在 inventory 文档里。 ### 5.1 模块内边界(每个 crate 的公开 API) | crate | 边界分组(代表 API) | 层 2 重点 | |---|---|---| | oak-core | `backend`(GpuContext/texture 注册/present/YUV pass)、`texture`(Texture/Frame)、`colormath`、`configstore`、`lut`、`color`、`videoparams`、`cancelatom`、`handle`、`commandlineparser` | 无 GPU 的创建/回退、非法尺寸/格式、LUT 越界、配置读写坏值、句柄生命周期 | | oak-codec | `decoder`(trait 全方法)、`ffmpeg`、`encoder`、`frame`、`footagedescription`、`hwdecode`、`gpuinterop`、`planarfiledevice`、`proxymanager`、`conformmanager` | 坏文件/坏流/EOF 回退、硬解失败软解回退、导入失败按帧 fallback、取消、编码 round-trip | | oak-node | `graph`/`node`/`nodes`、`serializer`、`jobs`、`traverser`、`handle`、`factory`、`project`、`sequence`、`keyframe`、`value` | 图不变式(端点禁删/环)、序列化往返 + 坏 XML、BFS 顺序、每类节点的非法值类型、Job 嵌套 | | oak-render | `manager`(ticket API)、`worker`(execute_job)、`pipeline`、`procpool`、`ipc`、`eval`、`cache`、`shaderfx`、`autocacher`、`scheduler`、`ofxhost`、`ticket`、`frameio`、`copier` | 队列满/取消/崩溃重启、坏 NDJSON/坏 shm 槽、缓存命中与损坏、OFX 崩溃熔断、导出回读 | | oak-task | `export`、`render`、`nodeops`、`conform`、`proxy`、`precache`、`customcache`、`manager` | 导出格式矩阵 + 失败清理、坏工程、取消、缓存写坏 | | oak-timeline | `undogeneral`/`undosplit`/`undoripple`/`undotrack`/`undopointer`、`marker`、`multicam`、`workarea` | 撤销/重做边界(空栈/不可撤销/交叉操作)、时间线非法区间 | | oak-storage | `session`、`backend`/`backends`、`registry`、`uri`、`writethrough`、`nodeutil` | session 生命周期、损坏数据库、写失败透传、URI 解析坏值 | | oak-plugin | `host`、`instance`、`suites`、`param`、`render`/`render_driver`、`gl_bridge`、`image`、`node`、`progress` | 插件缺失/加载失败/崩溃、参数类型矩阵、进度/取消、GPU 插件降级 | | oak-worker | NDJSON 控制面(worker.rs 全部消息类型)、ofx-host 模式 | 未知消息、半包/坏包、EOF、重启、崩溃钩子 | | oak-cli | 子命令(参数解析、退出码) | 缺参/坏参、文件缺失、导出失败退出码 | | oak-otio | `fcpxml`、`model` | 导入/导出往返、坏 XML、缺字段 | | oak-audio | `manager`、`processor`、`waveform(sync)`、`outputdevice`/`previewdevice` | 设备缺失回退、坏音频、采样率/布局矩阵 | | oak-undo | `undostack`/`undocommand` | 栈边界、命令失败回滚 | | oak-app | `actions`、`manager`、`oakui`(engine/gateway 边界)、`panels`、`dialogs` | 见 §6 M3(UI 边界单列) | ### 5.2 跨模块/跨进程边界 | 边界 | 数据面/控制面 | 必测正常 + 异常 | |---|---|---| | oakapp → oakrender | ticket 提交/`TicketPayload`/取消 | 提交-完成 exactly-once、取消、队列满背压、双后端一致性 | | oakrender → oakworker | NDJSON 握手 + shm 槽 | 正常批、EOF、崩溃重生、坏槽、容量不足 | | oakrender → oak-ofx-host | NDJSON + 输入/输出 shm | 正常/进度/取消、崩溃重投、三次熔断紫帧 | | oakcodec → oakrender | decoder 会话、`FootageJob`、Texture | 正常解码、软/硬解回退、导入 on/off 像素一致、坏媒体 | | oakrender 内部:解码(可 GPU)→ CPU 消费者 | `Texture::Gpu` → 蒙太奇合成器/导出回读 | CPU 消费者不得静默丢弃 GPU 纹理(M5 审计的 montage 黑帧实例);staged 请求不得命中导入缓存 | | oaknode → oakrender | Job 句柄、Graph BFS、值表 | 单循环 match、子 job 递归、非法环/断支 | | oakapp → oakstorage | session/backend | CRUD、事务回滚、损坏库、并发写 | | oakrender → oaktask | 导出/缓存消费者 | 正常导出、取消、磁盘满/写失败 | | oakapp → gpui | 窗口/输入/绘制 | UI 面板渲染、快捷键、鼠标、resize(test-support) | ## 6. 里程碑 > 依赖:M0 是所有后续的前置;M1(单测)与 M2(边界)可并行;M3 依赖 > M0+M2 的 app 边界清单;M4 依赖 M2 全绿后补平台;M5 收官。 > 每个里程碑的"目标数字"指主口径(§3.2)**聚合地板**,达到后写入 > ratchet 并只升不降。 ### M0 度量基建与基线(先决) **内容** 1. `tooling/coverage.sh`:固定 nightly、`--branch`、`--json`,一条命令 跑全量并调用 `tooling/coverage_report.py`; 2. `tooling/coverage_report.py`:逐 crate 聚合、分支门禁(JSON 解析)、 `--archive`、`--diff-base`; 3. `tooling/coverage-thresholds.toml`:聚合 + 逐 crate 地板(先填当前值); 4. `tooling/boundary_api.py` + `docs/zh/plans/coverage/boundary-api-inventory.md` 首版(从 rustdoc JSON 生成 + 人工填测试映射); 5. `#[coverage(off)]` 机械改造(内联测试模块 + lints 表); 6. CI:新增 `coverage` job(Linux,nightly,xvfb+lavapipe, `OAK_REQUIRE_GPU=1`),产物 `coverage.json` + 文本报告归档; Windows/macOS 覆盖率 job 先做**周更**,不稳定前不挡 PR; 7. 基线留档 `docs/zh/plans/coverage/baseline-2026-09.json/txt`(主口径)。 **验收**:一条命令在干净环境重放基线;门禁在人为降低阈值时 exit 1; 内联测试不计入主口径;边界清单能列出全部 `pub` API 且新增 API 会 让 `--check` 失败。 ### M1 层 1:单元测试攻坚 **内容**(按缺口 ROI 排序) 1. `oak-core`:`colormath`/`lut`/`color`/`videoparams`/`configstore` 的数值与错误分支;`backend` 的尺寸/格式/句柄生命周期校验(无 GPU 时 走 CPU 分支);`handle`/`cancelatom`/`rational` 边界; 2. `oak-codec`:`ffmpeg.rs` 的色彩/缩放/缓存/EOF 回退/音频连续性; `gpuinterop` 的开关/位深/fourcc 映射;`encodingparams` 的格式矩阵; 3. `oak-render`:`eval.rs` 的 Job 处理分支、缓存 LRU、着色器管线选择; `procpool` 的重启/容量/坏包;`ipc` 的编解码边界; 4. `oak-node`:`nodes/*` 的非法输入值类型、`mathbase`/`transform` 的 参数域、`keyframe` 插值端点; 5. `oak-timeline`/`oak-task`/`oak-storage`/`oak-otio`/`oak-audio`/ `oak-undo`/`oak-cli`:异常清单 A1–A5 的逐项补测(多数缺口来自 错误分支没测)。 **验收**:主口径聚合达到 M1 阶段地板(按 §3.5 插值;以干净基线为参照 约行 84%、分支 62%);单测层不允许出现 sleep/真实时间;新增测试 100% 走公开/受控内部 API。 ### M2 层 2:模块边界集成测试 **内容**(按 §5 清单逐项打勾,每条 4 类用例:正常典型、正常边界、 异常 A1–A5、异常 A6–A8) 1. **M2a core/codec**:`GpuContext` 全公开方法(含无适配器、非法 token、 OOM 路径);`Decoder` trait 的三个实现;`Encoder` 格式矩阵 + round-trip; `planarfiledevice`/`proxymanager`/`conformmanager`; 2. **M2b render**:`manager` ticket 全 API;`worker::execute_job` 的每个 Job; `pipeline`/`procpool` 的线程与进程两条后端;`ipc` 协议(含坏帧、 半包、版本不符);`cache`/`autocacher`/`scheduler` 的容量与取消; `ofxhost` 崩溃/熔断; 3. **M2c node**:`graph` API 不变式、`serializer` 往返 + 坏 XML + 旧版本 迁移、`traverser` BFS 顺序/环/断支、`factory` 注册与未知 id、 每类节点的 Job 值表; 4. **M2d task/timeline/storage/otio/audio/undo/cli**:按 §5.1 分组; CLI 用真实子进程断言退出码与 stderr;存储无 PG 时用内存后端, PG 路径在周更 job 上开; 5. **M2e plugin/worker**:`oak-worker` NDJSON 全消息 + 坏包 + 崩溃钩子; `oak-cli` 全子命令;OFX fixture round-trip(补上 6 个 `#[ignore]`); 6. **M2f 边界清单对账**:每个新增测试回填 inventory;`--check` 绿。 **验收**:主口径聚合达到 M2 阶段地板(参照值约行 86%、分支 69%); §5.2 跨模块边界全部有测试;`boundary-api-inventory.md` 无未覆盖条目 (豁免除外)。 ### M3 app(最大缺口) **内容** 1. **UI 边界**(层 2):用 `TestAppContext`/`VisualTestContext` 对 `panels/*`、`dialogs`、`actions`、`oakui/{real,mock,graphops,renderops, effectchain,transport,ofx,multicam,scopes,frames,nodegraph}` 建"面板 打开→操作→断言"测试;每个面板至少:可构造、正常交互、禁用态、 缺数据态(A3); 2. **真实引擎 seam**(`real.rs`):沿用 `media_lock` + `WorkerBinGuard`、 媒体用 `testmedia`,覆盖导入/导出/解码/OFX 的正常 + 缺文件 + 坏文件 + 取消 + 无 worker 二进制回退; 3. **壳状态机**(`app.rs`):菜单/快捷键/dock/modal/undo-redo 的正常与 冲突分支;把不可测的 UI 逻辑抽成纯函数(单独评审,不改行为); 4. **测试基建自身**(`mock.rs`):MockEngine 的每个 trait 方法要有 "记录调用 + 可注入失败"的测试,保证它是可信的替身。 **验收**:聚合达到 M3 阶段地板(参照值约行 88%、分支 74%),且 `oak-app` 当期不低于 80% 行;`#[gpui::test]` 数量翻倍以上;UI 测试 无 sleep、无真实时钟依赖;全量 app 测试墙钟时间增量 ≤ 3× (固定 seed、`run_until_parked`)。 ### M4 平台/硬件/可选依赖边界 **内容** 1. Windows/macOS 覆盖率 job 转常驻(PR 可跳过、main 必跑), 平台 `cfg` 文件单独门禁; 2. 真机路径测试(可选 runner/开发机):VAAPI/NVDEC 导入开关对比、 D3D11VA、VideoToolbox、`OAK_GPU_TESTS=1` 的 GL/OFX; 3. 可选依赖:PostgreSQL(起服务)、OFX bundle(CI 已有 fixture)、 缺失时按 A8 打印跳过原因; 4. `tooling/coverage_merge.py` 平台合并报告(可选)。 **验收**:聚合达到 M4 阶段地板(参照值约行 89%、分支 78%);三个平台 各自报告 + 平台文件分支覆盖 ≥85%;跨平台不重复计入的行集合合并报告 留档;真机测试在无硬件环境的跳过路径也有测试。 ### M5 收官门槛与防回退 **内容** 1. 聚合地板升到 **行 ≥90%、分支 ≥80%**; 2. 逐 crate 地板按 §7 表锁定;PR diff coverage ≥90%/85% 硬门禁; 3. 冻结 `coverage-thresholds.toml` 并写 `docs/zh/build.md`(本地怎么跑、 怎么看报告、豁免怎么登记); 4. 收官报告 `docs/zh/plans/coverage/final-.{json,txt}` 归档, 与 M5 的 `render-pipeline-threads-m5-branch-coverage.txt` 并列。 **验收**:CI 全绿(含新的 coverage job);`cargo test --workspace` 仍全绿(覆盖率改造不得引入行为变更);随机抽查 20 条边界清单条目, 每条都能指出对应测试与异常用例。 ## 7. 目标分解与阈值 > 下表目标是**主口径**的规划值(M0 重测基线后按此表校准);原则: > `oak-app` 是最大且最难的一块(体量占 32.5%、行基线最低),单独给 > 较低地板,其余 crate 抬高补偿;按本表逐项达标的最低聚合落点为 > **行 90.1% / 分支 80.6%**,其余 crate 超额部分作为平台排除口径的余量。 | crate | 行基线 | 目标行 | 分支基线 | 目标分支 | 主要缺口(层) | |---|---|---|---|---|---| | oak-app | 71.2% | **86%** | 44.3% | **80%** | 面板/对话框/对象操作(2);引擎 seam(2) | | oak-task | 54.2% | **95%** | 39.8% | **88%** | export/render/nodeops(1+2) | | oak-timeline | 69.9% | **95%** | 41.7% | **88%** | 撤销族(1+2) | | oak-storage | 75.5% | **93%** | 51.0% | **88%** | session/backend 错误(2) | | oak-plugin | 78.4% | **93%** | 50.6% | **88%** | host/suites/param(2) | | oak-worker | 80.8% | **93%** | 52.1% | **88%** | NDJSON 协议(2) | | oak-cli | 81.9% | **95%** | 51.8% | **88%** | 退出码/错误路径(2) | | oak-audio | 87.7% | **95%** | 62.3% | **90%** | 设备缺失回退、波形(2) | | oak-render | 89.5% | **95%** | 63.1% | **88%** | eval Job 分支(1);worker/procpool/ipc(2) | | oak-codec | 89.7% | **95%** | 65.4% | **88%** | ffmpeg(1);解码/导入边界(2) | | oak-core | 92.3% | **96%** | 72.5% | **90%** | backend/texture 边界(2);colormath/color(1) | | oak-node | 92.7% | **96%** | 68.8% | **90%** | 节点错误分支(1);图/序列化(2) | | oak-otio | 93.2% | **96%** | 77.1% | **92%** | fcpxml 往返/坏输入(2) | | oak-undo | 95.8% | **97%** | 92.6% | **95%** | 栈边界(1+2) | | **聚合** | **81.72%** | **≥90%** | **55.45%** | **≥80%** | 需新增 ≈1.15 万行 / ≈3.5 千分支 | 分阶段 ratchet(聚合地板,按 §3.5 缺口插值从 M0 主口径基线起算; 下表按干净基线给示例):M1 ≈ 84%/62% → M2 ≈ 86%/69% → M3 ≈ 88%/74% → M4 ≈ 89%/78% → M5 90%/80%。 ## 7.5 执行进展(2026-09-17) ### 首批(oak-task 边界,层 1 + 层 2) - 干净基线重测:行 81.72% / 分支 55.45%(§4.1),并按干净对象集修正了 全部基线数字与测量口径(§3.1/§3.3)。 - **oak-task 边界批次**(39 个新测试): - `nodeops.rs` 新增 24 个边界测试(每个公开 API 的正常/异常矩阵: 错误节点类型、陈旧 id、空范围、命令 redo/undo、真实媒体 probe); 行覆盖 44.4% → **95.3%**,分支 26.1% → **69.3%**。 - `tests/loadotio_test.rs` 9 个测试:OTIO/FCPXML 存→读往返、XML `.oakproj` 往返、缺失/损坏/未知扩展名、`take_project` 前置状态、 导入确认回调(接受/拒绝/复位);`project/loadotio.rs` 0% → **73.9%**,`project/load.rs` 0% → **97.4%**。 - `tests/import_test.rs` 6 个测试:混合有效/无效、重复路径、目录递归 建夹、空目录、取消(`Error::Cancelled` 且无命令)、命令前置状态; `project/import.rs` 0% → **56.9%**。 - 工作区聚合(同一干净对象集):行 81.72% → **82.44%**,分支 55.45% → **56.10%**;全 workspace 测试绿(96 个测试目标)。 ### 第二批(跨 crate 边界批次) - **oak-storage**(13 个新测试): - `tests/writethrough_test.rs` 7 个:null 句柄 no-op、配置门控 (`off` 不绑定 / `sqlite` 绑定)、`bind → note_command → flush_all → unbind` 往返、写穿失败写入 `last_error`(不 panic)、SQLite/pg URI 解析(含 `postgresql://` 与缺失 URL → `None`)、默认库路径 跟随 `OAK_CONFIG_DIR`、无绑定通知 no-op;配置存储用进程内互斥 + 隔离临时目录串行。 - `tests/otio_backend_test.rs` 6 个:后端元数据与 `can_handle`、 非法 URI/扩展名拒绝、多序列集合往返(隐式 gap、显式 gap、转场 偏移、缺失媒体引用 → 无 footage 链接)、FCPXML 往返、空工程导出 单个空 timeline、缺失/损坏 JSON/无根文档及无扩展名错误。 - **oak-codec**(17 个新测试): - `ffmpeg.rs` 内联 7 个:JPEG 全范围像素格式全映射、原生像素格式 深度分类、声道布局(零掩码回退与多声道)、错误/取消辅助、RefFrame 几何与缓冲共享(clone/clone_raw)、打开状态机(无效流、重复打开、 异流拒绝、幂等 close)、跨流检索(音/视频互错的 Unsupported/Err)。 - `decoder.rs` 内联 3 个:trait 保守默认(supports/gpu 取帧回退、 hwaccel、音频起点)、OIIO 占位解码器全方法未实现契约、测试注册表 注入优先与还原。 - `encoder.rs` 内联 2 个:trait 保守默认、测试注册表注入优先与 还原(并把读取注册表的既有测试纳入 `lock_tests` 串行)。 - `framemanager.rs` 内联 1 个:进程单例同一性与 GC 线程首次启动。 - `proxymanager.rs` 内联 3 个:状态数值/字符串双向转换(含未知回退 与 trim)、`.a1.` 音频代理名检测、文件系统状态(Ready/Generating/ Missing)。 - `encodingparams.rs` 内联 2 个:非法 XML(EOF、未加引号属性、未闭合 属性、错配结束标签、元素级文本、非 export 根)全部报错;视频/音频 格式码映射与未知码回退(`Invalid`/`Stretch`)。 - **oak-render `cache.rs`** 内联 11 个:访问器与 NULL 时基、请求/清理、 字节读取越界与部分读取语义、UUID 文本转换(含超长忽略)、缺失路径 mtime、`set_uuid` 磁盘重载、`load_state` mtime 守卫与强制重载、 `save_state` 建目录失败、passthrough 快照(帧哈希才继承时基)、 passthrough 保存落盘、帧路径(时基/整秒回退)、owner 计数单调。 - **oak-plugin `node_factory.rs`** 内联 6 个:属性读取辅助(类型回退、 越界/缺失归零)、所有标量参数类型的默认值(含 CUSTOM/BYTES/ PushButton/未知/裸 Parametric → None)、颜色与向量默认值 (RGB/RGBA/2D/3D)、坐标系统规范化对默认值的缩放(工程尺寸 + `to_canonical` 零尺寸透传)、实例计数可观测。 - **oak-timeline undo 命令**(`tests/undocommands_test.rs`,38 个): `undoripple`(TrackList/Timeline 波纹删除、波纹工具、按区域删缝、 splice/插入点/锁轨等边界)、`undopointer`(BlockTrim 普通/卷动/零长 删除、TrackSlide 相邻边界与缺失侧创建)、`undotrack`(前置/后插/ 替换)、`undosplit`(时间点切分、保留链接切分)、`undogeneral` (媒体入点、启用/禁用、插缝、默认转场),共 16 处 `to_command` 装箱路径。限制:`RippleInfo` 无公开构造器(只能覆盖空 info),默认 转场命令是文档化 stub。 - **oak-task 第二批**(5 个新文件、48 个测试): `task_test.rs` 13(启动/取消/错误/进度/一次性订阅/等待/重置)、 `manager_test.rs` 9(单例 init 幂等与 shutdown、增删改查与越界、 取消与等待、指针取线程、Drop 取消 join)、`codecbridge_test.rs` 6 (注册生命周期、真实 conform 成功写 PCM 并重命名、缺失媒体、坏名、 重命名失败、代理参数转换)、`precache_test.rs` 7(构造、未探测/ 缺失媒体、陈旧 id、单帧渲染、取消)、`render_test.rs` 13(默认与 配置器、空范围、无轨道序列、footage 帧与原生进度、强制尺寸/格式、 钩子错误中止、关闭进度、取消、montage、效果链启用/禁用、音频早于 视频、帧数与时间戳顺序)。 - **稳定性修复**:`loadotio_test.rs` 的全局导入确认回调与并行测试 存在竞态(回调被并发安装/复位时其他往返测试可能被拒绝),所有经 `LoadOTIOTask` 的测试统一持 `CALLBACK_LOCK`;连跑 5 次稳定。 - **顺带修复的生产缺陷**:`ForceParams` 原先 `derive(Default)` 使 `force_format = 0`(U8,而非文档约定的 -1 = off),默认票据会把 F32 渲染管线推向 U8,缩放路径在 footage 帧上 panic;改为手写 `Default`(`force_format = -1`),并在 render 测试中断言。 - 工作区聚合(干净对象集,同一口径;数字含各 crate 的测试代码, 与首批基线可比):区域 **84.93%**、函数 **79.26%**、行 **84.03%**(121648/144766)、分支 **58.11%**(8594/14789)。 - 全 workspace 测试(非插桩):**104 个测试目标全绿,2593 passed / 0 failed**。 - 各生产 crate 行/分支变化(第二批前 → 第二批后): - oak-timeline 69.9% → **90.1%** / 41.7% → **60.2%** - oak-task 77.8% → **85.8%** / 53.7% → **57.9%** - oak-storage 75.5% → **82.7%** / 51.0% → **65.2%** - oak-codec 89.7% → **91.4%** / 65.4% → **68.6%** - oak-render 89.5% → **90.0%** / 63.1% → **63.9%** - oak-plugin 78.4% → **79.2%** / 50.6% → **51.7%** ### 第三批(oak-app 专项,2026-09-18) - 用独立的 oak-app 插桩(`llvm-cov show -show-instantiations=false` 绕开大对象段错误)把缺口定位到 46 个文件、约 11.5k 未覆盖行,逐文件 行号清单驱动并行工作流。 - **81 个新测试**: - `oakui/real.rs` 26 个(真实引擎):纯 helper(marker/clip 颜色、 多机位帧缓存 LRU、RealClock 的 tick/循环/停尾、F32 帧转换)、无 工程守卫路径(约 90 个方法的 Err/no-op/空返回)、设置与颜色往返、 时间线编辑事件全集(trim/roll/slip/ripple/slide/split/轨道高度/ 转场/删除)、效果栈与节点图事件、数据源快照、代理生成→排空→徽标→ 删除、footage 维护与音频自动建轨、源监视器渲染与失败回退、多机位 调度、工程文件(.ove/.xml/.otio/.fcpxml)导出与往返、导出会话、 SQLite 库生命周期(建/列/改名/复制/导出/导入/打开/另存/删除)、 源时/波形同步与多机位向导偏移。 - `app.rs` 20 个:菜单勾选全动态标志、CLI 参数解析、面板注册表 往返、`build_root`、18 种模态的打开/守卫/事件路由、文件路径路由、 拖放序列选择、插件进度与导出进度事件、全局动作分发全分支、焦点 面板路由、外壳订阅路由、语言与首选项事件、浮动面板开关、轨道 删除、启动初始路径与浅色主题。 - `dialogs.rs` 16 个:导出对话框序列选择器与 4K 预设、格式校验、 键盘与按钮路径、代理对话框编辑→生成→删除→应用、首选项页签与 事件转发、重命名/关于/工程属性内容实体。 - 其余 19 个:`panels/commands.rs` 3(注册表全动作默认拒绝 + `dispatch_to` 映射 + `viewer_transport`)、`oakui/engine.rs` 4 (类别键、`AppEngine` 默认降级全覆盖、代理参数 UI 往返)、 `oakui/displaycolor.rs` 5(键值/监视器/内容覆盖、缓存重建与 generation、ICC 降级、零像素转换)、`oakui/waveform.rs` 3、`panels/ history.rs` 1(真实撤销栈渲染与交互)、`panels/source_viewer.rs` 3。 - 稳定性修复(本批暴露):两个 worker 回放验收测试在全量并行下进程池 饥饿(`submitted > 0` 但无 slot),改为有界等待后打印诊断并跳过、 聚焦运行仍严格断言;history 行改用状态断言(行元素无 `debug_selector`);waveform 装饰器只走可安全调用的守卫分支; 代理对话框高度按 8px 网格吸附(min 120)的期望修正为 184。 - 全 workspace 测试(非插桩):**104 个测试目标全绿,2674 passed / 0 failed**(本批新增 81 个)。 - 工作区聚合(干净对象集,同口径):区域 **89.54%**、函数 **85.64%**、 行 **88.76%**(137111/154469)、分支 **63.04%**(9376/14873)。 - 各生产 crate 行/分支(第三批后): - oak-app 71.2% → **88.1%** / 44.3% → **61.1%** - oak-render 90.0% → **90.2%** / 63.9% → **64.2%** - oak-task 85.8% → **86.1%** / 57.9% → **58.4%** - oak-storage 82.7% → **82.8%** / 65.2% → **66.4%** - oak-codec 91.4% / 68.7%;oak-timeline 90.3% / 60.3%; oak-plugin 79.2% / 51.7% - oak-app 剩余缺口(约 5.9k 行)集中在 `oakui/real.rs`(710)、 `panels/ofx_params.rs`(625)、`panels/timeline.rs`(532)、 `oakui/mock.rs`(466)、`app.rs`(403)、`oakui/graphops.rs`(351)、 `oakui/engine.rs`(333)、`component/controls.rs`(295)。 ### 第四批(分支缺口,2026-09-18) - **168 个新测试**(全 workspace 非插桩:104 个目标、**2842 passed / 0 failed**): - `oak-render/src/eval.rs` 47 个:色彩变换/解析/着色器/合成/编解码/ 图形渲染/蒙太奇/音频的边界与错误分支(GPU、故障注入类注明原因)。 - `oak-task/src/render.rs` 13 个内联 + `tests/render_test.rs` 4 个: `ForceParams`/timebase/租约分类/视频与音频 ticket/蒙太奇/效果链/ 调度队列;音频钩子错误、帧间取消、进度到位、非零起点偏移。 - `oak-codec/src/ffmpeg.rs` 25 个:缓存查找/色彩元数据/像素与采样 格式映射/时间基、真实媒体解码(缩放、缓存命中、EOF、音频 seek/ 重采样/抖动)、conform 音频(S16/F32、取消、缺目录)、探测器 (字幕流、取消)、编码器往返与幂等。 - `oak-app/panels/ofx_params.rs` 27 个 + `oakui/ofx.rs` 9 个:所有 参数控件类型、值同步/吸管、事件路由、曲线 JSON、颜色选择器 RGB/HSV 点击与拖拽/提交校验/取消;OFX 交互视口、图像打包与合成、 按键符号、初始化幂等。 - `oak-app/oakui/graphops.rs` 23 个 + `panels/timeline.rs` 12 个: 工程/素材/命令/查询/放置/剪贴板/编辑/转场的错误矩阵;时间线上下文 菜单、面板命令、拖放路由、落点解析、缩放/微移钳制、真实引擎回退。 - 自有批次 8 个:`component/controls.rs`(Slider/SpinBox/ComboBox/ CheckBox 的方法与渲染分支)、`oakui/mock.rs`(设置/代理/同步/ 多机位)、`oakui/engine.rs`(`AppEngine` 其余全部默认实现)。 - 稳定性:`render_threads_test` 的三个逐字节比对测试固定软件解码 (`OAK_HWACCEL=0` 的 RAII 守卫,测试锁内无竞态)——硬件与软件解码 存在几 LSB 差异,此前表现为 ±3 字节的间歇性失败;连跑 5 次稳定。 - 记录(未改生产代码):`graphops::roll_edit` 与文档描述不符——当前 `BlockTrimCommand` 的 `TrimOut` 映射使左片段 in 移动、后继 out 移动, 接缝不移动;`undocommands_test` 固化了同一行为,列为后续 PR 的语义 缺陷候选。 - 聚合(干净对象集,同口径):区域 **91.38%**、函数 **87.75%**、行 **90.72%**(148231/163392,**行目标 90% 达成**)、分支 **65.98%** (9995/15149)。 - 各 crate 行/分支:oak-app **92.5%** / 67.7%;oak-render 92.7% / 67.7%;oak-codec 93.9% / 74.4%;oak-task 87.7% / 61.5%; oak-timeline 90.6% / 61.3%;oak-storage 82.7% / 65.5%; oak-plugin 79.2% / 51.7%(非 app 最大缺口)。 - 下一步:分支 80% 目标还差 ~14pp。优先 oak-plugin (node_factory 222 / suites/property 165 / host 104 / instance 222 / render_driver 163 / clip 90)、oak-app(real.rs 712、app.rs 403、 renderops 270、program_viewer 262、controls 230、nodegraph 213)与 oak-worker/ofx_host(272 行)。 ### 第五批(分支缺口,2026-09-19) - **227 个新测试**(全 workspace 非插桩:104 个目标、**3069 passed / 0 failed / 7 ignored**——ignored 为既有 GPU 门控): - oak-plugin 核心合计 46 个(`node_factory`/`instance`/`host` 三文件的 组合计,非单文件计数):`node_factory`(用 build.rs 测试插件 bundle 的确定性布局覆盖 build_core 组/页/颜色/组合框表、命名与 派发、执行器分支)、`instance`(编辑事务、clip 偏好、RoD/RoI、 渲染/GL 路径、序列渲染、interact)、`host`(dlopen、bundle 扫描、 PluginCache 去重、build_plugin 上下文协商、实例存活计数)。 - oak-plugin 套件/驱动合计 62 个(`render_driver`/`clip`/ `suites/property`/`suites/gl_render` 四文件的组合计,非单文件计数): `render_driver`(CPU/GL 帧渲染、组件与深度矩阵、参数覆盖、输出写回 与上传失败)、`clip`(fetch/store 转换矩阵与协商)、`suites/property` (属性套件全 API)、`suites/gl_render`(格式/纹理属性/加载与清理; GL 不可用路径跳过)。 - oak-worker 39 个:`ofx_host`(共享内存 attach/帧池/输入读取/输出 发布/作业处理与回退)与 `worker`(进度管道、渲染器回退、握手与 加载、渲染/批量/音频 ticket、图形回退与崩溃钩子);新增全局工厂/ 颜色测试锁避免并行竞态。 - oak-core/render 53 个:`procpool`(调度、取消、回收、段增长、关停 排空、真实 worker 崩溃重启)、`videoparams`、`color` 分支。 - oak-app 27 个:`renderops`(代理/分辨率/编码参数/重打包守卫)、 `program_viewer`(事件路由/菜单/交互防护)、`nodegraph`(数据源/ 构建/连接/编辑)、`project_explorer`(事件/菜单/重命名/删除)与 `mock`/`controls`/`inspector`(效果事件、指针与键盘路径、上下文 与添加菜单)。 - 生产缺陷修复(oak-plugin `clip.rs`):`store_output_image` 此前把像素 写入 `texture_get_frame` 返回的深拷贝(CPU 纹理的写入被丢弃);现在 CPU 直接回写 `f.data`、GPU 走上传、Planar 明确报错(与 `render_driver::write_output_frame` 的既有修复一致),新测试固化。 - 稳定性:修复本批 procpool 测试的三个自锁死锁(`cancel_frame` / `set_graph_snapshot` / `poll` 内部会锁 dispatcher,测试却在持锁时 调用)、调度亲和性误设、U10 槽位尺寸期望与握手替身二进制;42 个 procpool 测试全绿。 - 聚合(干净对象集,同口径):区域 **93.11%**、函数 **88.99%**、行 **92.62%**(162596/175544)、分支 **70.09%**(11024/15729)。 - 各 crate 行/分支:oak-app **94.5%** / 71.0%;oak-plugin 79.2% → **90.9%** / 51.7% → **75.7%**;oak-render 94.3% / 70.4%; oak-core 93.5% / 75.4%;oak-worker 80.8% → **90.3%** / 52.1% → **70.3%**;oak-codec 93.9% / 74.4%;oak-task 87.7% / 61.3%; oak-timeline 90.6% / 61.3%;oak-storage 82.7% / 65.5%。 - 下一步:分支 80% 还差 ~10pp。剩余分支热点:`real.rs`(428)、 `eval.rs`(157)、`oak-task/render.rs`(150)、`worker.rs`(109)、 `color.rs`(104)、`app.rs`(88)、`mock.rs`(87)、`graphops.rs` (84)、`backend.rs`(74)、`controls.rs`(71)、 `undogeneral`/`undoripple`(各 67)。 ### 第六批(分支缺口,2026-09-20) - **102 个新测试**(全 workspace 非插桩:104 个目标全绿): - `oakui/real.rs` 15 个:时间线编辑事件的陈旧 id 守卫、文档导出/互换 失败路径、未知 id 查找、素材/序列/文本/转场放置守卫、多机位与 AFV 解析、代理自动启动与失败策略、源监视器回放窗口与陈旧取消、预览窗口 陈旧完成释放与 BGRA8 槽读取、波形同步放置与不可相关跳过、帧助手。 - `oak-render/eval.rs` 15 个(GPU/OCIO 夹具,缺环境时打印跳过)+ `oak-task/render.rs` 3 个(私有调度器经 worker 渲染、缺失媒体错误)。 - `oak-core/color.rs` 6 个、`backend.rs` 16 个(GPU 路径按适配器门控)、 `oak-codec/ffmpeg.rs` 5 个(软件重开、EOF 回退、重采样平面/flush)。 - `oak-worker/worker.rs` 7 个、`ofx_host.rs` 2 个、`oak-render/procpool.rs` 12 个(死锁修复后的调度/回收/VRAM 探测/读取 EOF 等)。 - `oak-app/controls.rs` 6 个、`mock.rs` 4 个、`app.rs` 10 个(滑块编辑 与拖拽、复选框键盘、组合框选择、微调框滚动/键盘/编辑器;节点图与 时间线事件、连接助手、放置路径;模态不匹配、进度排空、订阅路由、 序列弹窗提交/取消、tick 循环、退出动作)。 - `panels/node_editor.rs` 1 个(菜单路由、适配/缩放、dock 元数据)。 - 本批无生产代码改动;各 agent 报告本 crate 套件全绿、clippy 无新增告警。 - 聚合(干净对象集,同口径):区域 **93.90%**、函数 **89.61%**、行 **93.46%**(173470/185601)、分支 **71.95%**(11553/16057)。 - 各 crate 行/分支:oak-app **96.0%** / **76.6%**;oak-core 94.8% / 77.5%;oak-render 95.0% / 69.7%;oak-codec 94.2% / 75.1%; oak-worker 91.9% / 70.7%;oak-task 88.8% / 62.8%;oak-timeline 90.6% / 61.3%;oak-plugin 90.9% / 75.7%;oak-storage 82.7% / 65.5%。 - 下一步:分支 80% 还差 ~8pp(约 1.3k 分支)。剩余热点:`real.rs` (321)、`eval.rs`(228)、`oak-task/render.rs`(136)、`ffmpeg.rs` (110)、`color.rs`(110)、`worker.rs`(108)、`graphops.rs`(82)、 `procpool.rs`(76)、`ofx_params.rs`(68)、`undogeneral`(67)。 ### 第七批(Review 修复,2026-09-22) - 依据 `test-coverage-90-80-review.md` 修复全部高危/中危与 §5 条目(仅 §3.1 的语义裁决按报告建议延后,准备项已落地)。要点: - §3.2:`render_test.rs` 私有调度器测试不再"永不失败"(环境错误跳过、 产品错误失败,RAII 恢复管理器/env)。 - §3.3:三个回放验收测试仅在**零交付**时跳过;`OAK_STRICT_PLAYBACK=1` 严格模式;`WorkerBinGuard` 按 `current_exe` 解析 worker。 - §3.4:CI 安装 `icc-profiles-free` + `system_icc()` 补 Ubuntu 实际路径; `gpu_context_for_import` 尊重 `OAK_REQUIRE_GPU` 并输出 `SKIP:` 标记。 - M1–M10 与 §5:文档/断言/读回/容差/RAII 全部落实(详见 review §9)。 - §3.1:以仓库内 C++ 上游源码核准语义;`RippleInfo::new` + 真实 info 测试 落地;roll/slide/insert-gaps/ResizeWithMediaIn 的期望值加 KNOWN-SWAP 标注,语义修复清单已细化(独立 PR)。 - 追加修复:`source_monitor_playback_window_and_stale_cancels` 的早期 playhead 断言缺陷;`preferences_general_tab_writes_cache_ahead_and_storage` 的点击→按键焦点竞态。 - 验收:全 workspace **104 目标、3179 passed / 0 failed / 7 ignored**。 - 插桩终测(干净对象集,同口径):区域 **93.96%**、函数 **89.59%**、行 **93.49%**(180742/193331)、分支 **71.58%**(11805/16491)。分支较第六批 微降 0.37pp:本批以测试质量换掉了若干"容忍型/同义反复"执行路径(例如 §3.3 的交付即失败、M3 的效果夹具更换),属预期代价而非回归。 - 各 crate 行/分支:oak-app 96.0% / 76.3%;oak-plugin 90.8% / 76.1%; oak-render 94.9% / 69.4%;oak-core 94.2% / 75.5%;oak-codec 94.2% / 75.1%;oak-worker 92.7% / 70.4%;oak-timeline 91.0% / 60.2%; oak-task 89.5% / 62.8%;oak-storage 82.9% / 66.6%。 - 下一步仍为分支 80%(缺口 ~8.4pp);热点见上。 ## 8. 风险与对策 1. **oak-app UI 覆盖成本最高、最易 flaky**:只用 test-support 的确定性 接口(`run_until_parked`/`dispatch_keystroke`/`simulate_click`), 禁止 sleep;固定 seed;面板测试按面板拆文件并行;CI 现有"失败重试 一次"仅用于已知 flake,新增 flake 必须根治或 `#[ignore]` + 登记。 2. **覆盖率驱动出坏测试**(断言实现细节、为行数写无意义 case): §2.1 行为断言 + review checklist;`#[coverage(off)]` 需登记理由。 3. **分支覆盖工具不稳定(nightly `--branch`)**:固定 `nightly-2026-07-21`;加一个"已知分支数"的 canary 文件(M0), 工具链升级导致计数漂移时用 canary 对账。 4. **跨平台合并复杂**:主门禁只认 Linux 全量;平台文件单独门禁; 合并报告是可选增强,不被 PR 阻塞。 5. **套件变慢**:M0 给测试加 `--no-fail-fast` + 超时;对 >2s 的单测 标记并解释;GPU/媒体测试复用 `testmedia` 与共享 fixture;必要时 `cargo nextest`(cargo-llvm-cov 原生支持)。 6. **异常路径需要注入点**:优先用现有开关(`OAK_HWACCEL`、 `OAK_GPU_IMPORT`、`OAK_PIPELINE`);不足的地方在 M0 加 `FailPoint`/假后端,**不修改生产语义**。 7. **既有 `#[ignore]`**:M2/M4 补齐 serializer C++ golden 与 OFX M11 golden,其余确实缺外部环境的登记豁免并保留 `#[ignore]` 理由。 8. **覆盖率只算生产代码后基线更低**:M0 先测主口径基线,再按 §7 校准 各里程碑数字;若主口径比兼容口径低 10pp 以上,先做 M1 再宣布 M2 目标。 ## 9. 不变量与边界 - **不改生产行为**:本计划只加测试、度量与最小可测性基建;任何为可测性 做的重构(纯函数抽出、依赖注入)必须单独 PR、单独评审、行为不变。 - **不引入不稳定因素**:无 sleep、无真实网络/时间依赖(外部依赖按 A3/A8 跳过并打印理由);测试失败必须可复现。 - **执行环境**:所有测试必须在 CI 的 headless(xvfb + lavapipe)通过; `OAK_REQUIRE_GPU=1` 下 GPU 测试真跑;硬件/平台测试在无硬件环境跳过, 跳过不等于通过(报告里单列)。 - **性能预算**:`cargo test --workspace` 墙钟时间相对 2026-09 基线不 超过 2×;coverage job 目标 ≤45 分钟。 - **豁免有账**:`unit-exemptions.md`、`exclusions.md`、边界清单三本账 是验收物,缺登记视为未完成。 - **文档同步**:`docs/zh/build.md` 增加覆盖率章节;`docs/zh/plans/README.md` 收录本计划与 `coverage/` 归档目录。 ## 10. 交付物清单 1. `tooling/coverage.sh`、`tooling/coverage_report.py`、 `tooling/boundary_api.py`、`tooling/coverage-thresholds.toml`; 2. `.github/workflows/coverage.yml` + `ci.yml` 接线(Linux 必跑, Windows/macOS 周更); 3. `docs/zh/plans/coverage/`: `baseline-2026-09.{json,txt}`、`boundary-api-inventory.md`、 `unit-exemptions.md`、`exclusions.md`、`final-.{json,txt}`、 `merged-.txt`(可选); 4. 新增测试:层 1 单测与层 2 边界测试(按 §6 里程碑分批); 5. 本计划文档与 `docs/zh/plans/README.md` 索引更新。