diff --git a/crates/oak-task/tarpaulin-report.html b/crates/oak-task/tarpaulin-report.html deleted file mode 100644 index d7e26e713..000000000 --- a/crates/oak-task/tarpaulin-report.html +++ /dev/null @@ -1,794 +0,0 @@ - - -
- - - - - - - - - - - \ No newline at end of file diff --git a/docs/zh/plans/README.md b/docs/zh/plans/README.md index 43030b8da..13e782b9f 100644 --- a/docs/zh/plans/README.md +++ b/docs/zh/plans/README.md @@ -8,13 +8,16 @@ | 文档 | 内容 | 启动前提 | |---|---|---| | [`ai-agent-design.md`](ai-agent-design.md) | **AI Agent 插件设计**(已按 OPP/1 重写):多模态 LLM 作为外部插件经策展工具面自动剪辑,`render.*` 取帧回喂形成"编辑→看图→再编辑"视觉闭环;事务化编辑、双层确认、声明式 AI 面板、Mock LLM/回放夹具测试、A1–A5 里程碑 | external-plugin-system P1–P3 完成(面板需 P4) | -| [`external-plugin-system.md`](external-plugin-system.md) | **外部功能插件系统**:插件=独立进程(非库加载),JSON-RPC over stdio 控制面 + shm 数据面(泛化 M15 render-worker 传输);策展宿主 API(事务化可撤销编辑、取帧回喂 AI)、声明式/像素面双 UI 路径、能力位与确认模式;含与 ai-agent-design.md 的关系与 P1–P6 里程碑 | 已解锁(RIIR + M15 完成),随时启动 | -| [`external-plugin-protocol.md`](external-plugin-protocol.md) | **插件协议规范 OPP/1**:NDJSON 分帧 + JSON-RPC 2.0 双向信封、握手/心跳/关闭、全量方法/事件/错误码、编辑事务协议、shm 数据面(无头部无锁)、声明式与像素面 UI 协议、限流配额、版本演进规则、AI 粗剪报文示例 | 随 external-plugin-system 启动 | +| [`completed/external-plugin-system.md`](completed/external-plugin-system.md) | **外部功能插件系统**:插件=独立进程(非库加载),JSON-RPC over stdio 控制面 + shm 数据面(泛化 M15 render-worker 传输);策展宿主 API(事务化可撤销编辑、取帧回喂 AI)、声明式/像素面双 UI 路径、能力位与确认模式;含与 ai-agent-design.md 的关系与 P1–P6 里程碑 | 已解锁(RIIR + M15 完成),随时启动 | +| [`completed/external-plugin-protocol.md`](completed/external-plugin-protocol.md) | **插件协议规范 OPP/1**:NDJSON 分帧 + JSON-RPC 2.0 双向信封、握手/心跳/关闭、全量方法/事件/错误码、编辑事务协议、shm 数据面(无头部无锁)、声明式与像素面 UI 协议、限流配额、版本演进规则、AI 粗剪报文示例 | 随 external-plugin-system 启动 | | [`render-pipeline-threads.md`](render-pipeline-threads.md) | **渲染管线改造**:单解码线程 + 单渲染线程 + 主进程上屏(GPU 单队列,多进程无意义);解码硬解优先且零拷贝(FFmpeg hwaccel 经 DMA-BUF/DXGI/IOSurface/CUDA 导入,CPU 解码上传仅 fallback,手写 GPU 解码为次选);OpenFX 收编进唯一隔离进程;decode→render→present 三队列流水线;内置效果全 GPU 零拷贝;resolve 重写为 match+Job 单循环并升级为 Job 图(GraphInput/GraphOutput 虚拟端点可见、禁删,从输入节点 Kahn 形态 BFS 至全部分支汇聚输出节点);M0a–M5 里程碑 | 已解锁,随时启动(M0a 独立先行) | +| [`test-coverage-90-80.md`](test-coverage-90-80.md) | **测试覆盖率提升(行 ≥90% / 分支 ≥80%)**:测试分两层——单元测试 + 模块边界集成测试;层 2 要求每个模块边界 API 都有正常/异常全矩阵;层 1 可豁免过于简单的函数但仍覆盖正常/异常;覆盖率进 CI(cargo-llvm-cov + nightly `--branch`,逐 crate 地板 + PR diff coverage ratchet);M0 度量基建 → M1 单测 → M2 边界 → M3 app → M4 平台 → M5 收官 | render-pipeline-threads M5 完成(已有基线留档 `render-pipeline-threads-m5-branch-coverage.txt`) | ## 其他参考 +- 覆盖率批次未提交测试的 review 报告(假测试与实现语义核查): + [`test-coverage-90-80-review.md`](test-coverage-90-80-review.md) - 构建:`docs/zh/build.md`、`docs/zh/build_macos-zh.md` - 工程文件:`docs/zh/project-file-reference.md` - 代码风格与 Google Test 要求:`CONTRIBUTING.md`(仓库根) diff --git a/docs/zh/plans/ai-agent-design.md b/docs/zh/plans/ai-agent-design.md index 983aa7fbc..dd7af0eb5 100644 --- a/docs/zh/plans/ai-agent-design.md +++ b/docs/zh/plans/ai-agent-design.md @@ -5,9 +5,9 @@ > AI 能力现在是一个**外部功能插件**:独立进程、经 OPP/1 协议操作 Oak。 > > 依赖文档(冲突时以它们为准): -> - [`external-plugin-system.md`](external-plugin-system.md)——插件系统总体设计 +> - [`completed/external-plugin-system.md`](completed/external-plugin-system.md)——插件系统总体设计 > (进程模型、能力位、确认模式、里程碑 P1–P6) -> - [`external-plugin-protocol.md`](external-plugin-protocol.md)——**OPP/1 协议全文** +> - [`completed/external-plugin-protocol.md`](completed/external-plugin-protocol.md)——**OPP/1 协议全文** > (方法/事件/事务/shm/UI 的冻结定义,本文引用的 §n 均指该文档) > > **一句话**:多模态 LLM 跑在一个独立插件进程里,经 OPP/1 的策展宿主 API diff --git a/docs/zh/plans/acescg-f32-pipeline.md b/docs/zh/plans/completed/acescg-f32-pipeline.md similarity index 99% rename from docs/zh/plans/acescg-f32-pipeline.md rename to docs/zh/plans/completed/acescg-f32-pipeline.md index ab71dedb2..21eb1967a 100644 --- a/docs/zh/plans/acescg-f32-pipeline.md +++ b/docs/zh/plans/completed/acescg-f32-pipeline.md @@ -7,7 +7,7 @@ > (Wayland color-management-v1 / macOS layer.colorspace / Windows > SetColorSpace1),UI 保持 sRGB 不进 ACEScg。实际色彩数学在 > `crates/oak-common/src/colormath.rs`,显示策略在 -> `crates/oak-app/src/oakui/displaycolor.rs`,内容色域声明 API 在 gpui 的 +> `../../../../crates/oak-app/src/oakui/displaycolor.rs`,内容色域声明 API 在 gpui 的 > `WindowContentColorspace`/`set_content_colorspace`。本文档仅作历史参考, > 以代码为准。 diff --git a/docs/zh/plans/external-plugin-protocol.md b/docs/zh/plans/completed/external-plugin-protocol.md similarity index 100% rename from docs/zh/plans/external-plugin-protocol.md rename to docs/zh/plans/completed/external-plugin-protocol.md diff --git a/docs/zh/plans/external-plugin-system.md b/docs/zh/plans/completed/external-plugin-system.md similarity index 98% rename from docs/zh/plans/external-plugin-system.md rename to docs/zh/plans/completed/external-plugin-system.md index f83bf5980..567adc5e9 100644 --- a/docs/zh/plans/external-plugin-system.md +++ b/docs/zh/plans/completed/external-plugin-system.md @@ -141,7 +141,7 @@ Oak ◄── {"result":{"name":"ai-cut","api":1,"capabilities":[...], 不引入 tokio)。 - `HostApi`:把插件请求翻译成内部调用(§3),执行前查能力位(§5)。 - `PluginPanel`:实现 gpui `DockPanel` 的通用面板壳,注册进 - `AppPanelRegistry`(`crates/oak-app/src/panels/mod.rs` 目前是硬编码 + `AppPanelRegistry`(`../../../../crates/oak-app/src/panels/mod.rs` 目前是硬编码 panel ids——需加一处"动态 panel 注册"扩展点,这是 app 侧唯一的新机制)。 - `ShmPool`:泛化自 `oak-render/src/ipc.rs`。 @@ -182,7 +182,7 @@ Oak ◄── {"result":{"name":"ai-cut","api":1,"capabilities":[...], | `ui.*` | 见 §4 | `PluginPanel` + gpui_widgets | `ui.panel` | | `edit.*` | `begin` / `commit` / `abort` / `undo` / `redo` | `oak_undo` | 随变更方法 | -**取帧→AI 通路**(旗舰用例的关键路径,对齐 `ai-agent-design.md` §2.2): +**取帧→AI 通路**(旗舰用例的关键路径,对齐 `../ai-agent-design.md` §2.2): `render.get_frame {sequence, time, max_size}` → 引擎经 ticket/进程池渲染 → BGRA 进 shm 槽 → 返回 `{shm_slot, width, height, format}`;插件侧 SDK 一行 `frame.to_png_bytes()`(OIIO/stb_image_write 或 Pillow)即可回喂多模态模型。 @@ -265,7 +265,7 @@ AI 剪辑插件的聊天面板、操作日志、确认按钮,这套完全够 - **能力位**:manifest `capabilities` 声明,安装/升级时向用户展示差异; `HostApi` 在每次调用入口检查,越权调用返回 `CAPABILITY_DENIED` 并记日志。 v1 能力集合即 §3.2 表右列。 -- **确认模式**(继承 `ai-agent-design.md` §6):`*.edit` 与 `export` 类调用 +- **确认模式**(继承 `../ai-agent-design.md` §6):`*.edit` 与 `export` 类调用 默认弹"插件 X 请求执行:split_clip n17 @ 3/25 [允许] [允许本会话] [拒绝]"; 用户可在插件设置里改为自动。 - **可撤销**:事务分组进 UndoStack,历史面板里显示为"插件名:事务标签", @@ -295,12 +295,12 @@ AI 剪辑插件的聊天面板、操作日志、确认按钮,这套完全够 "聊天指令 → get_thumbnails 扫时间线 → 事务化 split/ripple_delete → get_frame 验证"的 AI 粗剪闭环——**它就是 ai-agent-design.md 的落地形态**。 -### 与 `ai-agent-design.md` 的关系 +### 与 `../ai-agent-design.md` 的关系 该文档写于 RIIR 拆分前,假设"C ABI 小库 + 引擎内置 MCP server"。RIIR 与 M15(渲染进程隔离)完成后,更优路径是:**AI 能力不进引擎,作为一个外部 插件**跑在本系统上;MCP 仍可作为该插件对外的协议(插件自己起 MCP server -连 LLM 客户端),Oak 内核始终对 AI 无感知。本文落地后,`ai-agent-design.md` +连 LLM 客户端),Oak 内核始终对 AI 无感知。本文落地后,`../ai-agent-design.md` 的 M1/M2(工具面、取帧通路)由 §3.2 取代,M3(AI 面板)由 §4.1 取代。 --- diff --git a/docs/zh/plans/ofx-misc-gpu-cleanroom.md b/docs/zh/plans/completed/ofx-misc-gpu-cleanroom.md similarity index 97% rename from docs/zh/plans/ofx-misc-gpu-cleanroom.md rename to docs/zh/plans/completed/ofx-misc-gpu-cleanroom.md index bf30f797f..c0ad6d9f4 100644 --- a/docs/zh/plans/ofx-misc-gpu-cleanroom.md +++ b/docs/zh/plans/completed/ofx-misc-gpu-cleanroom.md @@ -14,7 +14,7 @@ ### 1.1 节点/渲染管线(完全够用) - 内置特效 = `oak-node/src/nodes/*.rs` 的 `NodeBehavior` 实现:声明输入(`Input`), - `value()` 推 `ShaderJobPayload`(`crates/oak-node/src/jobs.rs`),`shader_code()` + `value()` 推 `ShaderJobPayload`(`../../../../crates/oak-node/src/jobs.rs`),`shader_code()` 返回 GLSL 片段。渲染端 `crates/oak-render/src/eval.rs::process_shader_job`: 编译(naga→WGSL,**不支持 GLSL switch**——新 shader 一律 if/else,教训见提交 `37df1d3d3`)、按名绑定全部纹理参数、嵌套 payload 递归(深度上限 8)、 @@ -32,7 +32,7 @@ - `crates/oak-app/src/oakui/effectchain.rs::addable_effects`:内置(`group: None`) + OFX 动态条目(`group: Some(子类)`),排序已按组+名字。 -- `crates/oak-app/src/panels/effect_library.rs`:渲染时组头已存在 +- `../../../../crates/oak-app/src/panels/effect_library.rs`:渲染时组头已存在 (`group_header()`,内置统一一个 "Built-in" 头),**不可折叠**;有搜索框。 - 检查器"添加特效"菜单(`panels/inspector.rs:157`)吃同一张 `addable_effects` 表。 @@ -102,7 +102,7 @@ ClipTest(斑马纹超范围指示)、Matrix3x3/Matrix5x5(通用卷积) 像素尺寸参数用 `resolution_in`;采样偏移用中心原点像素空间与否按特效语义—— 颜色类与坐标无关,几何类参照 transform 的中心原点)。 4. `register()` 进 `nodes/mod.rs` 的注册表。 -5. 单元测试(输入默认值/隐藏标志/job 参数)+ **`crates/oak-render/src/eval.rs` +5. 单元测试(输入默认值/隐藏标志/job 参数)+ **`../../../../crates/oak-render/src/eval.rs` GPU 像素测试**(eval_node_row 模式,无 GPU 自动跳过)。颜色类用纯色输入断言 输出值;几何/模糊类用点/块图案断言位移/扩散。 diff --git a/docs/zh/plans/text-footage-redesign.md b/docs/zh/plans/completed/text-footage-redesign.md similarity index 96% rename from docs/zh/plans/text-footage-redesign.md rename to docs/zh/plans/completed/text-footage-redesign.md index 92426ada2..71de648c7 100644 --- a/docs/zh/plans/text-footage-redesign.md +++ b/docs/zh/plans/completed/text-footage-redesign.md @@ -9,7 +9,7 @@ ## 0. 已先行修复(不在本计划范围) - 输入框无法输入:参数视图每个引擎 tick 都把引擎值重刷进输入框,击键下一帧即被清掉。 - 已改为聚焦期间跳过重同步(`crates/oak-app/src/panels/ofx_params.rs` sync_values 的 + 已改为聚焦期间跳过重同步(`../../../../crates/oak-app/src/panels/ofx_params.rs` sync_values 的 Text 分支,与曲线编辑器拖拽保护同款),含回归测试 `text_field_keeps_in_progress_edits_while_focused`。**已提交**(`5f8db8e31`)。 @@ -17,7 +17,7 @@ ### 1.1 节点层 -- `crates/oak-node/src/nodes/textv3.rs`(type id `org.olivevideoeditor.Olive.text3`): +- `../../../../crates/oak-node/src/nodes/textv3.rs`(type id `org.olivevideoeditor.Olive.text3`): 当前"Text"特效。输入仅 `text_in`(**HTML 原文**,默认值是 `Sample Text
`)、`valign_in`、 `use_args_in`、`args_in`,外加 ShapeNodeBase 继承的 `pos_in`/`size_in`/`color_in`。 @@ -36,7 +36,7 @@ ### 1.2 素材/时间轴层 -- bin 条目 = 项目根文件夹 `FolderBehavior.children`(`crates/oak-app/src/oakui/projectbrowser.rs`, +- bin 条目 = 项目根文件夹 `FolderBehavior.children`(`../../../../crates/oak-app/src/oakui/projectbrowser.rs`, `roots()/children()`,条目 id = 节点 identity,名称 = `core.label`)。 - 时间轴接受 bin 拖放:footage 走 `engine.drop_footage_at` → `graphops::place_footage_clip`(footage 节点连到 clip `tex_in`)。**没有**非 footage @@ -97,7 +97,7 @@ ### 3.2 文本后端(cosmic-text) -- 新增 `crates/oak-app/src/oakui/textengine.rs`(app 层安装 hook,oak-node 不加依赖): +- 新增 `../../../../crates/oak-app/src/oakui/textengine.rs`(app 层安装 hook,oak-node 不加依赖): - 用 lockfile 已有的 `cosmic-text`(在 oak-app 的 Cargo.toml 提升为直接依赖, 版本与 gpui 一致 0.19,避免双版本)。 - 实现 `measure`/`render` 两个 `fn`,在 `RealEngine::create`(或 app 启动) diff --git a/docs/zh/plans/render-pipeline-threads-m5-branch-coverage.txt b/docs/zh/plans/render-pipeline-threads-m5-branch-coverage.txt new file mode 100644 index 000000000..1918cdb5a --- /dev/null +++ b/docs/zh/plans/render-pipeline-threads-m5-branch-coverage.txt @@ -0,0 +1,450 @@ +# M5 阶段性验收:分支覆盖率(branch coverage)留档(平台行未验证) + +> 生成日期:2026-09-17(管线改造 M0a–M5 **阶段性**;M5 未完全关闭) +> +> **状态说明(2026-09-17 审计后补注)**: +> +> - 本轮采集在当天的 M5 审计修复(montage staging、导入计数器三态、 +> planar 表面驻留上限)**之前**;修复后的数字将在下一轮覆盖率门禁重测。 +> - Windows(D3D11VA)/macOS(IOSurface)两行只有 cfg 代码,**尚未在 +> 对应平台编译过**,也无硬件实测;平台编译 + 真机测试是 M5 关闭前置。 +> - P010/HDR 导入未单独实测;平台错误回退分支单机走不到。 +> - 因此本文件是**阶段性质量快照**,不构成 M5 收官证明。 +> - **口径勘误(2026-09-17 晚)**:本文件的数字是**陈旧对象集拉低的 +> 下界**——`target/coverage-obj` 累积了多次构建的测试二进制,同一 +> 源文件的新旧副本 region 被合并,未执行的旧副本显著拉低行/分支 +> 比例(实测同一文件:多对象 51% vs 干净对象 95%)。清空测试二进制 +> 重测后的干净基线为**行 81.72% / 分支 55.45%** +> (2026-09-17 干净基线),后续覆盖率门禁一律使用干净对象集; +> 详见 `test-coverage-90-80.md` §4.1。 +> +> 工具链:`nightly-2026-07-21`(rustc 1.99.0-nightly,`-Zcoverage-options=branch`) +> 采集命令: + +```sh +RUSTFLAGS="-Cinstrument-coverage -Zcoverage-options=branch" \ + LLVM_PROFILE_FILE="$PWD/target/coverage/%p-%m.profraw" \ + CARGO_TARGET_DIR=target/coverage-obj \ + cargo +nightly-2026-07-21 test --workspace --no-fail-fast + +~/.rustup/toolchains/nightly-2026-07-21-*/lib/rustlib/x86_64-unknown-linux-gnu/bin/llvm-profdata \ + merge -sparse target/coverage/*.profraw -o target/coverage.profdata + +~/.rustup/toolchains/nightly-2026-07-21-*/lib/rustlib/x86_64-unknown-linux-gnu/bin/llvm-cov report \ + -instr-profile=target/coverage.profdata --show-branch-summary \ + --ignore-filename-regex="(\.cargo|registry/src|rustlib|/gpui/|target/coverage-obj|/tooling/|\.rustup)" \ + $(find target/coverage-obj/debug/deps -maxdepth 1 -type f -executable) +``` + +> 口径:仅统计本仓库 `crates/oak-*` 源码(vendored gpui、ocio-rs、Rust 标准库与 +> 目标目录均已排除)。分支列来自 `-Zcoverage-options=branch` 的分支区域计数; +> 硬件导入的真机路径(VAAPI/DMA-BUF)由 `oak-render/tests/footage_import_test.rs` +> 覆盖(该二进制随后单独串行补采,保证导入分支计入),其平台错误回退分支 +> 单机无法全部走到,因此 `gpuinterop.rs` 的分支覆盖率天然偏低。 +> 本次汇总(项目源码):区域 53.60% / 函数 49.90% / 行 53.15% / **分支 27.94%**。 + +> **管线改造关键文件(Regions / Lines / Branches):** + + +crates/oak-codec/src/gpuinterop.rs 471 164 65.18% 34 7 79.41% 300 98 67.33% 36 18 50.00% +crates/oak-codec/src/hwdecode.rs 408 217 46.81% 28 14 50.00% 256 138 46.09% 12 5 58.33% +crates/oak-core/src/backend.rs 5977 2997 49.86% 279 142 49.10% 4941 2423 50.96% 178 97 45.51% +crates/oak-core/src/backend/external.rs 293 45 84.64% 11 2 81.82% 268 28 89.55% 18 10 44.44% +crates/oak-core/src/texture.rs 589 263 55.35% 57 19 66.67% 412 159 61.41% 24 11 54.17% +crates/oak-render/src/eval.rs 9865 5441 44.85% 450 261 42.00% 5742 3311 42.34% 510 288 43.53% +crates/oak-render/src/pipeline.rs 1226 41 96.66% 78 9 88.46% 807 25 96.90% 108 59 45.37% + +## 全量报告(项目源码过滤后) + + + +``` +Filename Regions Missed Regions Cover Functions Missed Functions Executed Lines Missed Lines Cover Branches Missed Branches Cover +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +crates/oak-app/src/actions.rs 1721 903 47.53% 135 75 44.44% 1027 544 47.03% 48 48 0.00% +crates/oak-app/src/app.rs 15900 10379 34.72% 1464 974 33.47% 8754 5782 33.95% 286 286 0.00% +crates/oak-app/src/dialogs.rs 10313 6952 32.59% 823 587 28.68% 6336 4284 32.39% 194 194 0.00% +crates/oak-app/src/i18n.rs 882 408 53.74% 79 45 43.04% 490 237 51.63% 34 34 0.00% +crates/oak-app/src/lib.rs 4 4 0.00% 1 1 0.00% 4 4 0.00% 0 0 - +crates/oak-app/src/logging.rs 202 58 71.29% 15 5 66.67% 104 36 65.38% 16 16 0.00% +crates/oak-app/src/main.rs 3 3 0.00% 1 1 0.00% 3 3 0.00% 0 0 - +crates/oak-app/src/manager.rs 903 450 50.17% 65 36 44.62% 575 291 49.39% 26 26 0.00% +crates/oak-app/src/oakui/audio_thread.rs 154 3 98.05% 11 1 90.91% 104 2 98.08% 6 6 0.00% +crates/oak-app/src/oakui/component/controls.rs 1233 546 55.72% 78 30 61.54% 782 299 61.76% 122 122 0.00% +crates/oak-app/src/oakui/component/menu.rs 1345 678 49.59% 92 52 43.48% 822 401 51.22% 14 14 0.00% +crates/oak-app/src/oakui/component/text_input.rs 67 7 89.55% 7 1 85.71% 44 4 90.91% 0 0 - +crates/oak-app/src/oakui/displaycolor.rs 595 475 20.17% 46 34 26.09% 378 298 21.16% 50 50 0.00% +crates/oak-app/src/oakui/effectchain.rs 3372 1580 53.14% 193 93 51.81% 1726 774 55.16% 98 98 0.00% +crates/oak-app/src/oakui/engine.rs 1072 1017 5.13% 215 201 6.51% 1109 1051 5.23% 14 14 0.00% +crates/oak-app/src/oakui/frames.rs 219 3 98.63% 8 0 100.00% 120 2 98.33% 8 8 0.00% +crates/oak-app/src/oakui/gpu.rs 293 157 46.42% 22 14 36.36% 232 139 40.09% 28 28 0.00% +crates/oak-app/src/oakui/graphops.rs 11296 5702 49.52% 792 489 38.26% 5995 3100 48.29% 466 466 0.00% +crates/oak-app/src/oakui/icons.rs 70 0 100.00% 6 0 100.00% 57 0 100.00% 2 2 0.00% +crates/oak-app/src/oakui/mock.rs 7864 4670 40.62% 777 437 43.76% 5499 3215 41.53% 248 248 0.00% +crates/oak-app/src/oakui/mod.rs 54 28 48.15% 6 3 50.00% 24 13 45.83% 0 0 - +crates/oak-app/src/oakui/multicam.rs 1135 89 92.16% 64 7 89.06% 646 41 93.65% 92 92 0.00% +crates/oak-app/src/oakui/nodegraph.rs 1876 1238 34.01% 133 89 33.08% 1116 731 34.50% 92 92 0.00% +crates/oak-app/src/oakui/ofx.rs 1945 1187 38.97% 121 71 41.32% 1079 632 41.43% 74 71 4.05% +crates/oak-app/src/oakui/projectbrowser.rs 81 9 88.89% 8 0 100.00% 49 6 87.76% 12 12 0.00% +crates/oak-app/src/oakui/real.rs 28448 17333 39.07% 2411 1389 42.39% 16413 10102 38.45% 1180 1180 0.00% +crates/oak-app/src/oakui/renderops.rs 6005 3186 46.94% 209 128 38.76% 3461 1921 44.50% 182 182 0.00% +crates/oak-app/src/oakui/scopes.rs 180 1 99.44% 7 0 100.00% 96 0 100.00% 0 0 - +crates/oak-app/src/oakui/timecode.rs 126 0 100.00% 14 0 100.00% 70 0 100.00% 4 4 0.00% +crates/oak-app/src/oakui/transport.rs 244 4 98.36% 25 1 96.00% 138 4 97.10% 6 6 0.00% +crates/oak-app/src/oakui/waveform.rs 407 324 20.39% 19 13 31.58% 229 176 23.14% 24 17 29.17% +crates/oak-app/src/oakui/waveformsync.rs 167 0 100.00% 10 0 100.00% 91 0 100.00% 12 12 0.00% +crates/oak-app/src/panels/commands.rs 877 856 2.39% 88 85 3.41% 459 442 3.70% 8 8 0.00% +crates/oak-app/src/panels/effect_library.rs 977 347 64.48% 80 30 62.50% 563 207 63.23% 20 20 0.00% +crates/oak-app/src/panels/history.rs 365 141 61.37% 31 12 61.29% 208 75 63.94% 16 16 0.00% +crates/oak-app/src/panels/inspector.rs 787 569 27.70% 54 41 24.07% 522 386 26.05% 18 18 0.00% +crates/oak-app/src/panels/mod.rs 29 5 82.76% 2 0 100.00% 19 2 89.47% 4 4 0.00% +crates/oak-app/src/panels/multicam.rs 736 160 78.26% 48 9 81.25% 413 94 77.24% 54 54 0.00% +crates/oak-app/src/panels/node_editor.rs 1961 990 49.52% 148 77 47.97% 1077 550 48.93% 48 48 0.00% +crates/oak-app/src/panels/ofx_params.rs 7801 4542 41.78% 467 267 42.83% 4365 2464 43.55% 218 218 0.00% +crates/oak-app/src/panels/program_viewer.rs 1907 1430 25.01% 148 114 22.97% 1068 778 27.15% 44 44 0.00% +crates/oak-app/src/panels/project_explorer.rs 987 629 36.27% 65 41 36.92% 608 384 36.84% 46 46 0.00% +crates/oak-app/src/panels/source_viewer.rs 434 403 7.14% 41 37 9.76% 226 200 11.50% 4 4 0.00% +crates/oak-app/src/panels/status_bar.rs 137 9 93.43% 5 1 80.00% 85 11 87.06% 6 6 0.00% +crates/oak-app/src/panels/timeline.rs 4453 3064 31.19% 339 255 24.78% 2665 1778 33.28% 96 96 0.00% +crates/oak-app/tests/waveform_e2e.rs 52 0 100.00% 3 0 100.00% 24 0 100.00% 0 0 - +crates/oak-audio/src/config.rs 69 50 27.54% 4 2 50.00% 31 21 32.26% 0 0 - +crates/oak-audio/src/error.rs 61 0 100.00% 5 0 100.00% 41 0 100.00% 0 0 - +crates/oak-audio/src/levelmeter.rs 82 1 98.78% 4 0 100.00% 66 1 98.48% 0 0 - +crates/oak-audio/src/manager.rs 915 509 44.37% 76 44 42.11% 605 322 46.78% 0 0 - +crates/oak-audio/src/outputdevice.rs 435 277 36.32% 30 22 26.67% 296 199 32.77% 0 0 - +crates/oak-audio/src/params.rs 78 17 78.21% 7 1 85.71% 54 17 68.52% 0 0 - +crates/oak-audio/src/previewdevice.rs 178 47 73.60% 18 4 77.78% 120 29 75.83% 0 0 - +crates/oak-audio/src/processor.rs 587 313 46.68% 34 20 41.18% 382 204 46.60% 0 0 - +crates/oak-audio/src/synchronizer.rs 32 0 100.00% 2 0 100.00% 38 0 100.00% 10 3 70.00% +crates/oak-audio/src/waveform.rs 785 88 88.79% 29 2 93.10% 478 45 90.59% 0 0 - +crates/oak-audio/src/waveformsync.rs 268 10 96.27% 6 0 100.00% 201 13 93.53% 0 0 - +crates/oak-audio/tests/common/mod.rs 255 127 50.20% 13 7 46.15% 103 51 50.49% 0 0 - +crates/oak-audio/tests/golden_test.rs 211 4 98.10% 12 0 100.00% 97 4 95.88% 0 0 - +crates/oak-audio/tests/levelmeter_test.rs 123 0 100.00% 8 0 100.00% 63 0 100.00% 0 0 - +crates/oak-audio/tests/manager_test.rs 571 248 56.57% 21 8 61.90% 291 120 58.76% 8 4 50.00% +crates/oak-audio/tests/processor_test.rs 616 290 52.92% 28 9 67.86% 272 117 56.99% 0 0 - +crates/oak-audio/tests/sync_test.rs 252 0 100.00% 16 0 100.00% 131 0 100.00% 2 0 100.00% +crates/oak-audio/tests/waveform_test.rs 290 5 98.28% 11 0 100.00% 118 1 99.15% 0 0 - +crates/oak-cli/src/cmd/info.rs 234 40 82.91% 17 12 29.41% 119 23 80.67% 22 11 50.00% +crates/oak-cli/src/cmd/probe.rs 101 5 95.05% 3 0 100.00% 72 1 98.61% 16 5 68.75% +crates/oak-cli/src/cmd/render.rs 256 63 75.39% 7 3 57.14% 150 53 64.67% 44 23 47.73% +crates/oak-cli/src/cmd/transcode.rs 345 118 65.80% 14 6 57.14% 229 92 59.83% 64 32 50.00% +crates/oak-cli/src/engine.rs 998 328 67.13% 87 33 62.07% 699 253 63.81% 88 52 40.91% +crates/oak-cli/src/fmt.rs 148 4 97.30% 18 0 100.00% 149 3 97.99% 10 4 60.00% +crates/oak-cli/src/main.rs 66 0 100.00% 2 0 100.00% 38 0 100.00% 10 1 90.00% +crates/oak-cli/src/ppm.rs 212 11 94.81% 11 3 72.73% 113 4 96.46% 12 1 91.67% +crates/oak-cli/src/wav.rs 238 39 83.61% 17 7 58.82% 114 10 91.23% 8 3 62.50% +crates/oak-cli/tests/cli.rs 326 0 100.00% 27 0 100.00% 201 0 100.00% 0 0 - +crates/oak-codec/src/audioparams.rs 7 4 42.86% 2 1 50.00% 6 3 50.00% 0 0 - +crates/oak-codec/src/conformmanager.rs 276 4 98.55% 17 0 100.00% 202 3 98.51% 10 3 70.00% +crates/oak-codec/src/decoder.rs 905 491 45.75% 91 54 40.66% 613 358 41.60% 14 3 78.57% +crates/oak-codec/src/encoder.rs 239 74 69.04% 30 19 36.67% 157 58 63.06% 24 9 62.50% +crates/oak-codec/src/encodingparams.rs 1046 46 95.60% 47 0 100.00% 770 31 95.97% 142 28 80.28% +crates/oak-codec/src/error.rs 68 0 100.00% 5 0 100.00% 45 0 100.00% 0 0 - +crates/oak-codec/src/exportcodec.rs 179 0 100.00% 7 0 100.00% 96 0 100.00% 0 0 - +crates/oak-codec/src/exportformat.rs 408 24 94.12% 14 0 100.00% 225 17 92.44% 2 0 100.00% +crates/oak-codec/src/ffmpeg.rs 7068 4052 42.67% 373 223 40.21% 4328 2442 43.58% 422 215 49.05% +crates/oak-codec/src/footagedescription.rs 205 0 100.00% 32 0 100.00% 159 0 100.00% 0 0 - +crates/oak-codec/src/frame.rs 449 14 96.88% 41 0 100.00% 253 12 95.26% 32 7 78.12% +crates/oak-codec/src/framemanager.rs 221 24 89.14% 18 5 72.22% 109 20 81.65% 4 1 75.00% +crates/oak-codec/src/gpuinterop.rs 471 164 65.18% 34 7 79.41% 300 98 67.33% 36 18 50.00% +crates/oak-codec/src/hwdecode.rs 408 217 46.81% 28 14 50.00% 256 138 46.09% 12 5 58.33% +crates/oak-codec/src/lib.rs 24 14 41.67% 8 5 37.50% 18 10 44.44% 0 0 - +crates/oak-codec/src/oiio.rs 239 9 96.23% 35 4 88.57% 200 6 97.00% 0 0 - +crates/oak-codec/src/oiioframebridge.rs 394 7 98.22% 12 0 100.00% 240 4 98.33% 20 5 75.00% +crates/oak-codec/src/planarfiledevice.rs 338 4 98.82% 16 1 93.75% 154 4 97.40% 16 2 87.50% +crates/oak-codec/src/proxymanager.rs 1351 713 47.22% 99 50 49.49% 814 427 47.54% 58 23 60.34% +crates/oak-codec/src/realmedia_tests.rs 824 39 95.27% 28 4 85.71% 348 17 95.11% 20 9 55.00% +crates/oak-codec/src/task.rs 374 189 49.47% 26 13 50.00% 280 141 49.64% 2 0 100.00% +crates/oak-codec/src/testmedia.rs 236 18 92.37% 7 2 71.43% 114 5 95.61% 20 13 35.00% +crates/oak-codec/src/timecodemetadata.rs 363 14 96.14% 27 1 96.30% 203 4 98.03% 34 6 82.35% +crates/oak-core/src/backend.rs 5977 2997 49.86% 279 142 49.10% 4941 2423 50.96% 178 97 45.51% +crates/oak-core/src/backend/external.rs 293 45 84.64% 11 2 81.82% 268 28 89.55% 18 10 44.44% +crates/oak-core/src/cancelatom.rs 58 2 96.55% 9 1 88.89% 38 1 97.37% 2 0 100.00% +crates/oak-core/src/color.rs 2394 1360 43.19% 175 105 40.00% 1381 757 45.18% 122 64 47.54% +crates/oak-core/src/colormath.rs 2904 1335 54.03% 171 76 55.56% 1570 688 56.18% 66 13 80.30% +crates/oak-core/src/colortransform.rs 132 0 100.00% 13 0 100.00% 77 0 100.00% 0 0 - +crates/oak-core/src/commandlineparser.rs 1245 18 98.55% 59 2 96.61% 517 7 98.65% 26 0 100.00% +crates/oak-core/src/commonutil.rs 70 5 92.86% 10 2 80.00% 40 2 95.00% 4 3 25.00% +crates/oak-core/src/configstore.rs 5199 2469 52.51% 206 97 52.91% 2151 1000 53.51% 118 15 87.29% +crates/oak-core/src/debug.rs 415 181 56.39% 38 16 57.89% 270 116 57.04% 6 1 83.33% +crates/oak-core/src/displayicc.rs 1416 824 41.81% 78 47 39.74% 802 462 42.39% 66 16 75.76% +crates/oak-core/src/error.rs 67 3 95.52% 7 1 85.71% 47 3 93.62% 0 0 - +crates/oak-core/src/ffmpegutils.rs 296 1 99.66% 27 0 100.00% 258 1 99.61% 14 1 92.86% +crates/oak-core/src/filefunctions.rs 1784 736 58.74% 81 29 64.20% 911 358 60.70% 72 21 70.83% +crates/oak-core/src/frame.rs 68 2 97.06% 8 0 100.00% 54 2 96.30% 8 4 50.00% +crates/oak-core/src/handle.rs 58 40 31.03% 13 10 23.08% 58 37 36.21% 8 8 0.00% +crates/oak-core/src/lib.rs 3 0 100.00% 1 0 100.00% 3 0 100.00% 0 0 - +crates/oak-core/src/lut.rs 227 6 97.36% 14 1 92.86% 120 6 95.00% 2 1 50.00% +crates/oak-core/src/miscutils.rs 472 9 98.09% 41 4 90.24% 244 4 98.36% 16 1 93.75% +crates/oak-core/src/ocioutils.rs 309 124 59.87% 34 18 47.06% 197 66 66.50% 0 0 - +crates/oak-core/src/oiioutils.rs 601 34 94.34% 38 4 89.47% 318 19 94.03% 20 2 90.00% +crates/oak-core/src/qtutils.rs 197 10 94.92% 20 1 95.00% 109 5 95.41% 6 3 50.00% +crates/oak-core/src/rational.rs 453 27 94.04% 29 0 100.00% 283 19 93.29% 114 65 42.98% +crates/oak-core/src/samplefmt.rs 46 7 84.78% 6 0 100.00% 42 7 83.33% 2 2 0.00% +crates/oak-core/src/subtitleparams.rs 2368 1021 56.88% 111 43 61.26% 1322 568 57.03% 122 24 80.33% +crates/oak-core/src/texture.rs 589 263 55.35% 57 19 66.67% 412 159 61.41% 24 11 54.17% +crates/oak-core/src/timerange.rs 190 4 97.89% 22 0 100.00% 131 3 97.71% 36 36 0.00% +crates/oak-core/src/videoparams.rs 2772 179 93.54% 171 7 95.91% 1581 99 93.74% 298 83 72.15% +crates/oak-core/src/xmlutils.rs 1664 18 98.92% 79 0 100.00% 773 12 98.45% 100 18 82.00% +crates/oak-core/tests/core_test.rs 637 0 100.00% 14 0 100.00% 256 0 100.00% 0 0 - +crates/oak-ffmpeg-link/src/lib.rs 2 2 0.00% 1 1 0.00% 1 1 0.00% 0 0 - +crates/oak-node/src/block.rs 1396 617 55.80% 121 58 52.07% 972 442 54.53% 30 30 0.00% +crates/oak-node/src/colormanager.rs 62 5 91.94% 10 1 90.00% 55 5 90.91% 10 1 90.00% +crates/oak-node/src/error.rs 57 0 100.00% 6 0 100.00% 42 0 100.00% 0 0 - +crates/oak-node/src/factory.rs 104 21 79.81% 19 4 78.95% 72 14 80.56% 10 10 0.00% +crates/oak-node/src/folder.rs 133 30 77.44% 19 2 89.47% 105 22 79.05% 20 20 0.00% +crates/oak-node/src/footage.rs 1002 151 84.93% 76 17 77.63% 665 97 85.41% 104 104 0.00% +crates/oak-node/src/graph.rs 2049 942 54.03% 134 57 57.46% 1149 523 54.48% 108 108 0.00% +crates/oak-node/src/handle.rs 52 1 98.08% 6 0 100.00% 59 1 98.31% 8 3 62.50% +crates/oak-node/src/id.rs 25 0 100.00% 6 0 100.00% 23 0 100.00% 2 2 0.00% +crates/oak-node/src/input.rs 17 3 82.35% 4 1 75.00% 20 3 85.00% 0 0 - +crates/oak-node/src/jobs.rs 88 37 57.95% 16 6 62.50% 87 44 49.43% 0 0 - +crates/oak-node/src/keyframe.rs 606 297 50.99% 28 12 57.14% 299 147 50.84% 62 62 0.00% +crates/oak-node/src/node.rs 495 12 97.58% 87 2 97.70% 418 7 98.33% 36 34 5.56% +crates/oak-node/src/nodes/blur.rs 1011 478 52.72% 48 25 47.92% 531 243 54.24% 32 32 0.00% +crates/oak-node/src/nodes/checkerboard.rs 371 10 97.30% 19 2 89.47% 221 10 95.48% 2 2 0.00% +crates/oak-node/src/nodes/chromakey.rs 807 415 48.57% 39 23 41.03% 485 254 47.63% 0 0 - +crates/oak-node/src/nodes/clamp.rs 293 5 98.29% 19 1 94.74% 170 5 97.06% 2 2 0.00% +crates/oak-node/src/nodes/colorbars.rs 293 7 97.61% 19 1 94.74% 179 7 96.09% 2 2 0.00% +crates/oak-node/src/nodes/colorcorrect.rs 394 5 98.73% 20 1 95.00% 233 5 97.85% 2 2 0.00% +crates/oak-node/src/nodes/colordifferencekey.rs 569 301 47.10% 37 22 40.54% 359 193 46.24% 0 0 - +crates/oak-node/src/nodes/colormatrix.rs 351 7 98.01% 22 1 95.45% 191 7 96.34% 4 4 0.00% +crates/oak-node/src/nodes/compositesource.rs 140 8 94.29% 13 2 84.62% 86 7 91.86% 2 2 0.00% +crates/oak-node/src/nodes/cornerpindistortnode.rs 932 496 46.78% 60 35 41.67% 566 305 46.11% 10 10 0.00% +crates/oak-node/src/nodes/cropdistortnode.rs 645 228 64.65% 35 15 57.14% 385 114 70.39% 8 8 0.00% +crates/oak-node/src/nodes/despill.rs 488 265 45.70% 34 20 41.18% 314 170 45.86% 0 0 - +crates/oak-node/src/nodes/dilate.rs 310 5 98.39% 22 1 95.45% 184 5 97.28% 2 2 0.00% +crates/oak-node/src/nodes/dirblur.rs 350 4 98.86% 23 1 95.65% 203 4 98.03% 0 0 - +crates/oak-node/src/nodes/displaytransform.rs 724 377 47.93% 62 34 45.16% 446 235 47.31% 8 8 0.00% +crates/oak-node/src/nodes/dissolve.rs 405 4 99.01% 26 1 96.15% 228 4 98.25% 0 0 - +crates/oak-node/src/nodes/dropshadowfilter.rs 531 196 63.09% 28 11 60.71% 300 103 65.67% 4 4 0.00% +crates/oak-node/src/nodes/edgedetect.rs 277 4 98.56% 22 1 95.45% 171 4 97.66% 0 0 - +crates/oak-node/src/nodes/erode.rs 310 5 98.39% 22 1 95.45% 184 5 97.28% 2 2 0.00% +crates/oak-node/src/nodes/flipdistortnode.rs 511 212 58.51% 30 12 60.00% 276 104 62.32% 4 4 0.00% +crates/oak-node/src/nodes/gamma.rs 294 5 98.30% 20 1 95.00% 176 5 97.16% 2 2 0.00% +crates/oak-node/src/nodes/generatorwithmerge.rs 199 27 86.43% 7 1 85.71% 146 17 88.36% 0 0 - +crates/oak-node/src/nodes/grade.rs 355 5 98.59% 19 1 94.74% 211 5 97.63% 2 2 0.00% +crates/oak-node/src/nodes/graphendpoints.rs 361 10 97.23% 25 2 92.00% 213 8 96.24% 6 6 0.00% +crates/oak-node/src/nodes/group.rs 221 70 68.33% 30 4 86.67% 179 45 74.86% 18 18 0.00% +crates/oak-node/src/nodes/invert.rs 294 5 98.30% 18 1 94.44% 200 5 97.50% 2 2 0.00% +crates/oak-node/src/nodes/keymix.rs 333 4 98.80% 23 1 95.65% 190 4 97.89% 0 0 - +crates/oak-node/src/nodes/mask.rs 712 365 48.74% 33 19 42.42% 453 233 48.57% 2 2 0.00% +crates/oak-node/src/nodes/math.rs 950 509 46.42% 54 32 40.74% 592 320 45.95% 4 4 0.00% +crates/oak-node/src/nodes/mathbase.rs 3493 1772 49.27% 136 65 52.21% 2358 1198 49.19% 90 90 0.00% +crates/oak-node/src/nodes/matrix.rs 844 269 68.13% 48 16 66.67% 508 151 70.28% 18 18 0.00% +crates/oak-node/src/nodes/merge.rs 523 270 48.37% 39 22 43.59% 329 173 47.42% 0 0 - +crates/oak-node/src/nodes/mirror.rs 346 10 97.11% 19 2 89.47% 198 8 95.96% 4 4 0.00% +crates/oak-node/src/nodes/mod.rs 254 112 55.91% 8 7 12.50% 138 66 52.17% 0 0 - +crates/oak-node/src/nodes/mosaicfilternode.rs 365 141 61.37% 26 10 61.54% 230 83 63.91% 2 2 0.00% +crates/oak-node/src/nodes/multicamnode.rs 1024 550 46.29% 80 44 45.00% 630 339 46.19% 32 32 0.00% +crates/oak-node/src/nodes/noise.rs 373 148 60.32% 26 10 61.54% 228 82 64.04% 0 0 - +crates/oak-node/src/nodes/ociobase.rs 162 5 96.91% 13 2 84.62% 111 3 97.30% 2 2 0.00% +crates/oak-node/src/nodes/ociogradingtransformlinear.rs 1934 1017 47.41% 85 48 43.53% 1238 671 45.80% 26 26 0.00% +crates/oak-node/src/nodes/ociogradingtransformlog.rs 1779 948 46.71% 84 47 44.05% 1145 634 44.63% 26 26 0.00% +crates/oak-node/src/nodes/ociolut.rs 1464 795 45.70% 87 48 44.83% 883 483 45.30% 26 26 0.00% +crates/oak-node/src/nodes/opacity.rs 482 136 71.78% 31 12 61.29% 326 113 65.34% 6 6 0.00% +crates/oak-node/src/nodes/pan.rs 665 283 57.44% 29 12 58.62% 341 145 57.48% 14 14 0.00% +crates/oak-node/src/nodes/plugin.rs 1167 599 48.67% 97 51 47.42% 726 374 48.48% 22 22 0.00% +crates/oak-node/src/nodes/polygon.rs 599 245 59.10% 30 12 60.00% 346 134 61.27% 4 4 0.00% +crates/oak-node/src/nodes/position.rs 262 8 96.95% 19 2 89.47% 164 8 95.12% 4 4 0.00% +crates/oak-node/src/nodes/premult.rs 325 4 98.77% 23 1 95.65% 192 4 97.92% 0 0 - +crates/oak-node/src/nodes/ramp.rs 365 10 97.26% 19 2 89.47% 221 10 95.48% 2 2 0.00% +crates/oak-node/src/nodes/rippledistortnode.rs 632 332 47.47% 42 25 40.48% 404 215 46.78% 2 2 0.00% +crates/oak-node/src/nodes/saturation.rs 294 5 98.30% 20 1 95.00% 180 5 97.22% 2 2 0.00% +crates/oak-node/src/nodes/shapenode.rs 744 392 47.31% 40 23 42.50% 505 269 46.73% 8 8 0.00% +crates/oak-node/src/nodes/shapenodebase.rs 96 43 55.21% 11 5 54.55% 61 27 55.74% 0 0 - +crates/oak-node/src/nodes/sharpen.rs 270 4 98.52% 21 1 95.24% 167 4 97.60% 0 0 - +crates/oak-node/src/nodes/solid.rs 248 100 59.68% 24 9 62.50% 165 59 64.24% 0 0 - +crates/oak-node/src/nodes/stroke.rs 563 216 61.63% 30 12 60.00% 299 103 65.55% 4 4 0.00% +crates/oak-node/src/nodes/swirldistortnode.rs 683 351 48.61% 40 22 45.00% 402 208 48.26% 4 4 0.00% +crates/oak-node/src/nodes/textbackend.rs 119 50 57.98% 10 3 70.00% 131 58 55.73% 0 0 - +crates/oak-node/src/nodes/textv1.rs 886 466 47.40% 52 30 42.31% 528 281 46.78% 2 2 0.00% +crates/oak-node/src/nodes/textv2.rs 1138 594 47.80% 60 34 43.33% 714 375 47.48% 2 2 0.00% +crates/oak-node/src/nodes/textv3.rs 3217 952 70.41% 160 55 65.62% 1837 592 67.77% 78 78 0.00% +crates/oak-node/src/nodes/threewaycolor.rs 613 315 48.61% 37 21 43.24% 399 208 47.87% 0 0 - +crates/oak-node/src/nodes/tiledistortnode.rs 676 372 44.97% 44 27 38.64% 438 247 43.61% 2 2 0.00% +crates/oak-node/src/nodes/timeformat.rs 605 13 97.85% 30 2 93.33% 337 10 97.03% 46 46 0.00% +crates/oak-node/src/nodes/timeinput.rs 110 6 94.55% 12 2 83.33% 71 6 91.55% 0 0 - +crates/oak-node/src/nodes/timeoffsetnode.rs 395 19 95.19% 29 3 89.66% 235 17 92.77% 6 6 0.00% +crates/oak-node/src/nodes/timeremap.rs 337 18 94.66% 25 3 88.00% 212 17 91.98% 4 4 0.00% +crates/oak-node/src/nodes/transformdistortnode.rs 1327 721 45.67% 74 41 44.59% 928 499 46.23% 4 4 0.00% +crates/oak-node/src/nodes/transitionfx.rs 642 4 99.38% 33 1 96.97% 349 4 98.85% 4 4 0.00% +crates/oak-node/src/nodes/transitions.rs 505 4 99.21% 30 1 96.67% 271 4 98.52% 0 0 - +crates/oak-node/src/nodes/trigonometry.rs 598 267 55.35% 30 13 56.67% 331 141 57.40% 0 0 - +crates/oak-node/src/nodes/unpremult.rs 306 4 98.69% 22 1 95.45% 184 4 97.83% 0 0 - +crates/oak-node/src/nodes/valuenode.rs 347 150 56.77% 24 10 58.33% 206 88 57.28% 8 8 0.00% +crates/oak-node/src/nodes/volume.rs 657 277 57.84% 29 12 58.62% 337 141 58.16% 6 6 0.00% +crates/oak-node/src/nodes/wavedistortnode.rs 576 303 47.40% 40 23 42.50% 358 191 46.65% 2 2 0.00% +crates/oak-node/src/nodes/whitebalance.rs 730 379 48.08% 44 24 45.45% 433 226 47.81% 8 8 0.00% +crates/oak-node/src/ops.rs 508 397 21.85% 44 39 11.36% 290 229 21.03% 26 16 38.46% +crates/oak-node/src/project.rs 735 421 42.72% 51 30 41.18% 440 254 42.27% 20 20 0.00% +crates/oak-node/src/sequence.rs 183 9 95.08% 17 1 94.12% 149 7 95.30% 12 12 0.00% +crates/oak-node/src/serializer.rs 2046 794 61.19% 116 44 62.07% 1204 493 59.05% 164 164 0.00% +crates/oak-node/src/track.rs 427 21 95.08% 51 2 96.08% 322 18 94.41% 48 42 12.50% +crates/oak-node/src/traverser.rs 1638 338 79.37% 87 32 63.22% 818 189 76.89% 66 66 0.00% +crates/oak-node/src/value.rs 1205 648 46.22% 69 31 55.07% 793 420 47.04% 30 30 0.00% +crates/oak-node/tests/engine_units_test.rs 1428 27 98.11% 29 9 68.97% 677 22 96.75% 2 1 50.00% +crates/oak-node/tests/factory_smoke_test.rs 102 51 50.00% 6 3 50.00% 44 22 50.00% 0 0 - +crates/oak-node/tests/graph_test.rs 656 10 98.48% 17 3 82.35% 184 9 95.11% 28 14 50.00% +crates/oak-node/tests/phase2_units_test.rs 1112 13 98.83% 23 3 86.96% 581 10 98.28% 0 0 - +crates/oak-node/tests/serializer_test.rs 2736 1214 55.63% 118 55 53.39% 1739 777 55.32% 0 0 - +crates/oak-node/tests/structure_test.rs 725 299 58.76% 18 8 55.56% 419 177 57.76% 0 0 - +crates/oak-node/tests/traverser_test.rs 554 71 87.18% 39 21 46.15% 278 65 76.62% 0 0 - +crates/oak-node/tests/value_keyframe_test.rs 341 8 97.65% 10 0 100.00% 202 4 98.02% 0 0 - +crates/oak-otio/src/error.rs 74 2 97.30% 4 0 100.00% 36 0 100.00% 0 0 - +crates/oak-otio/src/fcpxml.rs 3009 1165 61.28% 136 50 63.24% 1535 551 64.10% 148 47 68.24% +crates/oak-otio/src/lib.rs 11 2 81.82% 2 0 100.00% 6 0 100.00% 0 0 - +crates/oak-otio/src/model.rs 920 88 90.43% 99 7 92.93% 634 53 91.64% 18 10 44.44% +crates/oak-otio/tests/fcpxml.rs 1384 719 48.05% 28 14 50.00% 720 366 49.17% 0 0 - +crates/oak-otio/tests/parity.rs 65 3 95.38% 11 3 72.73% 39 3 92.31% 0 0 - +crates/oak-otio/tests/save_parity.rs 100 0 100.00% 3 0 100.00% 62 0 100.00% 0 0 - +crates/oak-otio/tests/semantic.rs 225 12 94.67% 5 0 100.00% 102 3 97.06% 0 0 - +crates/oak-plugin/src/clip.rs 387 153 60.47% 17 7 58.82% 252 90 64.29% 0 0 - +crates/oak-plugin/src/descriptor.rs 89 9 89.89% 9 3 66.67% 49 8 83.67% 0 0 - +crates/oak-plugin/src/error.rs 60 0 100.00% 6 0 100.00% 38 0 100.00% 0 0 - +crates/oak-plugin/src/gl_bridge.rs 437 383 12.36% 44 32 27.27% 225 175 22.22% 0 0 - +crates/oak-plugin/src/host.rs 2578 1421 44.88% 143 90 37.06% 1433 792 44.73% 0 0 - +crates/oak-plugin/src/image.rs 432 29 93.29% 23 1 95.65% 253 19 92.49% 0 0 - +crates/oak-plugin/src/instance.rs 1553 759 51.13% 94 58 38.30% 1077 565 47.54% 0 0 - +crates/oak-plugin/src/lib.rs 15 1 93.33% 1 0 100.00% 13 1 92.31% 2 0 100.00% +crates/oak-plugin/src/node.rs 580 26 95.52% 36 7 80.56% 422 14 96.68% 0 0 - +crates/oak-plugin/src/node_factory.rs 1986 829 58.26% 101 48 52.48% 1146 450 60.73% 0 0 - +crates/oak-plugin/src/param.rs 828 122 85.27% 39 12 69.23% 456 60 86.84% 0 0 - +crates/oak-plugin/src/param_curve.rs 812 17 97.91% 43 0 100.00% 474 12 97.47% 0 0 - +crates/oak-plugin/src/progress.rs 107 49 54.21% 17 7 58.82% 78 32 58.97% 0 0 - +crates/oak-plugin/src/property.rs 98 2 97.96% 19 1 94.74% 69 1 98.55% 0 0 - +crates/oak-plugin/src/render.rs 226 31 86.28% 20 10 50.00% 140 38 72.86% 0 0 - +crates/oak-plugin/src/render_driver.rs 574 229 60.10% 30 13 56.67% 376 164 56.38% 0 0 - +crates/oak-plugin/src/suites/draw.rs 400 7 98.25% 32 1 96.88% 232 5 97.84% 0 0 - +crates/oak-plugin/src/suites/gl_render.rs 848 776 8.49% 65 60 7.69% 569 522 8.26% 0 0 - +crates/oak-plugin/src/suites/image_effect.rs 1043 580 44.39% 86 46 46.51% 725 387 46.62% 0 0 - +crates/oak-plugin/src/suites/interact.rs 1229 506 58.83% 60 18 70.00% 730 315 56.85% 0 0 - +crates/oak-plugin/src/suites/memory.rs 125 50 60.00% 18 8 55.56% 104 45 56.73% 0 0 - +crates/oak-plugin/src/suites/message.rs 293 12 95.90% 24 5 79.17% 195 7 96.41% 0 0 - +crates/oak-plugin/src/suites/mod.rs 441 207 53.06% 35 13 62.86% 275 138 49.82% 0 0 - +crates/oak-plugin/src/suites/multithread.rs 399 224 43.86% 56 31 44.64% 272 152 44.12% 0 0 - +crates/oak-plugin/src/suites/param.rs 1995 1134 43.16% 131 72 45.04% 1292 715 44.66% 0 0 - +crates/oak-plugin/src/suites/parametric.rs 1305 391 70.04% 75 27 64.00% 749 238 68.22% 0 0 - +crates/oak-plugin/src/suites/progress.rs 153 30 80.39% 29 6 79.31% 116 25 78.45% 0 0 - +crates/oak-plugin/src/suites/property.rs 1064 537 49.53% 97 41 57.73% 747 352 52.88% 0 0 - +crates/oak-plugin/src/suites/timeline.rs 136 15 88.97% 20 4 80.00% 101 8 92.08% 0 0 - +crates/oak-plugin/tests/colour_test.rs 155 10 93.55% 15 0 100.00% 109 7 93.58% 0 0 - +crates/oak-plugin/tests/common/mod.rs 158 100 36.71% 16 11 31.25% 106 67 36.79% 6 3 50.00% +crates/oak-plugin/tests/gl_render_test.rs 474 378 20.25% 27 21 22.22% 301 239 20.60% 9 4 55.56% +crates/oak-plugin/tests/golden_test.rs 12 12 0.00% 6 6 0.00% 12 12 0.00% 0 0 - +crates/oak-plugin/tests/handle_test.rs 35 0 100.00% 3 0 100.00% 23 0 100.00% 0 0 - +crates/oak-plugin/tests/interact_test.rs 442 184 58.37% 31 4 87.10% 249 96 61.45% 13 6 53.85% +crates/oak-plugin/tests/negotiation_test.rs 204 25 87.75% 18 0 100.00% 137 16 88.32% 16 8 50.00% +crates/oak-plugin/tests/node_e2e_test.rs 752 368 51.06% 41 21 48.78% 450 232 48.44% 0 0 - +crates/oak-plugin/tests/ofx_roundtrip.rs 74 69 6.76% 5 4 20.00% 35 30 14.29% 0 0 - +crates/oak-plugin/tests/property_test.rs 314 4 98.73% 12 0 100.00% 114 0 100.00% 0 0 - +crates/oak-plugin/tests/push_button_test.rs 74 6 91.89% 2 0 100.00% 33 4 87.88% 0 0 - +crates/oak-plugin/tests/suites_test.rs 1570 787 49.87% 40 20 50.00% 913 458 49.84% 0 0 - +crates/oak-render/src/autocacher.rs 891 397 55.44% 80 38 52.50% 538 237 55.95% 22 8 63.64% +crates/oak-render/src/cache.rs 1166 166 85.76% 72 13 81.94% 594 93 84.34% 88 28 68.18% +crates/oak-render/src/copier.rs 131 27 79.39% 19 3 84.21% 113 31 72.57% 8 3 62.50% +crates/oak-render/src/error.rs 36 0 100.00% 3 0 100.00% 26 0 100.00% 0 0 - +crates/oak-render/src/eval.rs 9865 5441 44.85% 450 261 42.00% 5742 3311 42.34% 510 288 43.53% +crates/oak-render/src/frameio.rs 324 24 92.59% 20 4 80.00% 188 20 89.36% 28 12 57.14% +crates/oak-render/src/handle.rs 132 52 60.61% 12 4 66.67% 87 29 66.67% 6 1 83.33% +crates/oak-render/src/ipc.rs 3672 1819 50.46% 203 101 50.25% 2282 1111 51.31% 66 22 66.67% +crates/oak-render/src/manager.rs 1005 550 45.27% 77 46 40.26% 661 360 45.54% 52 19 63.46% +crates/oak-render/src/ofxhost.rs 791 106 86.60% 52 12 76.92% 544 75 86.21% 68 39 42.65% +crates/oak-render/src/pipeline.rs 1226 41 96.66% 78 9 88.46% 807 25 96.90% 108 59 45.37% +crates/oak-render/src/procpool.rs 4622 2718 41.19% 264 156 40.91% 3059 1792 41.42% 264 241 8.71% +crates/oak-render/src/scheduler.rs 1689 893 47.13% 98 55 43.88% 925 495 46.49% 46 7 84.78% +crates/oak-render/src/shaderfx.rs 1839 132 92.82% 65 7 89.23% 969 75 92.26% 124 43 65.32% +crates/oak-render/src/textengine.rs 856 17 98.01% 48 2 95.83% 508 11 97.83% 108 32 70.37% +crates/oak-render/src/ticket.rs 1355 698 48.49% 117 61 47.86% 955 494 48.27% 26 13 50.00% +crates/oak-render/src/worker.rs 1227 651 46.94% 98 61 37.76% 822 438 46.72% 38 10 73.68% +crates/oak-render/tests/adjustment_layer.rs 378 4 98.94% 9 0 100.00% 223 2 99.10% 0 0 - +crates/oak-render/tests/common/mod.rs 272 246 9.56% 51 47 7.84% 210 193 8.10% 2 2 0.00% +crates/oak-render/tests/copier_test.rs 313 156 50.16% 18 9 50.00% 196 98 50.00% 0 0 - +crates/oak-render/tests/footage_decode_test.rs 149 14 90.60% 6 1 83.33% 87 15 82.76% 0 0 - +crates/oak-render/tests/footage_import_test.rs 261 26 90.04% 8 2 75.00% 141 17 87.94% 24 12 50.00% +crates/oak-render/tests/graph_render.rs 1566 698 55.43% 40 17 57.50% 962 389 59.56% 26 12 53.85% +crates/oak-render/tests/ofxmisc_blur.rs 392 13 96.68% 20 0 100.00% 152 0 100.00% 0 0 - +crates/oak-render/tests/ofxmisc_color.rs 318 15 95.28% 15 0 100.00% 166 12 92.77% 16 8 50.00% +crates/oak-render/tests/ofxmisc_gen.rs 367 15 95.91% 15 0 100.00% 173 12 93.06% 0 0 - +crates/oak-render/tests/ofxmisc_matrix.rs 357 13 96.36% 17 0 100.00% 120 0 100.00% 0 0 - +crates/oak-render/tests/ofxmisc_merge.rs 301 17 94.35% 15 0 100.00% 136 14 89.71% 0 0 - +crates/oak-render/tests/pipeline_test.rs 224 7 96.88% 4 0 100.00% 121 7 94.21% 0 0 - +crates/oak-render/tests/render_threads_test.rs 1674 72 95.70% 91 19 79.12% 996 37 96.29% 0 0 - +crates/oak-render/tests/text_outline_glow.rs 331 34 89.73% 17 2 88.24% 127 12 90.55% 0 0 - +crates/oak-render/tests/ticket_worker_test.rs 638 301 52.82% 37 15 59.46% 296 131 55.74% 8 2 75.00% +crates/oak-render/tests/transition_render.rs 696 19 97.27% 12 0 100.00% 397 7 98.24% 26 9 65.38% +crates/oak-render/tests/transitionfx.rs 420 17 95.95% 16 0 100.00% 156 0 100.00% 18 9 50.00% +crates/oak-storage/src/backend.rs 9 0 100.00% 3 0 100.00% 18 0 100.00% 0 0 - +crates/oak-storage/src/backends/database/migration.rs 25 5 80.00% 3 1 66.67% 17 6 64.71% 0 0 - +crates/oak-storage/src/backends/database/mod.rs 3700 2015 45.54% 247 136 44.94% 2220 1155 47.97% 0 0 - +crates/oak-storage/src/backends/mod.rs 6 0 100.00% 1 0 100.00% 6 0 100.00% 0 0 - +crates/oak-storage/src/backends/otio.rs 749 243 67.56% 60 16 73.33% 450 147 67.33% 0 0 - +crates/oak-storage/src/backends/ove_xml.rs 137 17 87.59% 14 3 78.57% 86 9 89.53% 0 0 - +crates/oak-storage/src/error.rs 75 0 100.00% 6 0 100.00% 47 0 100.00% 0 0 - +crates/oak-storage/src/nodeutil.rs 106 8 92.45% 9 2 77.78% 72 4 94.44% 0 0 - +crates/oak-storage/src/registry.rs 199 32 83.92% 31 11 64.52% 133 31 76.69% 0 0 - +crates/oak-storage/src/session.rs 14 0 100.00% 4 0 100.00% 12 0 100.00% 0 0 - +crates/oak-storage/src/uri.rs 172 3 98.26% 12 0 100.00% 96 1 98.96% 0 0 - +crates/oak-storage/src/writethrough.rs 509 440 13.56% 54 46 14.81% 296 255 13.85% 0 0 - +crates/oak-storage/tests/common/mod.rs 711 84 88.19% 53 15 71.70% 432 56 87.04% 0 0 - +crates/oak-storage/tests/database_pg_test.rs 1444 1382 4.29% 79 64 18.99% 750 701 6.53% 0 0 - +crates/oak-storage/tests/database_test.rs 3313 1431 56.81% 127 50 60.63% 1648 708 57.04% 0 0 - +crates/oak-storage/tests/storage_test.rs 2267 983 56.64% 98 42 57.14% 1172 505 56.91% 12 3 75.00% +crates/oak-task/src/codecbridge.rs 95 95 0.00% 9 9 0.00% 58 58 0.00% 0 0 - +crates/oak-task/src/conform.rs 133 102 23.31% 6 5 16.67% 88 69 21.59% 16 10 37.50% +crates/oak-task/src/customcache.rs 90 8 91.11% 11 3 72.73% 63 6 90.48% 8 1 87.50% +crates/oak-task/src/error.rs 73 0 100.00% 5 0 100.00% 47 0 100.00% 0 0 - +crates/oak-task/src/export.rs 728 455 37.50% 29 19 34.48% 481 302 37.21% 58 33 43.10% +crates/oak-task/src/handle.rs 51 23 54.90% 6 3 50.00% 51 20 60.78% 6 2 66.67% +crates/oak-task/src/manager.rs 266 266 0.00% 34 34 0.00% 175 175 0.00% 2 2 0.00% +crates/oak-task/src/nodeops.rs 2390 1832 23.35% 259 194 25.10% 1639 1260 23.12% 176 130 26.14% +crates/oak-task/src/precache.rs 69 69 0.00% 5 5 0.00% 41 41 0.00% 0 0 - +crates/oak-task/src/project/format.rs 16 1 93.75% 2 0 100.00% 13 3 76.92% 0 0 - +crates/oak-task/src/project/import.rs 460 460 0.00% 24 24 0.00% 274 274 0.00% 0 0 - +crates/oak-task/src/project/load.rs 51 51 0.00% 6 6 0.00% 39 39 0.00% 0 0 - +crates/oak-task/src/project/loadotio.rs 487 487 0.00% 15 15 0.00% 307 307 0.00% 0 0 - +crates/oak-task/src/project/save.rs 47 47 0.00% 4 4 0.00% 33 33 0.00% 0 0 - +crates/oak-task/src/project/saveotio.rs 874 532 39.13% 48 30 37.50% 512 323 36.91% 56 27 51.79% +crates/oak-task/src/proxy.rs 1356 725 46.53% 85 42 50.59% 838 450 46.30% 78 66 15.38% +crates/oak-task/src/render.rs 2002 1316 34.27% 110 72 34.55% 1415 919 35.05% 182 98 46.15% +crates/oak-task/src/task.rs 184 72 60.87% 26 10 61.54% 129 50 61.24% 16 11 31.25% +crates/oak-task/tests/bridge_test.rs 49 2 95.92% 4 0 100.00% 38 3 92.11% 2 1 50.00% +crates/oak-task/tests/customcache_test.rs 133 3 97.74% 7 1 85.71% 59 1 98.31% 0 0 - +crates/oak-task/tests/export_test.rs 197 8 95.94% 3 0 100.00% 151 3 98.01% 10 5 50.00% +crates/oak-task/tests/parity_test.rs 93 0 100.00% 6 0 100.00% 73 0 100.00% 0 0 - +crates/oak-task/tests/saveotio_test.rs 317 11 96.53% 7 0 100.00% 205 5 97.56% 0 0 - +crates/oak-timeline/src/common.rs 19 19 0.00% 3 3 0.00% 18 18 0.00% 0 0 - +crates/oak-timeline/src/error.rs 67 0 100.00% 5 0 100.00% 43 0 100.00% 0 0 - +crates/oak-timeline/src/handle.rs 69 22 68.12% 7 2 71.43% 55 17 69.09% 10 8 20.00% +crates/oak-timeline/src/marker.rs 566 147 74.03% 91 33 63.74% 426 125 70.66% 0 0 - +crates/oak-timeline/src/multicam.rs 2903 1483 48.91% 148 85 42.57% 1409 691 50.96% 0 0 - +crates/oak-timeline/src/undocommon.rs 84 24 71.43% 17 5 70.59% 62 15 75.81% 0 0 - +crates/oak-timeline/src/undogeneral.rs 2033 1288 36.65% 171 124 27.49% 1346 855 36.48% 0 0 - +crates/oak-timeline/src/undopointer.rs 573 396 30.89% 26 16 38.46% 444 326 26.58% 0 0 - +crates/oak-timeline/src/undoripple.rs 799 591 26.03% 54 44 18.52% 567 414 26.98% 0 0 - +crates/oak-timeline/src/undosplit.rs 382 96 74.87% 35 17 51.43% 236 68 71.19% 0 0 - +crates/oak-timeline/src/undotrack.rs 112 84 25.00% 24 18 25.00% 85 63 25.88% 0 0 - +crates/oak-timeline/src/util.rs 1866 1145 38.64% 208 128 38.46% 1020 624 38.82% 0 0 - +crates/oak-timeline/src/workarea.rs 205 66 67.80% 39 14 64.10% 157 47 70.06% 0 0 - +crates/oak-timeline/tests/domain_test.rs 1953 826 57.71% 49 22 55.10% 817 339 58.51% 0 0 - +crates/oak-timeline/tests/handle_test.rs 81 1 98.77% 6 0 100.00% 41 0 100.00% 0 0 - +crates/oak-timeline/tests/marker_test.rs 1299 658 49.35% 44 23 47.73% 616 308 50.00% 0 0 - +crates/oak-timeline/tests/workarea_test.rs 208 8 96.15% 12 1 91.67% 93 1 98.92% 0 0 - +crates/oak-undo/src/error.rs 66 9 86.36% 7 1 85.71% 51 9 82.35% 0 0 - +crates/oak-undo/src/global.rs 448 21 95.31% 54 8 85.19% 231 10 95.67% 0 0 - +crates/oak-undo/src/undocommand.rs 167 2 98.80% 40 0 100.00% 147 2 98.64% 0 0 - +crates/oak-undo/src/undostack.rs 160 7 95.62% 18 2 88.89% 127 4 96.85% 0 0 - +crates/oak-undo/tests/edge_coverage_test.rs 232 123 46.98% 34 24 29.41% 117 65 44.44% 0 0 - +crates/oak-undo/tests/undo_safe_test.rs 1017 498 51.03% 39 18 53.85% 392 186 52.55% 0 0 - +crates/oak-worker/src/framecache.rs 331 13 96.07% 24 4 83.33% 182 8 95.60% 12 4 66.67% +crates/oak-worker/src/main.rs 144 71 50.69% 11 5 54.55% 98 48 51.02% 8 2 75.00% +crates/oak-worker/src/ofx_host.rs 513 417 18.71% 26 20 23.08% 339 272 19.76% 68 65 4.41% +crates/oak-worker/src/worker.rs 4699 2534 46.07% 180 106 41.11% 2782 1533 44.90% 188 68 63.83% +crates/oak-worker/tests/ofx_host.rs 367 21 94.28% 23 7 69.57% 186 10 94.62% 6 3 50.00% +crates/oak-worker/tests/procpool_integration.rs 2627 1402 46.63% 126 89 29.37% 1686 876 48.04% 0 0 - +crates/oak-worker/tests/worker.rs 55 0 100.00% 3 0 100.00% 31 0 100.00% 0 0 - +examples/simple_player/src/main.rs 3 3 0.00% 1 1 0.00% 3 3 0.00% 0 0 - +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +TOTAL 362437 168169 53.60% 23749 11899 49.90% 214877 100668 53.15% 10714 7720 27.94% +``` diff --git a/docs/zh/plans/render-pipeline-threads.md b/docs/zh/plans/render-pipeline-threads.md index 71cc0b6a6..3c2b8764e 100644 --- a/docs/zh/plans/render-pipeline-threads.md +++ b/docs/zh/plans/render-pipeline-threads.md @@ -426,6 +426,127 @@ fallback。** 解码上传与上屏共用一层 `gpuinteop` 抽象,按后端 > 可真机验证;CI(lavapipe,无 hwaccel)只跑 fallback、计数器与 > 接口单测。 +> **M5 落地回填(2026-09-17)**: +> +> - **数据面**:`Texture` 新增瞬态变体 `Texture::Planar`(`PlanarFormat` +> = NV12/P010,携带 luma/chroma 两个 registry token、YUV 矩阵/范围、 +> 源 primaries/trc 与 keep-alive guard);`GpuContext::run_planar_yuv_to_rgb` +> 是 M2 三平面 pass 的双平面(NV12/P010)变体,8 位输入的窄范围展开由 +> `YuvTransform::from_matrix_depth(matrix, full_range, 8)` 精确给出 +> (16 位构造与原公式逐位一致)。 +> - **解码侧**(`oak-codec/src/gpuinterop.rs`):`try_import_hw_frame` 在 +> `transfer_to_cpu` **之前**尝试导入;计数器 `HW_IMPORTS` / +> `HW_IMPORT_FALLBACKS`,硬解导入路径 `HW_TRANSFERS` 保持 0;连续 8 次 +> 失败后进程内熔断(单帧失败不污染后续帧)。开关:全局 +> `GpuDecodeImport` + `OAK_GPU_IMPORT=0`,逐平台 +> `GpuDecodeImportVaapi` / `GpuDecodeImportD3d11` / +> `GpuDecodeImportVideotoolbox`(各自 `OAK_GPU_IMPORT_*` 环境变量)。 +> **只有 host 安装的共享设备**(`GpuContext::host_gpu_installed`,即 app +> 的上屏 device)才走导入:worker/CLI/测试里惰性创建的私有 device 继续 +> CPU 路径,避免把进程池/导出悄悄拉上 GPU。零拷贝只服务原生尺寸 + F32 +> 请求(不能缩放/改格式);代理尺寸等按帧回退 staging(解码器会话缓存 +> 复用同一帧,回退不重解)。 +> - **平台实现**: +> - **Linux(真机验证)**:VAAPI → `av_hwframe_map`(DRM_PRIME) → 每层 +> dup fd + `VkImportMemoryFdInfoKHR`(`VK_EXT_external_memory_dma_buf` +> + `VK_EXT_image_drm_format_modifier`,显式 plane layout)→ +> `wgpu_hal::vulkan::texture_from_raw` + `create_texture_from_hal`; +> 导入的 VkImage/内存由 wgpu-hal 的 drop callback 释放,AVFrame 的引用 +> 作为 guard 一并持有到 GPU 用完。NVDEC/CUDA 表面没有公开句柄导出,按 +> 帧回退 staging;因此 Linux 设备候选顺序改为 **VAAPI 优先、CUDA 兜底** +> (§3.6 的"优先通用 DMA-BUF"),本机 RTX 5070 Ti + nvidia-vaapi-driver +> 实测 NV12 block-linear modifier 导入可用。 +> - **Windows(cfg 隔离,待 CI 编译验证)**:D3D11VA 纹理经 +> `IDXGIResource1::CreateSharedHandle` 取 NT 句柄 → 手建多平面 +> NV12/P010 VkImage(`VK_KHR_external_memory_win32` 导入句柄)→ +> 同一纹理注册 `Plane0`/`Plane1` 两个 token;COM 侧在 oak-codec, +> Vulkan 侧在 oak-core。 +> - **macOS(cfg 隔离,待 CI 编译验证)**:VideoToolbox 的 CVPixelBuffer +> → `CVPixelBufferGetIOSurface` → `newTextureWithDescriptor:iosurface:plane:` +> 两个平面 Metal 纹理 → `wgpu_hal::metal::texture_from_raw`;Metal 纹理 +> 无 drop callback,帧引用经 `ImportedHwFrame::keep_alive` 挂在 +> `PlanarTexture` 上随纹理释放。 +> - **渲染侧**(`oak-render/eval.rs`):`render_footage_frame` 把 planar +> 变体在渲染线程解析为工作空间 RGBA —— YUV→RGB pass + 源→工作空间变换 +> (legacy sRGB 直通;ACEScg 下按 (primaries, trc) 把 +> `colormath::decode_to_acescg` 烘焙成 3D LUT 在 GPU 应用,缓存 16 个); +> 解析失败按帧回退 CPU staging(import 关掉重解一次,解码器缓存兜底)。 +> 图/蒙太奇/导出等消费者只会看到 `Gpu`/`Cpu`。 +> - **一个必要修复**:ffmpeg-next 的 `Video::clone` 用 `av_frame_copy` 深拷 +> 数据,**不会**复制硬件表面引用(克隆的 VAAPI 帧 transfer 报 EINVAL)。 +> 硬解帧缓存改用内部 `RefFrame`(`av_frame_ref` 语义)持有。 +> - **验收**:`oak-render/tests/footage_import_test.rs`(真机 RTX 5070 Ti): +> 导入路径 `HW_TRANSFERS==0`、结果 `Texture::Gpu`、与 staging 逐像素 +> max diff < 0.15(swscale 色度滤波差异,同硬解/软解测试口径);ACEScg +> 下与 CPU 精确变换 max diff 0.011(LUT 三线性插值);已知红蓝图案断言。 +> `gpuinterop` 单测覆盖"无 host device 不导入/开关关闭/位深";oak-core +> 单测覆盖 8/16 位变换系数;montage 原生尺寸 + host GPU 回归测试见下。 +> 全 workspace `cargo test --workspace` 全绿(全部测试套件)。 +> - **遗留(M5 未完全关闭)**:Windows(D3D11VA 共享句柄)/macOS +> (IOSurface)两行是 cfg 代码,**从未在对应平台编译过**;host 可达性 +> 与 Windows 数组 slice/feature 门已在桌面审查后修复(见下面"审计修复" +> 第 4 条),但平台编译与真机硬件测试仍待补;P010 与 HDR 元数据未单独 +> 实测;代理尺寸的 GPU 缩放导入(省掉 native→proxy 的回退)未立项。 +> 本节的"阶段性验收"不等于 M5 收官。 + + + +> **M5 审计修复(2026-09-17)**:对 M5 落地的自审发现并修复以下问题: +> +> 1. **montage 静默黑帧(严重,已实机复现并修复)**:`render_footage_frame` +> 可返回解析后的 `Texture::Gpu`,而 CPU 蒙太奇合成器只接 +> `Texture::Cpu`,其余 `_ => continue` 直接跳过该 clip——序列尺寸 == +> 素材原生尺寸 + host GPU 已装 + 硬解 + 线程管线(导出最常见情形)时 +> 整条序列输出全透明黑且无任何报错。修复:新增 +> `render_footage_frame_staged`(`DecodeRequest.allow_import=false` +> 进 cache key),montage 一律走 staging;合成器遇到意外的 `Gpu` +> 纹理显式回读而不是丢弃,`Planar` 则报错。回归测试 +> `footage_import_test::montage_native_size_with_host_gpu_composites_the_clip` +> (先按单素材原生尺寸导入并缓存 planar,再合成 montage,断言图案 + +> "无新导入 + 发生 staging transfer")。 +> 2. **计数器/熔断语义**:NVDEC 设备、平台开关关闭、不可导入的表面布局 +> 原本都被记成"失败"并喂给连续失败熔断(NVDEC 8 帧后熔断、 +> `HW_IMPORT_FALLBACKS` 无界增长)。现在平台导入是三态结果: +> `Imported / Unsupported / Failed`;只有真正尝试后失败才计 +> `HW_IMPORT_FALLBACKS` 并参与熔断,新增 `HW_IMPORT_UNSUPPORTED` +> 单独观测(含单测)。 +> 3. **硬件表面驻留**:eval 帧 LRU 只允许最多 +> `MAX_CACHED_PLANAR_FRAMES = 4` 个 planar 条目(每张经 keep-alive +> pin 一个解码器表面);淘汰只丢弃导入,解码器会话缓存仍在,不重解码。 +> 4. **平台状态如实标注 + 桌面审查修复(2026-09-17 二次)**: +> Windows/macOS 两行仍未在对应平台编译;符号级复核(对照本机 registry +> 里的 windows 0.62.2 / objc2-metal 0.3.2 / ash 0.38)发现并修复: +> - **macOS**:IOSurface/Metal 路径符号全部匹配(`RG8Unorm`/`RG16Unorm` +> 命名无误——审查中"应为小写 Rg"的说法经源码核对不成立); +> `newTextureWithDescriptor_iosurface_plane`、`texture_from_raw` +> 六参签名、`MTLStorageMode::Shared`、P010 MSB 容器与 +> `from_matrix_depth(16)` 均核对一致。**仍未编译/实测**。 +> - **Windows 三重失效**(能编译但不可用)已修: +> i. **不可达**:`install_shared` 唯一生产调用点(adopt gpui 设备) +> 只在 Linux/FreeBSD 编译,Windows/macOS 上 `host_gpu_installed()` +> 恒 false,导入路径是死代码。新增 +> `GpuContext::mark_host_context()` + `oakui::gpu::register_engine_context()`: +> 非 Linux 平台的窗口建立时把引擎自建的 shared 上下文登记为 host +> 设备(worker/CLI/测试不调用,保持 CPU 路径)。 +> ii. **feature 门互斥**:host 设备现在是引擎 `create()` 建的, +> 会按适配器能力请求 `TEXTURE_FORMAT_NV12/P010`;将来若仍是 +> adopt 设备而缺 feature,`import_d3d11_shared_texture` 的 +> State/Invalid 错误现在归类为 `Unsupported`(不计数、不熔断)。 +> iii. **array slice 丢帧**:FFmpeg D3D11VA 帧是表面数组的 slice +> (`data[1]` 在 0..ArraySize-1 循环),原实现只收 slice 0。 +> 现在导入按 `arrayLayers = ArraySize` 建 VkImage,registry 条目 +> 携带 `layer`(`GpuTexture.layer`),采样视图用 +> `base_array_layer`/`array_layer_count: 1` 选中该 slice;视图必须 +> 显式 `dimension: D2`(wgpu-core 按纹理层数推导 D2Array,不看 +> view range,否则与 planar pass 的 D2 布局校验冲突);UV token 的 +> 登记尺寸用 chroma 半尺寸(planar pass 由它推采样比例)。 +> - **二次桌面复核追加修复**:`DmaBufPlane` 的 re-export 曾漏 cfg +> (非 Linux 上 `cargo check` 直接 E0432);`import_iosurface_plane` +> 改为 `unsafe fn`(解引用调用方裸 IOSurface 指针,附 `# Safety`)。 +> 平台编译 + 真机实测仍是 M5 关闭的前置。 +> 5. **口径与小项**:"94 个测试目标"改称"全部测试套件";P010/HDR 未实测; +> Vulkan `initial_layout: UNDEFINED` 的首次 transition 属驱动依赖行为 +> (本机正确,异驱动出伪影时优先排查这里)。 ### 3.7 resolve 重写(M0a,独立先行) @@ -512,6 +633,20 @@ M1(与 M2 可并行);M4 依赖 M2;M5 依赖 M2(YUV→RGB pass 与互 **全程验收(用户要求)**:M5 完成、整个任务收官后,跑一轮**分支覆盖率** (branch coverage)测量并留档,作为管线改造整体的质量闸门。 +**阶段性测量已完成(2026-09-17,审计修复之前)**:nightly +`-Zcoverage-options=branch` 全 workspace 采集,项目源码(排除 vendored +gpui/ocio/标准库)TOTAL = 区域 53.60% / 函数 49.90% / 行 53.15% / +**分支 27.94%**;关键文件分支覆盖率:`pipeline.rs` 45.37%、`backend.rs` +45.51%、`texture.rs` 54.17%、`eval.rs` 43.53%、`gpuinterop.rs` 50.00% +(硬件导入的真机分支;平台错误回退分支单机走不到)。完整报告与采集/合并 +命令见 `docs/zh/plans/render-pipeline-threads-m5-branch-coverage.txt`。 +**该轮采集早于 2026-09-17 审计修复(montage/计数器/驻留),且 +Windows/macOS 行尚未在对应平台编译——它是阶段性质量快照,不构成 M5 +收官证明;M5 关闭以平台编译与真机平台测试完成为准,届时重测。** +另经 2026-09-17 复查:该留档的多对象合并被**陈旧测试二进制**拉低 +(同文件多对象 51% vs 干净对象 95%),干净重测的基线是 +**行 81.72% / 分支 55.45%**(见 `test-coverage-90-80.md` §4.1 勘误); +覆盖率门禁以干净对象集为准。 ## 5. 不变量与边界 diff --git a/docs/zh/plans/test-coverage-90-80-review.md b/docs/zh/plans/test-coverage-90-80-review.md new file mode 100644 index 000000000..0b7812109 --- /dev/null +++ b/docs/zh/plans/test-coverage-90-80-review.md @@ -0,0 +1,722 @@ +# 未提交测试 Review 报告:假测试与实现语义核查 + +> 审查对象:工作区全部未提交改动(HEAD `800011ef7`,2026-09-20 快照; +> 63 个修改文件 + 11 个新测试文件,+38180 / −254 行)。 +> 审查重点(用户指定):**假测试**、**修改实现语义以匹配测试的行为**。 +> 报告落笔:2026-09-22。配套计划文档:[`test-coverage-90-80.md`](test-coverage-90-80.md)。 +> +> 标注约定:**[实证]** = 审查会话直接核对过代码原文/手算过数值; +> **[子代理]** = 深查子代理报告、审查会话抽样复核过关键链条。 + +## 0. 结论摘要 + +**问题一:是否修改了实现语义以匹配测试?——未发现。** +全部 254 行删除/修改逐条核对:生产侧改动要么是有设计文档回填的 M5 +零拷贝功能开发,要么是与文档契约对齐的**真 bug 修复**(`ForceParams` +默认值、`clip.rs::store_output_image`),没有一处是"为了让测试通过而 +扭曲实现"。 + +**问题二:是否存在假测试?——大部分是真测试,但确认 4 组高危 + 10 项中危。** +最严重的不是传统意义的"无断言测试",而是两类更隐蔽的模式: + +1. **测试固化既有实现缺陷**:约 12 个 `undocommands_test.rs` / + `graphops.rs` 测试把 `block.rs` setter 语义互换缺陷的反向行为 + (roll 不 roll、slide 不 slide、负时间线坐标、块重叠)写成精确期望值 + (§3.1)——测试向实现投降,而非实现向契约收敛。 +2. **"环境容忍"逃逸路径**:`Ok|Err` 双向接受、饥饿即跳过、CI 恒跳过 + (§3.2–§3.4)——产品回归与环境污染不可区分,验收测试在门禁 job + 里静默空转。 + +统计:高危 4 组(含 1 个活的产品缺陷)、中危 10 项、低危约 12 项; +正面确认(合格项)9 项,见 §6。 + +## 1. 审查范围与方法 + +- **删除行全查**:`git diff` 的 254 行删除是既有实现语义变更的唯一可能 + 载体,逐条核对并手算验证数值等价性(§2)。 +- **新测试文件全文审查**:11 个 untracked 测试文件(约 6300 行)由三个 + 深查子代理全文通读,审查会话对全部高危论断做了原文复核。 +- **内联测试**(约 3 万行新增):模式扫描(静默 return、`let _ =` 丢弃 + 结果、`#[ignore]`、sleep、同义反复 matches!、Ok|Err 全收)+ 高风险区 + 抽样精读(`eval.rs`、`ffmpeg.rs`、`worker.rs`、`instance.rs`、 + `history.rs`、`dialogs.rs`、`controls.rs`)。 +- **交叉验证**:与 `test-coverage-90-80.md` §7.5 六批自述、 + `render-pipeline-threads.md` M5 回填逐条对账;CI 配置 + (`.github/workflows/ci.yml`)核对跳过条件在门禁 job 上是否成立。 +- 未重新运行测试套件(计划文档声称 3069 passed / 0 failed;静态审查 + 未发现与该声称矛盾之处)。 + +## 2. 生产语义核查:全部 254 行删除的定性 + +| 改动 | 定性 | 证据 | +|---|---|---| +| `oak-core/backend.rs`:YUV 矩阵常数(`65535/56064` 等)删除 | **合法泛化** [实证] | 新 `from_matrix_depth` 在 16-bit 下与旧常数逐位一致(`ay = max/(y_white−y_black) = 65535/56064`;full-range 分支 `(1.0, 0.0, 1.0, 32768/65535)` 亦一致),为 M5 NV12/P010 平面导入服务 | +| `oak-codec/hwdecode.rs`:Linux 设备顺序 CUDA 优先 → VAAPI 优先 | **有意行为变更** [实证] | CUDA/NVDEC 表面无公开句柄导出,零拷贝只能走 VAAPI DMA-BUF;`render-pipeline-threads.md` M5 回填记录了真机(RTX 5070 Ti + nvidia-vaapi-driver)验证 | +| `oak-codec/ffmpeg.rs`:hw transfer / 色彩范围判定 / 帧缓存删除 | **等价重构 + 真修复** [实证] | 范围判定抽成 `frame_colorimetry`(force 优先、YUVJ 判定、`AVCOL_RANGE_JPEG` 分支逐一保留,CPU/GPU 两路共享);`RefFrame`(`av_frame_ref` 语义)修复 ffmpeg-next `Video::clone` 用 `av_frame_copy` 深拷、丢失硬件表面引用(克隆 VAAPI 帧 transfer 报 EINVAL)的真 bug | +| `oak-task/render.rs`:`ForceParams` 去 derive、手写 `Default` | **真 bug 修复** [实证] | `derive(Default)` 给出 `force_format = 0`(= `PixelFormat::U8`),违反文档契约 `-1 = off`,会把 F32 管线推向 U8 并在 footage 帧上 panic;测试断言的是文档契约(`render_test.rs:348-351`,注释明示 "not 0 = U8"),非实现现状 | +| `oak-plugin/clip.rs`:`store_output_image` 重写 | **真 bug 修复** [实证] | 原实现把像素写入 `texture_get_frame` 的深拷贝,CPU 纹理的插件输出被静默丢弃;现在 CPU 直接回写 `f.data`、GPU 走 upload、Planar 显式报错,与 `render_driver::write_output_frame` 既有修复一致 | +| `oak-render/eval.rs`:`DECODED_FRAMES` LRU 值类型 `Frame`→`Texture`、`render_footage_frame_inner`→`_opts(allow_import)` | **M5 功能** [实证] | planar 条目独立上限 4(硬件表面驻留约束);resolve 失败按帧回退 CPU staging(解码器会话缓存兜底,不重解码) | +| `oak-render/pipeline.rs`:`DecodeRequest.allow_import` | **M5 功能** [实证] | montage 合成器(CPU 消费者)`false`、footage 路径 `true`,且入缓存键——正面落实计划 §5.2 "staged 请求不得命中导入缓存" 的 M5 审计项 | +| `oak-app/dialogs.rs` 测试期望 180→184 | **合法期望修正** [实证] | 高度 SpinBox 的 `SliderModel`(gpui_widgets)以 step=8、min=120 建网格,180 恰在 176/184 中点、向上吸附;控件自身测试已固化 "snapped to the model's step" 契约(`controls.rs:1654/1720`) | +| `oak-app/oakui/real.rs` / `worker.rs` / `node_factory.rs` / `ofx.rs` 删除行 | **测试改写**,见 §3.3 / §6 | 回放验收测试弱化(高危);其余为等价重构或断言保留 | + +**流程问题**:M5 功能开发(`gpuinterop.rs`、`backend/external.rs`、 +`PlanarTexture`、hwdecode 顺序)与六批覆盖率测试**混在同一未提交工作 +区**。计划 §9 承诺"覆盖率改造不得引入行为变更",混提使该不变量无法被 +review 验证——本次是人工逐行核对全部删除行才得出"功能与测试可分离" +的结论。建议分开提交。 + +## 3. 高危问题(合并前应处理) + +### 3.1 🔴 undopointer 族测试固化了一个活的产品缺陷 [实证 + 子代理] + +**根因**(已提交代码,本次 diff 未改动):`oak-node/src/block.rs:93-107` +的 `set_length_and_media_out` / `set_length_and_media_in` 相对其声称的 +C++ parity 语义**正好互换**: + +```rust +/// Set the length, keeping the media out anchored (C++ +/// `Block::set_length_and_media_out`): ... +pub fn set_length_and_media_out(&mut self, length: Rational) { + let out = self.in_() + self.length(); + self.range = TimeRange::new(out - length, out); // ← 实际锚定 OUT、移动 IN + self.media_in = self.range.in_(); // ← 时间线 in 写进 media_in(坐标空间混淆) +} +``` + +C++ Olive 的 `set_length_and_media_out` 是 IN 锚定、OUT(及 media_out) +移动。该互换同时违反三处成文契约:block.rs 自己的 C++ parity 注释、 +`oak-timeline/src/common.rs:39`("`k_trim_out`: trim the out point")、 +`graphops.rs:2703-2707`(roll_edit 文档:"the boundary moves without +disturbing anything else")。附带缺陷:`media_in := range.in_()` 把媒体 +入点写成时间线入点;真锚定 media_out 应为 `media_in = media_out - length`。 + +**净效果**(`undopointer.rs` 的机械移植在互换后的 setter 上运行): + +- **roll edit 不 roll**:`undocommands_test.rs:625-648` + `block_trim_roll_edit_resizes_adjacent_clip` 断言 first=[0,50)、 + second=[50,100) roll 到 25 后得 **first=[25,50)、second=[50,125)** + ——接缝 50 纹丝不动,first 的 IN 移动、second 的 OUT 外扩。真 roll + 应为 first=[0,25)、second=[25,100)。**无偏差标注**;doc 只描述 + "没有插 gap" 这一表层现象。 +- **slide 不 slide**:`:736-760` 断言被滑块不动([50,100)),两个邻居 + 反向膨胀,previous 出现**负时间线入点 [-20,50)**。 +- graphops 内联测试(`graphops.rs:6048-6071`)是**自觉固化**:NOTE 明说 + "Assert the actual outcome so a future semantic fix has to update this + expectation deliberately",并把 `media_in = -2` 写进期望——诚实且有价值 + (修复时强制改写),但 NOTE 自身措辞也不准(称锚定 follower 的 OUT, + 断言显示其 OUT 从 20 移到 18、锚定的是 IN)。 +- 数值例(与 graphops 测试一致):a=[0,10)、b=[10,20) roll 到 12 → + a=[-2,10)(负入点、media_in=-2)、b=[10,18),接缝仍在 10,b 尾部反被 + 截掉 2 秒。若 b 还有后继,会产生块重叠/留洞(`undopointer.rs:160-174` + 的补偿 gap 按 C++ 假设放在 `[out, out+diff)`,与实际移动方向矛盾)。 +- **缺陷是活的**:`real.rs:4772-4798` 把 UI 事件 + `TimelineEvent::ClipRollRequested` 直接接到 `graphops::roll_edit`。 +- 仓库内 `undosplit.rs:200-214`、`undoripple.rs:342-358`、 + `undogeneral.rs:865-872` 的作者显然发现了互换并做了补偿性选边 + 注释, + 唯独 undopointer 的 BlockTrim/TrackSlide 与 undogeneral 的 + Resize/InsertGaps 未补偿。[子代理] + +**加重项** [子代理]: + +- 4 处测试**恰好省略了唯一能暴露矛盾的断言**:`:593-620`(gap=[50,75) + 与 second=[50,100) 重叠,只断言序号不断言 span)、`:653-708` 两处 + (被 trim 块 redo 后实为 [-25,50),不断言)、`:861-889`(gap 与 tail + 重叠,tail 只断言 id)。 +- **全文件(1488 行、38 测试)无一处断言 trim/slide/ripple/split 后的 + media_in**,夹具一律 media_in=0、timeline in=0,使两个内容损坏缺陷 + 不可见:(a) `media_in := range.in_()`;(b) `undoripple.rs:342-346` + splice 修剪不动 media_in——删除 [25,50) 后余块 [25,75) 实际播放**被删 + 内容**而非后移内容。 +- `:565-588` 普通 trim 测试的注释把反转当规范写("Trim-out is + out-anchored: the clip's in shifts to 25"),与 `common.rs:39` 直接 + 矛盾,会误导后来者。 +- `:1108-1122` ResizeWithMediaIn:注释说 "keeping the out point fixed", + 断言却是 in 锚定、out 移动;实现与命令名 `WithMediaIn` 及其 doc + (`undogeneral.rs:105-107`)相反。 +- `:1173-1216` InsertGaps 两测试:既有 gap 向左生长越过插入点并与前块 + 重叠,注释把左向生长表述为预期,而 `undogeneral.rs:866-872` 在另一 + 命令里明说 out-anchored "would push the in point negative"。 + +**定性**:作为产品缺陷 **高**(指针编辑族整体反向 + 负坐标 + 重叠 + +内容损坏,挂在真实 UI 路径);作为测试问题 **中-高**(graphops 一处是 +诚实的 characterization test;undocommands_test 多处无标注固化且回避 +关键断言)。计划文档第四批"记录(未改生产代码)"只登记了 roll_edit +一条,实际波及面大得多。 + +**修复顺序建议**:先补 `RippleInfo` 公开构造器与 media_in 断言维度,再 +统一裁决 block.rs:93-107 的语义互换,届时上述期望值按 graphops NOTE 的 +意图 "deliberately" 改写。 + +### 3.2 🔴 `oak-task/tests/render_test.rs:932-951`:全批次唯一"永不失败"的测试 [实证] + +`private_dispatcher_renders_frames_and_audio_through_workers` 对两次真实 +渲染的结果做双向接受: + +```rust +match outcome { + Ok(()) => { /* 真断言:order、size、audio */ } + Err(error) => { + eprintln!("private dispatcher worker render unavailable: {error}; skipping"); + } +} +``` + +`f32_outcome` 同构。Err 分支不区分**环境缺失**(worker 池起不来)与 +**产品失败**(`"Frame render ticket failed"`、`shm_frame_to_texture` 的 +BGRA8/F32 转换回归、poll pump 超时、解码失败)——全部放行。一旦 worker +二进制存在,本测试**不可能失败**,恰好掩盖它名义上要测的 ShmFrame/ +ShmAudio 交付缝与 dispatcher teardown 的任何回归。 + +叠加 `:881-884` 的"缺二进制即 return"(`locate_oak_worker` 依赖 +`target/{debug,release}/oak-worker` 已存在,而 `cargo test -p oak-task` +不构建同 workspace 的 oak-worker 二进制):定向运行/干净 CI 下长期静默 +跳过,私有 dispatcher 路径**实际覆盖可能为零**——覆盖率数字虚高的典型 +来源。 + +**修法**:Err 分支按错误内容分流——环境性错误(pool 启动失败)才跳过, +产品性错误必须失败;或至少断言 `error` 匹配已知环境错误串。 + +### 3.3 🔴 `oak-app/oakui/real.rs`:三个回放验收测试的"饥饿即跳过→通过" [实证] + +program-window、display、source-window 三个验收测试均新增 +`eprintln! + return`(计为 pass)的逃逸路径: + +- program-window:`pumps == 2000 && filled == 0` 跳过;pump 上限 + 5000→15000。 +- display:`pumps >= 1200 && peak < 0 && slots == 0` 跳过;原"playhead + 60→120 间 peak 严格增长"的两点采样断言弱化为"任意时刻 advances > 0 + 一次";pump 上限 9000。 +- source-window:**跳过条件仅 `pumps == 2000`,不检查槽位数**(real.rs + 约 :16396)——注释声称 "skip only when the shared pool delivered + nothing",但若帧已交付而 `preview_slot_frame` 服务侧有 bug(candidate + 存在、hit 恒 None),同样被伪装成池饥饿跳过。program-window 至少有 + `filled == 0` 判别,此处没有。 + +**产品真坏(worker 永远交不出帧)与环境保护不可区分**——验收测试失去 +回归守卫功能。计划文档声称"聚焦运行仍严格断言",但**代码里没有任何 +机制区分聚焦/全量运行**,该声称不成立。 + +**复合隐患** [实证]:`WorkerBinGuard`(`real.rs:8044-8056`、 +`renderops.rs:3659-3671`)硬编码 `../../target/debug/oak-worker`: + +- coverage job 的 target 目录是 `target/llvm-cov-target/`,干净环境下 + `target/debug/oak-worker` 不存在; +- `procpool.rs:2402` `resolve_worker_bin` 对 `OAK_WORKER_BIN` **不检查 + 存在性**直接采用 → spawn 失败 → 槽位恒空 → 跳过路径必然触发; +- 即普通 CI(`cargo test --workspace` 会把 oak-worker 建到 + `target/debug`)能真跑,**coverage job 里这组验收测试静默空转**—— + 恰好在产出覆盖率数字的那次运行里。 + +对照:`procpool.rs:2922` 测试侧 `find_real_worker` 对 env 覆盖做了 +`p.exists()` 检查,守卫没有。 + +**修法**:跳过条件补"槽位已交付但未服务→必须失败"判别(source-window +尤其);给严格模式一个真实机制(如 CI 设 `OAK_STRICT_PLAYBACK=1` 时禁止 +跳过);`WorkerBinGuard` 改为从 `current_exe()` 推导(与 +`find_real_worker` 一致)。 + +### 3.4 🔴 CI 门禁 job 上永久跳过的两组测试 [实证] + +**(a) ICC 门控(`oak-core/color.rs`,17 处跳过)**:`system_icc()` +(color.rs:770-783)只探测固定系统路径(macOS ColorSync、Linux +colord/ghostscript),而 `ci.yml:46-53` 的 apt 列表**不含任何 ICC 包** +→ Linux CI(唯一聚合门禁 job)上恒 None → `display_icc_bgra8_never_outputs_black` +(文档自称"viewer 黑屏回归守卫")等测试**永久跳过**,只在非门禁的 +macOS 周更 job 上真跑。修复便宜:`apt-get install icc-profiles-free` +(或 colord),或仓库内置一个小 sRGB ICC 测试资产。 + +**(b) footage_import(`oak-render/tests/footage_import_test.rs`)** [子代理,关键链条已复核]: +CI 是 lavapipe、无 `/dev/dri`,VAAPI/CUDA 设备创建必败且负缓存进程级 +粘滞(`hwdecode.rs:197-201`)→ `HW_IMPORTS == 0` → 三个测试恒跳过、 +cargo 计为 passed、**零断言执行**。加重项: + +- `common/mod.rs:61-71` 的 `gpu_context_for_import()` 直接 `?` 返回 + None,**绕过了仓库自己的 `OAK_REQUIRE_GPU=1` 硬失败策略**(对照 + `backend.rs` 的 `gpu_or_skip`/`shared_gpu_or_skip` 会 panic;同目录 + `render_threads_test.rs:776/824` 用的是合规版本); +- 计划文档承诺"跳过不等于通过(报告里单列)"**未实现**——无 `#[ignore]`、 + 无 skip 上报机制,CI 报表里与真跑过的测试不可区分。 + +测试本体质量不错(见 §6),问题在门禁信号缺失。M5 真机验收已留档 +(`render-pipeline-threads-m5-branch-coverage.txt`),但 PR 门禁对该 +文件恒零信号,应在 CI 报告侧显式标注(warning annotation)或接入计划 +M4 的平台/真机 job。 + +## 4. 中危问题 + +| # | 位置 | 问题 | 标注 | +|---|---|---|---| +| M1 | `manager_test.rs:164-182` ↔ `manager.rs:84-99` | `init()` doc 声称 "Create the singleton **and register the codec task submitter**",实现只置 `codec_submitter_registered: false`;`register_codec_task_submitter` 全仓库**零生产调用点**(仅 codecbridge.rs:94 定义)。测试断言 `Some(false)` 把矛盾固化为"契约"。结合 codecbridge "submission is synchronous" 的过渡契约,判断为文档过期(或注册逻辑缺失),需裁决 | [实证] | +| M2 | `render_test.rs:471-494` | `forced_size_and_format_are_applied`:设置了 `force_format = F32`,断言只有 `recorder.frames == vec![(32,32)]`(尺寸);Recorder 不记录像素格式,format 是否应用到交付纹理**不可证伪** | [子代理] | +| M3 | `render_test.rs:621-676` | `clip_effect_chain_is_walked_and_bypassed`:enabled=false 与 true 两分支断言**完全相同**(order、frames);若 `clip_effects()` 回归为完全不收集效果链,测试照过。fixture sanity 只验证测试自建图 | [子代理] | +| M4 | `writethrough_test.rs:126-161` | `backend_enabled_by_config_binds_and_round_trips` 名义 "round_trips",但**从不读回 sqlite、不断言 `library.db` 文件存在**;write-through 成功仅以 `last_error == None` 反推。`save_project` 退化为静默 no-op 不可检出 | [子代理] | +| M5 | `undocommands_test.rs:528-556` | `RippleInfo` 字段私有且**全仓库无公开构造器** → 只能构造空 info → `ripple()` 在 `is_empty()` 守卫处立即 return → "断言状态未变"**不可能失败**;`TrackListRippleToolCommand` 主体约 125 行对集成测试不可达,行为覆盖为 0。测试注释与计划文档均有诚实标注,但覆盖率数字(oak-timeline 90.1%)因此有误导性。根修:给 `RippleInfo` 加公开构造器 | [子代理,构造器缺失已 grep 复核] | +| M6 | `loadotio_test.rs:294-310`、`otio_backend_test.rs:388-401` | 两处 FCPXML round-trip 只断言 `sequence_labels == ["Edited"]`;轨道/块/footage 全丢也过(对照 OTIO 版有完整结构断言) | [子代理] | +| M7 | `footage_import_test.rs:165` | 导入 on/off 容差 `max_diff < 0.15`,注释两处声称"the same tolerance the hardware-vs-software decode test uses",实际先例(`oak-codec/src/realmedia_tests.rs:567`,未被本次修改)是 **0.08**——引用失实,容差约为先例 2 倍。测试不空洞(图案通道间距 ≥0.65、逐像素 max、test2 ACEScg 用 0.05 且实测 0.011),但应改注释论证放宽理由(swscale 色度滤波 vs GPU 双线性)或对齐 0.08 | [实证:两处阈值均已 grep 核对] | +| M8 | 无断言/弱断言冒烟测试 | `worker.rs:3188` `renderer_create_variants_are_tolerant`(**零断言**,纯"不 panic"遍历);`worker.rs:2391` `renderer_creation_falls_back_or_succeeds_headless`(expect + 丢弃 `has_renderer()` 结果);`instance.rs:2123` `trace_probe_branches`(5 个调用仅 1 个断言);`history.rs` `rows_render_and_interact`(`on_menu(MENU_UNDO/REDO)` 只调用不断言效果,如 undo 后条目数应减少);`writethrough_test.rs:232-244`(零断言 + 依赖不受保护的"无残留 binding"前提)。注释均有坦白,但这正是计划 §8.2 自己警告的"为行数写无意义 case"。CI 有 lavapipe + `OAK_REQUIRE_GPU=1`,本可断言确定结果而非"两种结局都接受" | [实证:前四个已读原文] | +| M9 | `footage_import_test.rs:136/145、217/226` | `OAK_GPU_IMPORT` 用裸 `set_var`/`remove_var` 非 RAII:中途 `expect("staging decode")` panic 会泄漏 `"0"`;串行锁下**把后续测试的真失败掩蔽成静默跳过**。同文件(render_threads_test)明明有 `SoftwareDecodeGuard` 先例可仿。另 `remove_var` 不恢复外部预设值 | [子代理] | +| M10 | `oak-core/src/backend/external.rs` | 611 行 unsafe 平台 FFI(DMA-BUF/D3D11 handle/IOSurface 导入)**零单测**;唯一覆盖是恒跳过的 footage_import(§3.4b)。计划 M4 的平台门禁 job 未接线 | [实证:grep 无 #[test]] | + +## 5. 低危问题(简列) + +- `render_test.rs:352-355`:configurator 边界段(`set_max_inflight(0/ + usize::MAX)` 等)调用后零断言;`max.max(1)` 钳制无公开 getter 可验。 +- `render_test.rs:405-428`:名义 "generated **(transparent)** frame" + 只断言尺寸,不检查像素透明。 +- `import_test.rs:219-220`:断言消息声称 "fails with Cancelled",实际 + 只 `is_err()`,未 `matches!(Err(Error::Cancelled))`(源码确实返回 + Cancelled,测试弱化了它)。 +- `codecbridge_test.rs:142/162/185/205`:失败路径仅 `is_err()` 不断言 + 错误变体/消息,无法区分失败原因(如 bad-output-name 若因 probe 失败 + 而 err 也会通过)。 +- `precache_test.rs:254-282`:doc 声称验证 window/progress + configurators,`set_max_inflight(1)`/`set_native_progress_signalling(false)` + 设置后零对应断言,其余断言与相邻测试重复。 +- `task_test.rs:266-292`:setter/getter 回环、`system_time_ms() > 0`、 + 结尾无断言的 `emit_progress(2.0)`。 +- `writethrough_test.rs:95-105`:null 句柄断言是真的,其后四次 + bind/unbind/flush 调用零断言。 +- `otio_backend_test.rs:296-317`:三种非法 URI 仅 `is_err()`,不区分 + 错误原因。 +- `undocommands_test.rs:428-487`:ripple 删 gap 仅摘成员关系(util.rs + 文档化偏差),时间线净效果为零;删区域后洞从 [60,80) 迁到 [50,70)、 + 可见时间线无变化——注释只描述机制不评语义。 +- `gpuinterop.rs:649-652`:`OAK_GPU_IMPORT` set/remove 不恢复外部预设值 + (有 `lock_tests` 守卫,实际风险低)。 +- `footage_import_test.rs` `sample()` 未断言 `frame.format == F32` 就按 + f32 解释字节(对照 render_threads_test.rs:232 有此断言)。 +- `render_test.rs:891-930`:测试中途 `RenderManager::shutdown()` + + env 覆盖,恢复在断言之前——render panic 会泄漏全局状态(后续 + `render_serial()` 自愈)。 + +## 6. 合格项(正面确认,防止误伤) + +1. **`render_threads_test.rs` +30/−0 纯新增**(`git diff --stat` 核对): + `assert_same_frame` 仍是严格逐字节相等、无容差;计数器期望 + (`(6,6,0)`、lru_hits=1/decodes=4)、双侧地面真值图案、时间戳逐帧 + 断言全部保留。旧 flake 靠**消除解码路径分歧**修复(hwdecode 设备负 + 缓存进程级粘滞 → 同进程两侧走不同解码路径),不是放宽容差。 +2. **`OAK_HWACCEL=0` 软解守卫合规**:该二进制全部 13 个测试第一条语句 + 都是 `let _lock = lock()`,守卫在锁内设置、drop 逆序先恢复 env 再 + 放锁;`hwdecode.rs` 每次开流实时读 env,守卫真实生效。代价(记录): + 硬解下的管线字节一致性从此无测试,hw/sw 像素等价仅剩 + `realmedia_tests.rs:504` 一处(0.08 容差,无硬解机器同样跳过)。 +3. **逐字节比对不是"自己比自己"**:inline 与 pipeline 是两条真实执行 + 机器,两侧用不同文件名隔离 eval/decode 缓存键;footage_import 的 + on/off 对比同样用 `fs::copy` 换文件名隔离缓存。 +4. **`worker.rs` "needs 16000 bytes" 断言**:只是提取变量重排,未弱化; + `node_factory.rs` 曲线 JSON 断言等价重构 + 补 helper 双分支覆盖。 +5. **ForceParams 测试断言的是文档契约**(`-1`,注释明示 "not 0 = U8"), + 与手写 `Default` 及 doc 完全一致——契约验证,非投降。 +6. **codecbridge 真实 conform 测试是真断言**:两通道 `.pcm` 存在 + (metadata 失败即 panic)、非空(len>0)、`.working` 已重命名消失; + 提交为源码确认的同步路径,无竞态。不足(低):未校验 PCM 字节数 + 是否等于预期样本量。 +7. **loadotio 的 CALLBACK_LOCK 真消除了竞态**:锁覆盖全部回调消费点 + (XML 路径测试不持锁是正确的——源码确认不触碰回调);`take()` + 一次性消费 + 毒化锁 `into_inner()` 恢复,残留回调会导致后续测试 + **响亮失败**(`.expect("loaded project")`)而非静默通过。 +8. **`eval.rs` OCIO 内联测试抽查质量高**:真 LUT(红通道加倍)、真 + resolve 管线、逐像素数值断言(0.25→0.5、green 不变、alpha 保持); + 跳过仅在 OCIO 库缺失时触发,CI 有真实静态 OCIO(`ocio-env.sh` + + `OCIO_RS_ENABLE_REAL=1`);GPU 跳过经 `shared_gpu_or_skip` 在 + `OAK_REQUIRE_GPU=1` 下硬失败,设计正确。 +9. **`ffmpeg.rs` 内联测试抽查合格**:像素格式映射矩阵、声道布局回退、 + RefFrame 几何/缓冲共享、打开状态机、跨流检索错误——均有具体期望值。 + 新测试文件无 `#[ignore]`、无 sleep。 + +## 7. 建议行动(按优先级) + +1. **裁决 `block.rs:93-107` setter 互换**(产品缺陷,§3.1):修复时按 + graphops NOTE 的意图 "deliberately" 改写约 12 个期望值;先给 + `undocommands_test.rs` 的无标注固化处补偏差 NOTE、给全文件补 + media_in 断言维度、给 `RippleInfo` 加公开构造器(M5)。 +2. **消灭"双向接受"**(§3.2):`render_test.rs:932` 的 Err 分支按错误 + 内容分流,产品性错误必须失败。 +3. **修回放验收测试的跳过判别**(§3.3):source-window 补槽位检查; + 给严格模式真实机制(CI 设 env 禁止跳过);`WorkerBinGuard` 改从 + `current_exe()` 推导。 +4. **修 CI 恒跳过**(§3.4):apt 装 ICC 包;`gpu_context_for_import` + 尊重 `OAK_REQUIRE_GPU`;实现计划承诺的"跳过单列"(CI annotation + 或汇总)。 +5. **对齐 `manager.rs::init()` 文档与实现**(M1)。 +6. 小修批次:M2/M3 补格式与效果链断言、M4 补读回、M7 改容差注释、 + M9 改 RAII、M8 在 CI 断言确定结果、§5 各项。 +7. **流程**:M5 功能与覆盖率测试分开提交(§2 末)。 + +## 8. 审查局限 + +- 内联测试约 3 万行,采取"删除行全查 + 模式扫描全查 + 高风险区抽样 + 精读"策略,未逐行读完;[子代理] 标注项中未注明"已复核"的细节以 + 子代理报告为准。 +- 未重新运行测试套件与覆盖率;文中覆盖率数字引自计划文档 §7.5 自述。 +- `block.rs` 互换缺陷的 C++ Olive 原始语义对照基于代码注释与仓库内 + 其他模块(undosplit/undoripple/undogeneral)的补偿性注释推断,未 + 直接核对 C++ 源码。 + +## 9. 处理记录(2026-09-22) + +本报告的高/中危与 §5 条目已按下列状态处理;仅 §3.1 的语义裁决按报告 +建议延后(准备项已落地,修复清单已细化)。 + +### §3.1(准备项已落地,语义裁决延后) + +**已用仓库内 C++ 上游源码核准语义**(此前报告 §8 的局限已解除): + +- `.cache/olive-upstream/app/node/block/block.cpp`:`Block::set_length_and_media_out/in` + 只调用 `set_length_internal(length)`,**从不移动时间线 in/out**; + in/out(`in_point_`/`out_point_`)由 Track 的布局(`track.cpp:469-470,514-515,575-579`) + 按前后块与长度推导。 +- `.cache/olive-upstream/app/node/block/clip/clip.cpp`(非 reverse 剪辑): + - `set_length_and_media_out`:`media_in` **不变**(media out 随长度移动); + - `set_length_and_media_in`:`media_in += old_length - length`(media out 固定)。 +- `.cache/olive-upstream/app/timeline/timelineundopointer.cpp` / `timelineundogeneral.cpp`: + `BlockTrimCommand` 的 TrimIn/TrimOut 与 `BlockResize*` 的调用与本仓库一一对应, + 因此缺陷只在 Rust 的两个 setter(绝对 range 模型把 Track 布局效果错误地编码进 + setter)。 + +**结论:不是两个函数简单对调即可**。同一 C++ 函数在不同命令里需要不同的净时间线 +锚定: + +| 命令 | 时间线锚定 | media 调整 | +|---|---|---| +| `BlockResizeCommand` | in 固定、out 移动 | 无 | +| `BlockResizeWithMediaInCommand` | in 固定、out 移动 | `media_in += old−new` | +| `BlockTrimCommand`(TrimIn) | out 固定、in 移动 | `media_in += old−new` | +| `BlockTrimCommand`(TrimOut) | in 固定、out 移动 | 无 | + +另有 `TrackSlide`、`TrackListInsertGaps`、`TrackListRippleToolCommand`、multicam 等 +调用点与 undosplit/undoripple/undogeneral 的补偿性选边需要一并裁决;应作为独立 +PR 处理(先按 §7 第 7 条把 M5 与覆盖率测试分开提交)。 + +**本批已落地**: + +- `RippleInfo::new(block, append_gap)` + `block()`/`append_gap()` 访问器; + `undocommands_test.rs` 新增 2 个真实 info 测试(resize 往返、append_gap + 插入/撤销),使 `TrackListRippleToolCommand::ripple()` 主体可达(§M5)。 +- 补齐 `media_in` 断言维度:roll-edit、`BlockResizeWithMediaInCommand` 往返; + 相关期望值加 **KNOWN-SWAP** 标注(roll、slide、insert-gaps、 + ResizeWithMediaIn),语义修复时按标注"故意"改写。 +- 修正 `graphops.rs` 内联测试 NOTE 的措辞(follower 锚定的是 IN,不是 OUT)。 + +### 高危(其余三项) + +- **§3.2 ✅**:`render_test.rs` 私有调度器测试的 Err 分支按错误内容分流—— + 仅 `Error::Failed` 且消息以 `"render worker pool"` 开头(池创建/启动失败) + 或缺少 worker 二进制(`locate_oak_worker` 提前打印跳过)才跳过;产品性错误 + (`"Frame render ticket failed"`、shm 转换、时间戳等)一律 panic。管理器/ + 环境用 RAII `DispatcherEnvGuard` 恢复,断言前显式 `restore()`;两个交付腿 + 均断言 `formats == [F32]`。 +- **§3.3 ✅**:三个回放验收测试的跳过条件收紧为**零交付**(`filled`/`delivered` + 为历史最大值,一旦有过交付就禁用跳过);"交付过但服务/显示从未取用" 走有界 + 失败上限并打印 `serve_misses` 等诊断。新增 `OAK_STRICT_PLAYBACK=1` 严格模式 + (跳过改为 panic,含 `#[should_panic]` 自测)。`WorkerBinGuard` 改用 + `renderops::test_worker_bin()` 解析链(已有 env → `current_exe()` 同级 → + `target/{debug,release}` 兜底),仅在文件真实存在时设置变量并恢复旧值; + `process_backend_preview_path_is_zero_copy` 不再硬编码路径。 +- **§3.4 ✅**:CI apt 增加 `icc-profiles-free`;同时给 `system_icc()` 补 + Ubuntu 实际路径 `/usr/share/color/icc/sRGB.icc`(实测 5 个 display-ICC + 测试真跑,BGRA8 中灰精确为 `[128,128,128,255]`)。`gpu_context_for_import` + 尊重 `OAK_REQUIRE_GPU=1`(硬失败),所有跳过路径打印 `SKIP:` 标记;文件头注 + 记录 lavapipe CI 恒跳过与真机验收出处。 + +### 中危 + +- **M1 ✅(裁决:文档化同步契约)**:证据为 C++ `taskmanager.cpp` 仅创建 + 管理器、移植版 codecbridge 的 "submission is synchronous" 契约与 + `TaskManager::init` 无生产调用点。已把 `manager.rs` 模块/`init`/`shutdown` + 文档与字段说明改为"不注册",测试断言 `init` 后未注册、且手工翻标志不会安装 + oakcodec 回调。 +- **M2 ✅**:`Recorder` 记录 `formats`/首像素;新增生成帧路径的强制 **U8** + 测试(可证伪),另断言 F32 腿的交付格式。 +- **M3 ✅**:夹具效果从 Invert 换成 **Opacity = 0**;enabled=true 必须全透明、 + enabled=false 必须非透明——效果链丢失或忽略 enable 标志都会失败。 +- **M4 ✅**:writethrough 往返测试插入 `"Written Through"` 文件夹后写穿,断言 + `library.db` 存在且非空,并用全新 `backend().load_project` 读回 uuid 与标记。 +- **M5 ✅**:`RippleInfo::new` + `block()`/`append_gap()`,新增 2 个真实 info + 测试(resize 往返、append_gap 插入/撤销),`ripple()` 主体可达。 +- **M6 ✅**:两处 FCPXML 往返补齐轨道/块/footage/转场偏移断言 + (转场居中偏移 `3/8`)。 +- **M7 ✅**:容差对齐先例 0.08;真机实测 sRGB 0.00878 / ACEScg 0.01141,注释 + 改为正确论证(GPU 双线性色度 vs swscale 滤波)并打印实测值。 +- **M8 ✅**:`worker.rs` 渲染器变体测试改为结果不变量断言(成功↔`is_open_gl`、 + 失败↔阶段错误串)、`has_renderer()` 与工厂结果一致、崩溃钩子 env 用 RAII、 + 一次性告警补并发存活断言;`instance.rs` 的 5 个 trace 调用全部断言返回值; + `history.rs` 断言 undo/redo 后 `history_index` 与 `done` 变化(行数按面板 + 契约保持不变,已在注释说明)。 +- **M9 ✅**:`StagingDecodeGuard` RAII + 显式 drop;外部预设的 `OAK_GPU_IMPORT` + 值会被恢复。 +- **M10 ✅**:`external.rs` 新增测试模块:顶部文档列明未单测的 unsafe 平台体与 + 对应平台 job;4 个测试覆盖纯映射(Vulkan/IOSurface 格式)、参数校验(DMA-BUF + 零尺寸/非平面格式)与 D2 别名视图回归。 + +### 低危 / §5 + +- oak-task:`import_test` 断言 `Err(Error::Cancelled)`;`codecbridge_test` 同时 + 断言桥接层消息与直接驱动 `ConformTask`/`ProxyTask` 的路径原因; + `precache_test` 用真实运行兑现"窗口/进度"声明;`task_test` 观测进度钳制; + `render_test` 的配置器边界、透明帧、shutdown 恢复顺序均已修正。 +- `gpuinterop.rs`/`footage_import_test.rs` 的 env 泄漏改 RAII(含恢复外部预设)。 + +### 追加修复 + +- **新暴露 flake**:`app::tests::preferences_general_tab_writes_cache_ahead_and_storage` + 在满载并行下"点击→按键"焦点交接竞态(1/4 次读到 120)。点击后补一次 + effect flush + 一帧绘制再派发按键;通过。 + +### 验证 + +- 各 crate:oak-task 133、oak-storage 74、oak-core 456+12、oak-render 全部、 + oak-worker 全部、oak-plugin 208+、oak-app lib 544(含 `OAK_STRICT_PLAYBACK=1` + 一次)、oak-timeline 全部。 +- 全 workspace 终验(2026-09-22):**104 个测试目标、3179 passed / 0 failed / + 7 ignored**(ignored 为既有 GPU 门控)。 +- 覆盖率(插桩终测,干净对象集同口径):区域 93.96%、函数 89.59%、行 + **93.49%**(180742/193331)、分支 **71.58%**(11805/16491)。分支较修复前 + 微降 0.37pp——本批刻意移除了若干"容忍型/同义反复"的执行路径(§3.3 交付即 + 失败、M3 夹具更换、M8 去重),属测试质量的预期代价。 + +## 10. 第二轮全量审查(2026-09-22) + +> 触发:§9 修复完成后,用户要求全量复审。范围:全部未提交改动 +> (66 文件,+38731/−281),含第七批修复自身。方法:五个深查子代理 +> 分区逐行通读(oak-app 面板群 19 文件、real.rs/renderops、 +> oak-render/oak-worker 269 个测试、oak-plugin/oak-codec 约 250 个测试、 +> oak-task/core/timeline/storage),审查会话对全部高危论断与 24 条修复 +> 声称的关键链条做了原文实证。标注约定同前([实证]/[子代理])。 + +### 10.1 修复声称验证总裁定 + +**24 条声称:20 条完全属实,4 条部分属实,0 条虚假陈述。** +所有核对到的新断言均为可证伪的真断言。上一轮四个高危全部实质化解: + +- **§3.1**:语义裁决延后**有据**——`.cache/olive-upstream` 的 + `block.cpp:62-79`(C++ setter 只调 `set_length_internal`,时间线位置 + 由 Track 布局推导)与 `clip.cpp:95-124`(非 reverse 时 media_out 版 + 不动 media_in、media_in 版 `+= old−new`)经原文核对,支持"不是简单 + 对调、需逐命令裁决"的结论;§8 原局限(未核对 C++ 源码)解除。准备 + 项落地:`RippleInfo::new` 被 2 个真实测试使用(resize 往返断言 + `[-10,100)`+`media_in==-10`、append_gap 断言块数 1→2+Gap 类型, + ripple 主体确认可达);KNOWN-SWAP 标注 6 处;`clip_media_in` 断言 + 1→11 处。[实证] +- **§3.2**:`worker_pool_environment_error` 分流精确(`Error::Failed` + + `"render worker pool"` 前缀,全仓库仅池创建/启动两处产生该前缀, + 产品错误消息穷举核对均不匹配);`DispatcherEnvGuard` RAII + 断言前 + 显式 restore;两腿断言 `formats == [F32]`。**"永不失败"测试已消灭。** + 固有残留:worker 存在但握手/协议回归导致 `start()` 失败仍归环境跳过 + (有打印)。[实证+子代理] +- **§3.3**:三验收测试统一"历史零交付才可跳过"(`filled/delivered` + 取历史最大值,一旦有交付跳过分支永久不可达;source-window 旧缺陷已 + 修);"交付但未服务"走有界 `assert!(pumps < FAIL_PUMPS)` 失败并携 + `serve_misses` 诊断;`OAK_STRICT_PLAYBACK=1` 严格模式 + 设计正确的 + `#[should_panic]` 自测;`test_worker_bin()` 解析链(env 存在性过滤→ + current_exe 同级→target 兜底,`None` 时不动 env)修掉了硬编码复合 + 隐患;早期 playhead 断言修复与生产 prune 语义精确匹配。**残留: + ci.yml/cd.yml 均未设置 `OAK_STRICT_PLAYBACK`**——门禁 job 上零交付 + 仍为绿色跳过(可辩护的权衡:全量并行下池饥饿本征 flaky,且兄弟测试 + 对管理器启动失败一律硬失败、worker 全坏会在别处红;但应在文档显式 + 登记该取舍)。[实证+子代理] +- **§3.4**:ci.yml 增 `icc-profiles-free`(注释精确指向黑屏守卫)+ + `system_icc()` 补 `/usr/share/color/icc/sRGB.icc`(该包实际落点) + [实证];`gpu_context_for_import` 经 `require_gpu_adapter()` 在 + `OAK_REQUIRE_GPU=1` 下硬失败,footage_import 三处跳过均带 `SKIP:`, + 头注如实登记 lavapipe 恒跳过与真机验收出处 [子代理,头注已实证]。 + 残留(低):color.rs(15)/eval.rs(4)/procpool.rs(2) 的旧式 `skipping` + 打印未统一为 `SKIP:` 标记(CI 上因 ICC/OCIO/二进制修复均不再触发)。 +- **M1–M10 与低危批**:M1(文档改为事实 + 真注册表查询断言)、M2 + (Recorder formats + 强制 U8 可证伪测试)、M3(Opacity=0 夹具,双分支 + 断言互斥)、M4(sqlite 落盘 + load_project 读回 uuid/label)、M5、 + M7(0.08 对齐先例,实测值打印)、M8(history.rs 真撤销栈三维断言; + instance.rs 5 个 trace 调用全断言且分支敏感;worker.rs 渲染器不变量 + +`has_renderer` 一致性+EnvGuard+并发存活)、M9(StagingDecodeGuard + 恢复外部预设)全部属实。[实证+子代理] +- **部分属实 4 条**: + 1. **M6**:otio_backend_test 全齐(轨道/块/footage/转场 3/8 居中 + 偏移,算术核对正确);loadotio_test 补了轨道/块/footage 但无转场 + 断言(夹具本就无转场)——声称"两处"只兑现一处。 + 2. **M10**:external.rs 顶部文档如实列明未单测平台体;4 测试中 3 个 + 完全真实(Vulkan/IOSurface 格式映射、DMA-BUF 参数校验精确变体); + **"D2 别名视图回归"守卫不可证伪**——只对三种别名 `.expect()` 视图 + 创建成功,而删掉生产修复行(external.rs:607 `dimension: Some(D2)`) + 后 `create_view` 照样成功(派生 D2Array 合法),真正的回归(planar + pass 的 D2 layout 校验拒绝 bind group)只在**创建 bind group** 时 + 显现,测试未建 bind group。[实证] + 3. **M8-worker 残留**:渲染器两测试仍是 Ok|Err 双向接受(错误串前缀 + 对即过),未按 `OAK_REQUIRE_GPU` 分流成确定断言——CI(lavapipe)上 + "渲染器创建失败"回归不可检出。比 §3.4 已实现的硬失败策略弱一档。 + 4. **§3.3-CI 接线**:见上。 +- 追加修复核实:`source_monitor` 早期 playhead(属实,与生产 prune + 语义逐值核对);`preferences_general_tab` 点击→按键竞态(属实,补 + `run_until_parked`+整帧 draw+再 park 的同步节拍,断言保持严格 + `==128` 无容差,非弱化;配置键 RAII Restore)。[实证+子代理] +- dialogs 184 期望值再确认:gpui_widgets `SliderModel` 吸附实现为 + `((raw−min)/step).round()`(半值远离零)→ (180−120)/8=7.5→8→184, + 与控件既有测试固化的契约自洽。[子代理,公式已核对] + +### 10.2 全量深查新发现(第一轮未覆盖区域) + +**高(1 项)** + +| 位置 | 问题 | +|---|---| +| `panels/inspector.rs:564-568` | **恒真断言**:`assert!(debug_bounds(..).is_none() \|\| ..is_some())` 逻辑上永不失败;该测试设置 `pending_add=Some(0)` 并 draw 后本应断言 add 菜单 `.is_some()`。全工作区唯一一处恒真断言。[实证] | + +**中(12 项)** + +| 位置 | 问题 | +|---|---| +| `oak-render/cache.rs:999-1023` | mtime 守卫测试同义反复:`validate` 先污染内存使文件与内存恒同构,`load_state` no-op/守卫反转/真跳过三种实现均通过;名义两个行为均不可证伪。修法:两次 load 间 invalidate 内存范围或用干净实例。[实证] | +| `pipeline.rs:959` ↔ `eval.rs:5636` | 进程级 working-space 竞态:pipeline 10 个测试 `pin_legacy_working_space` 不取 eval 的模块私有 `WORKING_SPACE_TEST_LOCK`,同一 lib 测试二进制并行——`footage_working_lut` 的 `is_some()` 有真实 flake 窗口。修法:锁提为 crate 级。[实证] | +| `eval.rs:1371-1390`(生产) | **M5 planar-resolve 失败→staging 回退分支零覆盖**,且现有测试缝(`decoded_frames` 全局缓存 + `planar_texture()` helper)本可单元级注入;留档"单机走不到"对该分支不成立。M5 最重要容错路径之一。[子代理] | +| `oak-app/mock.rs` ↔ `ofx_params.rs:4886/4825` | MockEngine 未实现 `set_effect_param`/`effect_push_button` 的记录与失败注入(计划 M3.4 缺口)→ 两个"路由/点击触发"名义测试不可证伪;生产侧 `let _ =` 丢弃 Err 加剧不可观测。[子代理] | +| `controls.rs:1842-1856`(及 1424-1434) | checkbox 键盘测试不订阅 `CheckBoxEvent::Toggled`,只断"状态不变"——space/enter 处理整体失效不可检出(同仓库 ofx_params.rs:2489 有正确示范)。[子代理] | +| `displaycolor.rs:558-578` | 缓存/generation 测试用恒真 `>=` 断言,"同 key 命中不重建/换 key 重建"语义均未验证;:594-599 五个指纹调用零断言。[子代理] | +| `oakui/gpu.rs` | **全文件零测试**(M5 功能文件):`upload_rgba16f` 参数校验分支(触碰 GPU 前返回)与 `build_display_lut` 纯 CPU 色彩数学均可确定性测试而未测。[子代理] | +| `project_explorer.rs:677-733` | RealEngine 菜单/打开/导入段:注释声称四种效果,整段零断言(同文件 mock 版对相同行为逐一断言,证明可断)。[子代理] | +| `real.rs:14789` + `:16234` | 代理 drain `matches!(Ready\|Failed)` 双向接受**原样保留**(§9 未声称处理),叠加弱断言后两文件内**无任何测试断言真实转码产出 Ready**——代理生成 happy path 无守卫。[子代理] | +| `real.rs:16147-16155` | "注释声称-结果丢弃"假断言:声称"删除后 name/path 查找 miss",两个查询结果 `let _ =` 丢弃。[子代理] | +| `renderops.rs:3749` | `ensure_render_manager()` 失败→SKIP+return;同情形 real.rs 一律 panic(10 处)——同仓库双标,管理器 init 回归可被静默跳过。[子代理] | +| `undocommands_test.rs:647-648` | KNOWN-SWAP 漏网:注释仍把互换行为写成规范("Trim-out is out-anchored: the clip's in shifts to 25"),与 common.rs:39 及 §9 C++ 裁决表直接矛盾。另 :874/:907、:659-687 三处固化无标注;:820 slide 标注措辞("grow into each other")与断言(next 缩短)不符。[实证] | + +**中低/低(择要)** + +- `hwdecode.rs:331` 全局 `CREATE_ATTEMPTS` 计数器断言未取 `lock_tests` + ——并发的 ffmpeg 真实媒体测试会递增它,唯一可能产生 CI 间歇红的 + 竞态(VDPAU 选型已封死污染面,仅剩自身受竞态)。[实证+子代理] +- `decoder.rs:899`/`encoder.rs:362` 注册表注入还原非 panic 安全 + (`registry_guard()` 只是锁、Drop 不还原;断言 panic 则 stub 注册表 + 残留进程,后续测试经 poison 恢复静默用假解码器)。[实证] +- `node_factory.rs:2206` 只测自建 mock 入口的边界分支(零生产代码); + `:1672` 读两次全局计数断言相等(近同义反复 + 无锁 flake);`:2350` + 无断言尾段;`gl_render.rs:1200` 零断言(注释诚实)。[实证+子代理] +- `external.rs:715` DMA-BUF 校验测试自建软跳过不受 `OAK_REQUIRE_GPU` + 约束(Linux CI 是 Vulkan,不触发)。 +- `backend.rs:2886` `user_config_env_override` 非 RAII(M9 同类未修到 + 此处);`:3579` `OAK_REQUIRE_GPU` 翻转窗口与 16 个 GPU 测试竞态(仅 + 无 GPU 主机);`:2951` shared-slot 测试锁纪律不一致(潜在)。 +- `color.rs:1164/1332` if-let 包裹全部断言的静默逃逸(CI 有真 OCIO, + 应硬断言);OCIO 缺失跳过仍静默(无 eprintln);`:1420` 不持 + config_lock 且恢复非 RAII。 +- 裸 `is_err()` 无消息断言(系统性偏弱,非个别假测试):graphops 23 + 个错误矩阵(分支隔离夹具 + 精确成功腿维持可失败性)、real.rs 无工程 + 守卫约 35 处(同文件 :11490 有正确示范)、encodingparams 11 个非法 + XML(生产明确区分两种失败原因而未断)。 +- "结果丢弃/弱断言"群:source_viewer.rs:375-454(config 动作前后读取 + 全丢弃、transport 9 个只断 handled)、program_viewer.rs:843(workarea + 三事件未断)、timeline.rs:2410(约 20 个命令只断 handled 布尔)、 + app.rs:8786(`exit_action_quits` 零断言)、eval.rs:5467/5557(无断言) + /6087(OAK_PERF 名义不符)/5585("and_clears" 半截)、procpool.rs + :4752/:4764(smoke/半验注入)、worker.rs:2179(frame_failed 不断错误 + 文本,同族 :2339 证明可断)、nodeops.rs:1332(零断言,名称诚实)、 + writethrough_test.rs:96/277(§5 指出未修,未声称)、precache "窗口" + 半边仅 liveness 间接兑现。 +- env 非 RAII 残留(与已修 M9 同款):ofx.rs:926 `MARKER_ENV`、 + program_viewer.rs:964 `HOME`、eval.rs:6095 `OAK_PERF`、 + renderops.rs:2881 配置开关、worker.rs:1992 颜色设置。 +- 卫生:`crates/oak-task/tarpaulin-report.html` 覆盖率工件混入源码树 + (内嵌他机绝对路径,不应提交);计划文档"nodeops 32 个"实为 24 个、 + node_factory/render_driver 的批次计数为组合计(文档口径应注明)。 +- `ofx_host.rs:68-77` 生产 `emit` 的 `cfg!(test)` 早退:为测试改形的 + 轻微实例(shipping 行为不变,集成测试真管道补偿),记录在案。 + +### 10.3 正面确认(防误伤) + +- **无成建制造假**:约 500+ 个新测试中,纯假(恒真/零断言/零生产代码) + 共 6 个,均有诚实注释或属笔误级;无 `#[ignore]`、无理由 sleep(全部 + 有界轮询+deadline+超时 panic);故障注入类全部真注入且断言注入生效 + (真 SIGSEGV+marker、/bin/false OFX host→紫帧、假 nvidia-smi 六形态)。 +- **修复未引入弱化**:preferences 竞态修复是补同步节拍而非放宽容差; + render_threads 逐字节断言原样;分支覆盖 −0.37pp 是移除容忍路径的 + 如实代价且已留档解释。 +- 教科书级真测试代表(各区各选):node_editor.rs:844(绕过面板证明 + 守卫在产品代码)、procpool.rs:4780(PATH 注入六形态+精确换算)、 + worker.rs:1757(跨进程 shm 游标环游+0xAB 传播)、eval.rs:4415(GPU + 旋转单像素精确+全帧 lit==1)、clip.rs:600(f16 −0 位级保留)、 + encodingparams.rs:1096(C ABI offset_of 18 项冻结锁)、app.rs:6610 + ("全分支"名副其实)、dialogs.rs:6483(按键改派+磁盘内容往返)、 + ofx.rs:920(真插件 marker 参数串+生命周期顺序)、history.rs:325 + (真引擎撤销栈三维断言)。 + +### 10.4 最终判定与建议 + +**判定:修复批次诚信且实质有效;工作区达到可提交状态,但下列 8 项 +建议先修(均为小改动):** + +1. `inspector.rs:564` 恒真断言改 `.is_some()`(一行)。 +2. `undocommands_test.rs:647` 补 KNOWN-SWAP 标注(并顺手修 :820 措辞、 + 补 :874/:907、:659-687 三处)。 +3. `cache.rs:999` mtime 测试改为可证伪构造。 +4. pipeline/eval 的 working-space 锁提为 crate 级共享。 +5. `external.rs:744` D2 守卫补 bind group 创建(或改名去掉"回归守卫" + 声称)。 +6. `real.rs:14789` 代理 drain:至少在转码可用的环境断言 Ready(可按 + ffmpeg 可用性分流,不可用才容忍 Failed)。 +7. `renderops.rs:3749` SKIP 改 panic(与 real.rs 对齐)。 +8. 删除 `crates/oak-task/tarpaulin-report.html`。 + +**登记事项(不阻塞提交)**:`OAK_STRICT_PLAYBACK` 的 CI 接线取舍; +`hwdecode.rs:331` 补 `lock_tests`(CI 间歇红风险);decoder/encoder +注册表还原改 RAII;M5 planar 回退分支注入测试;gpu.rs 纯 CPU 分支 +补测;MockEngine `set_effect_param` 族记录缺口;旧式 `skipping` 打印 +统一 `SKIP:` 标记;block.rs setter 语义裁决独立 PR(§9 清单)。 + +**提交拆分(重申 §2 末建议)**:M5 功能(gpuinterop/external/Planar/ +hwdecode 顺序/RefFrame)与覆盖率批次(测试+两处 bug 修复+文档)分开 +提交;tarpaulin 工件与本报告/计划文档更新可并入文档提交。 + +### 10.5 §10.2/§10.4 处理记录(2026-09-22) + +**"建议先修 8 项":全部完成。** + +1. ✅ `inspector.rs` 恒真断言 → 以具有 debug selector 的分组头断言菜单 + 已渲染,并断言 `pending_add == Some(0)` 生效。 +2. ✅ `undocommands_test.rs` KNOWN-SWAP 标注补齐::647 措辞改为与 + `common.rs:39`/§9 裁决表一致,:659-687、:874、:907 补标,slide 处 + 措辞修正(next 缩短)。 +3. ✅ `cache.rs` mtime 守卫测试改为可证伪构造(干净实例装载 + 内存/文件 + 不再恒同构),守卫反转或真跳过都会失败。 +4. ✅ working-space 锁提升为 crate 级 `eval::working_space_test_lock`, + pipeline 与 eval 两侧共同持锁。 +5. ✅ `external.rs` D2 守卫补 bind group 创建(临时删修复行可复现失败, + 已还原)。 +6. ✅ `real.rs` 代理 drain 按 ffmpeg 可达性分流:可达硬断言 `Ready` + + `ProxyBadge::Ready`;不可达打印 `SKIP:` 并容忍 `Failed`(两种环境均 + 实测)。 +7. ✅ `renderops` 的 `ensure_render_manager` 失败测试改为硬断言(仅 + "管理器已初始化" 保留自保护跳过)。 +8. ✅ 删除 `crates/oak-task/tarpaulin-report.html`(及其绝对路径泄漏)。 + +**中危 12 项:** + +- ✅ MockEngine 记录 `set_effect_param`/`effect_push_button` 调用(含 + 失败注入开关),`ofx_params` 生产侧改错误日志;两个名义测试改为完整 + 路由矩阵 + 类型转换值断言(曲线 JSON 先 `set_points` 再发事件)。 +- ✅ `controls.rs` checkbox 键盘测试订阅 `CheckBoxEvent::Toggled` 并断言 + 事件序列;space/enter 处理整体失效会失败。 +- ✅ `displaycolor.rs` generation/链缓存改为精确断言(同 key 不重建、 + 换 key 恰好 +1、指针同一/不同),5 个指纹逐一断言。 +- ✅ `oakui/gpu.rs` 补 4 个测试:`upload_rgba16f` 参数校验、f16 打包 + (钳制/字节序/256 对齐行距)、display LUT 网格完整性与确定性、key + 跟随色彩设置。 +- ✅ `project_explorer.rs` RealEngine 段补断言(序列切换、导入计数、 + 菜单选择与开关状态、引擎分支前置条件)。 +- ✅ `real.rs` 两处丢弃的 name/path 查询改为断言;删除后 rename no-op + 一并断言。 +- ✅ `hwdecode.rs:331` 取 `lock_tests`;decoder/encoder 注册表注入改 + RAII `RegistryGuard`(panic 不再泄漏假解码器)。 +- ✅ `color.rs` if-let 逃逸改硬断言(OCIO 缺失才打印跳过);配置锁 + RAII。`backend.rs` env 覆盖 RAII、共享上下文测试锁纪律统一、 + `OAK_REQUIRE_GPU` 竞态窗口消除。 +- ✅ env 残留 RAII:`ofx.rs` MARKER_ENV、`program_viewer.rs` HOME、 + `eval.rs` OAK_PERF、`worker.rs` 颜色设置、`renderops.rs` 配置开关。 +- ✅ 计划文档计数修正(nodeops 32 → 24,组合批次注明口径)。 + +**登记(不阻塞提交,遗留项)**:`eval.rs` M5 planar 回退分支注入测试 +(需要专用测试缝);`node_factory.rs` 三处弱断言与 `gl_render.rs` 零断言; +旧式 `skipping` 打印统一 `SKIP:`;`OAK_STRICT_PLAYBACK` 的 CI 接线取舍; +`block.rs` setter 语义裁决独立 PR(§9 清单)。 + +**验收(2026-09-22)**:全 workspace **104 个测试目标、3185 passed / +0 failed / 7 ignored**(ignored 为既有 GPU 门控)。本批生产改动仅 +`MockEngine` 记录字段与 `ofx_params` 错误日志两处可观测性增强。 diff --git a/docs/zh/plans/test-coverage-90-80.md b/docs/zh/plans/test-coverage-90-80.md new file mode 100644 index 000000000..fad2e1f45 --- /dev/null +++ b/docs/zh/plans/test-coverage-90-80.md @@ -0,0 +1,829 @@ +# 测试覆盖率提升计划:行 ≥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/