# Studio / shared 测试修复与门禁计划 状态:待实施。日期:2026-10-18。基线:`orisys-app` `main` 的 `85d0b39`。执行者:一个代理(DeepSeek),按顺序完成, 每个阶段结束时向用户汇报。本计划补充 [分层测试执行计划](test-execution-plan.zh.md),不替代它。 ## 1. 为什么要做 在 `85d0b39` 上,studio 测试在 Windows 上有 23 项失败或错误(Linux 上 24 项),shared 有 2 项,没有人察觉。原因不是单个缺陷,而是流程本身: | 问题 | 证据 | 后果 | |---|---|---| | 没有强制执行的门禁 | 仓库没有 CI 或 pre-commit;规则只写在 AGENTS 里 | 10-05 至 10-08 至少 6 个提交改了行为但没改测试(第 3 节逐项列出) | | 只跑定向测试,从不定期全量 | 分层计划写明"定向测试通过后结束验证" | 其他分组里的测试悄悄变红,没有人发现 | | 文档里的门禁命令无效 | `-m "not timing"` 是文档写的门禁命令,但 `timing` 标记从未注册,也没有测试使用 | 已知不稳定的计时测试每次都失败,大家习惯无视红色 | | 测试依赖未跟踪的数据 | 回放和裸视频测试读取 `dist/OrisysStudio/outputs/recordings/…`;10-14 重新打包后这份录像消失 | 换机器或重新打包,11 项测试报错,4 项失败 | | 从仓库根目录无法运行 studio 测试 | `from tests.conftest import …` 在根目录找不到 `tests`;studio 与 autocal 的 `test_binding_purity.py` 同名冲突 | 文档写的"根目录 pytest"跑不起来,每个人自己找运行方式 | | `ui_check.py` 不在任何门禁里 | 它的模拟 worker 从 10-08 起与 `start()` 签名不符,脚本中途崩溃,并连带约 15 项冒烟测试失败(已在 `85d0b39` 修复) | 3700 行的检查静默失效 | | 跳过的测试无人计数 | studio 有 35 项跳过,原因没有汇总 | 环境坏了也可能显示为通过 | 结论:分层选择测试的思路是对的,但缺少两样东西——**一个任何人都绕不过去的全量门禁**,以及**一份必须为空的已知失败清单**。 ## 2. 规则(执行者必须遵守) - 遵守 `D:\orisys_ws\AGENTS.md`:scratch 放在 `orisys-app/.scratch/test-repair/`,带 `.active` 标记;pytest 使用 `--basetemp=/pytest-` 和 `-p no:cacheprovider`;改动不暂存、不提交;向用户汇报用中文。 - 解释器固定为 `D:\orisys_ws\orisys-app\.venv-build\Scripts\python.exe`,用 Windows PowerShell 5.1 语法。 - **不得**通过删除断言、放宽断言、加 skip 或 xfail 让测试变绿。改测试只有一个理由:测试与已确认的产品行为不一致,并且要写明是哪个提交确认了该行为。 - 每修一项,记录它在修复前失败、修复后通过的证据(命令和输出摘要)。 - `apps/autocal/` 目前有另一个任务在改。不要编辑 autocal 的源码或测试;如果第 4 节的收集问题必须改 autocal 文件,停下来问用户。 - 遇到第 3 节标为"需决定"的项,先准备好证据和建议,再停下来问用户,不要自己决定。 ## 3. 逐项诊断(执行者先复核,再动手) 以下诊断来自 2026-10-18 在 Linux 离屏环境与 Windows 上的两次运行;"引入提交"由 `git log -S` 查得。执行者须用 `git show <提交>` 确认诊断后再修。 | # | 测试 | 现象 | 判断 | 处理 | |---|---|---|---|---| | 1 | `test_bare_video_analysis.py` 全部 11 项 | 找不到 `dist/…/20260930T015028Z_session0002_…/raw_0001.mp4` | 测试数据缺失 | 见第 4 节阶段 2 | | 2 | `test_replay_wiring.py` 4 项(`_open` 报 the recording did not open;declined prompt 报 FileNotFoundError) | 同一份录像缺失;`_copy_recording` 静默跳过缺失文件,而模块级的 skip 只在部分路径生效 | 测试数据缺失,加上 fixture 行为不一致 | 阶段 2;同时让缺数据时一律明确失败 | | 3 | `test_replay_analysis.py::test_a_stitch_grid_is_not_painted_onto_the_camera_frame` | 测试桩 `_Reader` 没有 `provenance` 属性 | 测试过时(`replay_analysis.py:245,401` 读取 `reader.provenance`,引入于 `829d2c1`,10-06) | 给测试桩补上 `provenance`,取值仿照真实 reader,不得改产品代码 | | 4 | `test_ui_smoke.py::test_old_result_depth_is_not_sent_to_the_new_sensor_scene` | 调用已删除的 `_show_analysis_frame` | 测试过时(`cf49b08`、`28e955a` 改为 `show_analysis_packet`) | 改用公共入口,**保留**原断言意图:旧结果的深度不得送进新传感器的场景 | | 5 | `test_scene3d_view.py::test_the_replay_view_shows_the_pane_and_says_where_the_field_comes_from` | 断言文案含"重算",现在是"分析结果:非录制时的测量值" | 测试过时(`c0a10e6` 改了文案) | 改为对比 locale 键 `source_recompute`,不再硬编码字面词 | | 6 | `test_ui_elements.py::test_every_declared_condition_is_used_and_reaches_a_window` | `liveCalibrationCombo`、`sensorProfileScroll` 未登记 | 登记表过时(`3d2fb0c` 新增控件) | 用 `apps/studio/tools/ui_elements_dump.py` 生成条目,补进 `apps/studio/docs/ui-elements.json`,逐条写明用途 | | 7 | `test_ui_smoke.py::test_run_tooltip_explains_disabled_reason` | 期望选了视频文件后「运行」可用 | 测试过时:实时视图不再用视频运行(`test_live_run_refuses_a_video_source` 断言的正是相反行为) | 改为断言:视频源时按钮禁用、提示文字和顶栏 `run_hint` 都是 `toolbar.run_video_in_replay` | | 8 | `test_ui_smoke.py::test_the_sensor_summary_opens_the_dialog` | 没有导出系数时「应用到设备」仍可用 | 很可能是测试过时(`3d2fb0c` 让该按钮应用对话框里选中的实时标定) | 读 `sensor_dialog.py:324` 一带确认行为后改断言;若该按钮在什么都没选时也可用并会出错,按产品缺陷处理 | | 9 | `test_pause_controls.py::test_the_transport_says_which_way_it_goes` | 暂停提示不再提到录制会留空档 | **需决定**:`f2bbab7`(10-08)为缩短文案删掉了这句,但修复计划 A 要求提示写明空档 | 建议恢复一句短的"录制会留空档",测试保持不变;须用户确认 | | 10 | `test_ui_smoke.py::test_the_replay_view_is_the_live_view_with_a_video_source`,以及 `ui_check` 的同名检查 | 回放与实时的分栏宽度不同(589/825 对 643/771) | **需决定**:可能是 `321e52f`(10-06,改善回放布局)有意改变,也可能是回归 | 用 `git bisect` 在测试上定位引入提交,向用户说明那次改动的意图,再决定改测试还是改代码 | | 11 | `test_ui_smoke.py::test_the_rail_puts_result_identity_before_a_long_video_name` | 仅 Linux 失败(字体宽度不同),Windows 通过 | 环境差异 | 不改;在门禁说明里写明门禁平台是 Windows | | 12 | `shared/tests/test_worker_frame_rate.py` 2 项 | "30 fps 设备只消费到 21 fps",取决于机器负载 | 计时测试,本身不稳定 | 注册并标记为 `timing`,移出合入门禁;另开任务把它改成确定性测试 | | 13 | `shared/tests/test_scene3d_protect.py::test_the_release_gate_fails_an_unsigned_package` | 仅 Linux 失败 | 环境差异 | 不改;记录在案 | ## 4. 实施阶段 | 阶段 | 内容 | 验收 | 估时 | |---|---|---|---| | 0 基线 | 记录 HEAD、解释器、PySide6 与 pyqtgraph 版本;分别在 `apps/studio`、`shared` 下跑全量(`-m "not timing"`),并运行 `ui_check.py`,产出 JUnit XML | scratch 下有失败清单,与第 3 节逐项对上;对不上的项先报告用户 | 0.5 h | | 1 让门禁能跑 | 在根 `pyproject.toml` 注册 `timing` 标记,并加 `--strict-markers`;标记第 3 节 12 号的两项;解决根目录收集问题(建议:把 studio 测试从 `tests.conftest` 导入的辅助函数移到 `apps/studio/tests/_studio_helpers.py`,并在 studio 的 conftest 里把该目录加入 `sys.path`;同名测试文件用 `--import-mode=importlib` 或改名解决,**不改 autocal 文件**) | 在仓库根目录执行 `python -m pytest --collect-only -q` 时收集错误为 0;`-m "not timing"` 确实排除了 2 项 | 2–3 h | | 2 测试数据 | 第 3 节 1、2 号:新建 `apps/studio/tests/data/`,放一段真实的 box 短录像(`raw_0001.mp4`、`frames.csv`、`forces.csv`、`events.csv`、`session.json`),约 30 帧;所有测试改为从这里读取;只需要回放接线的测试,优先用 `ExperimentRecorder`(`shared/.../record/experiment.py`)在 fixture 里生成合成录像,写法参考 `ui_snapshot.py --scene replay`;数据缺失时一律明确失败,不跳过 | 两个文件的全部用例在干净的检出上可运行;`dist/` 不再出现在任何测试路径中(`grep -r "dist" apps/studio/tests` 只剩注释) | 2–3 h,另需用户录一段 | | 3 逐项修复 | 第 3 节 3–8 号直接修;9、10 号先问用户再修 | 每项记录修前失败、修后通过;断言数量不减少(列出每个改动测试的断言前后对比) | 3–4 h | | 4 门禁脚本 | 新增 `tools/gate.ps1`:依次运行 `tools/lint_gate.py`(改动文件)、studio 全量、shared 全量、`ui_check.py`;全部输出写进 scratch 并汇总为一页;读取 `tools/known_failures.txt`,**该文件必须为空,有内容就让门禁失败**;记录跳过数,超过 `tools/skip_budget.txt` 中的上限就失败;收集数低于下限也失败(防止整个模块被静默跳过) | 在当前状态运行 `gate.ps1` 退出码为 0;故意改坏一个断言后退出码非 0;故意删掉测试数据后退出码非 0 | 3–4 h | | 5 接入流程 | 在 `orisys-app` 提供可选的 `pre-push` 钩子(调用 `gate.ps1`,安装方式写进 README,不自动安装);修订 `test-execution-plan.zh.md`:定向测试用于开发中,**提交前跑一次 `gate.ps1`**;第 4 节表格里加"门禁平台:Windows" | 文档门禁通过;README 写有安装钩子的一条命令 | 1 h | | 6 收尾 | 以最终状态跑一次 `gate.ps1`;写 `commit-message.txt`;用中文汇报 | 汇报区分:已修、需用户决定、移出门禁的(含理由)、仍未解决的 | 0.5 h | 阶段 1 必须先于其他阶段完成;阶段 2 和 3 可以交错进行;阶段 4 依赖前三个阶段全部变绿。总计约 12–16 小时,外加用户录制测试数据的时间。 ## 5. 需要用户先决定的事 1. **测试录像放在哪里。** 建议放进 git(`apps/studio/tests/data/`,控制在 5 MB 以内);如果不想让仓库变大,就改用环境变量 `ORISYS_TEST_DATA` 指向 NAS 上的目录,但缺失时测试必须失败,不能跳过。还需要用户用 box 传感器录一段约 1 秒的短录像。 2. 第 3 节 9 号:暂停提示是否恢复"录制会留空档"。 3. 第 3 节 10 号:回放与实时布局不一致,属于有意改动还是回归(执行者 bisect 之后再问)。 4. 以后是否搭 Gitea runner(Windows 自托管)自动跑 `gate.ps1`。本计划只做到本地钩子;runner 另开任务。 ## 6. 不在本计划内 计时测试改为确定性测试;GL 真机冒烟测试;autocal 测试树的问题;测试目录重排。这些都在门禁稳定之后另行安排。 ## 交接记录 按 [代理交接约定](../../docs/platform/agent-handoff.zh.md) 维护;只保留当前状态和每轮一行摘要。 - task_id:test-repair;round:1;updated_at:2026-10-18 17:45 +08:00 - status:ready;owner:Claude(定义);next_agent:DeepSeek(执行) - 目标与完成条件:本轮只做第 4 节阶段 0 和 1,验收以该两行为准;阶段 2 起留待下一轮 - 范围:`pyproject.toml`、`apps/studio/tests/**`、`shared/tests/**` 的标记与收集修复,新增 `apps/studio/tests/_studio_helpers.py`;排除 `apps/autocal/**`、产品源码、locale 与 `docs/` 以外的文档 - 停止条件:交接约定的默认条件;收集问题必须改 autocal 文件时停止 - 时间预算:按阶段分别计算,取第 4 节估时区间的上限(阶段 0 为 0.5 h,阶段 1 为 3 h), 不含等待用户或数据的时间;超出 50% 即触发停止条件 - 影响面:根 `pyproject.toml` 同时影响 autocal 测试树;记录受影响的 autocal 测试并在汇报中提醒用户, 不直接联系 autocal 任务(`.scratch/autocal-*` 中带 `.active` 的目录) - 授权:用户 2026-10-18 17:17 要求制定计划交 DeepSeek 执行、17:40 确认启动;仅授权本地修改与测试,提交、推送、安装依赖仍需用户另行授权 ### 基准与输入 | 仓库/checkout | 分支、HEAD | 起始暂存/未暂存/未跟踪变化 | 本轮修改 | |---|---|---|---| | `D:\orisys_ws\orisys-app` | `main`、`e268a9d`(studio/shared 与 `85d0b39` 相同) | autocal 有其他任务的改动,不属本任务 | 待填 | - 环境:`D:\orisys_ws\orisys-app\.venv-build\Scripts\python.exe` - 输入:不适用(阶段 2 的测试录像尚待用户提供) ### 执行结果 | 验收项与预期 | 实际命令及 run_id | 实际结果 | 证据路径 | |---|---|---|---| | 阶段 0:失败清单与第 3 节对上 | 待填 | 待填 | 待填 | | 阶段 1:根目录收集错误为 0;`timing` 排除 2 项 | 待填 | 待填 | 待填 | ### 验收与下一步 - 轮次摘要:(执行后填写) - 验收者:Claude 或 GPT;决定:待验收 - 下一步:DeepSeek 执行阶段 0–1,交付 review