Add docs/agent-handoff (backlog, execution state, verification, platform plan, per-task evidence), repo AGENTS.md, and architecture notes updated for the dual-renderer design.
105 lines
8.3 KiB
Markdown
105 lines
8.3 KiB
Markdown
# P2-04 证据:跨平台聚合测试入口
|
||
|
||
状态:COMPLETE
|
||
日期:2026-07-17(本机执行时间)
|
||
平台:Windows 11 10.0.26200 x64(主)+ WSL Ubuntu-22.04 / CPython 3.12.3(Linux 路径验证)
|
||
|
||
## 交付物
|
||
|
||
- `tests/run_all.py`(新增,唯一新文件):跨平台聚合测试入口。
|
||
- `python tests/run_all.py --group logic|offscreen|all`(默认 all)
|
||
- `--list` 只列条目;`--only <id,子串>` 跑子集(调试);`--keep-logs` 保留全部子日志(默认只留失败/超时)。
|
||
- 设计:每个子测试 = 独立子进程 + 独立临时目录(`haocode_RUN_<id>`/`haocode_CONF_<id>`,POSIX 走 TMPDIR、Windows 走 TEMP)+ 显式秒级 timeout(`subprocess.run(timeout=...)`);offscreen 组子进程注入 `QT_QPA_PLATFORM=offscreen`、`HAOCODE_RENDER=software`、`QTWEBENGINE_CHROMIUM_FLAGS=--disable-gpu`(均 setdefault,不覆盖外层)。
|
||
- 退出码语义:任一 FAIL/TIMEOUT → 退出码 1(并打印"失败命令"清单);SKIP 不影响退出码。
|
||
- SKIP 双闸门:①依赖探测(`importlib.util.find_spec`:node/openai/PyMuPDF/PyQt6,缺失即 SKIP 并给理由);②平台令牌(`platform:win32` 等,平台不适用即 SKIP 并给理由)。
|
||
- 聚合鲁棒性:子进程崩溃/超时只记 FAIL/TIMEOUT,不中断后续;输出统一 UTF-8(`errors=replace`,Windows cp936/cp1252 宿主安全);WSL 时钟回拨保护(耗时 clamp ≥0)。
|
||
- 不要求 pytest / npm / 网络;聚合器自身零第三方依赖。
|
||
|
||
## 默认聚合范围(25 条)与排除项
|
||
|
||
- logic 12 条:run_tests.py(agent core 41)、test_tool_params、test_bash_stream、test_copy_session、test_compaction_persist、test_file_attach、test_cross_platform_shell、test_global_hotkey_platforms、test_wv2_guard、test_pdf_reader、test_math_extract.js、test_render_window.js。
|
||
- offscreen 13 条:smoke_offscreen、smoke_mode、smoke_copy_session、smoke_bash_panel、smoke_timeline、smoke_midswitch、test_main_window_event_filter、test_config_isolation、test_error_persist、test_think_code_neutral、test_debug_window、test_renderer_matrix、test_screen_capture_platforms。
|
||
- 默认排除(有注释理由,不进默认聚合):
|
||
- `diag_*`(rename_overlay / panel_scrollbar / render_scale):人工/像素诊断,需真机或交互。
|
||
- `verify_*`:人工验证脚本。
|
||
- `tune_model_popup.py`:调参实验。
|
||
- `smoke_offscreen.py` 之外的旧式 real-DB 冒烟(引用真实 `data/haocode.db`,未接临时库)。
|
||
- `diag_live_agent.py`、`probe_agent_loop.py`:需真实 API 凭据。
|
||
- `_probe_*`、`_test_env.py`:探针/工具模块(被其他测试 import,非独立用例)。
|
||
- `run_all.py` 自身:聚合器不入聚合。
|
||
|
||
## 汇总结果(硬约束要求:Windows/Linux 各一份)
|
||
|
||
### Windows 11 / CPython 3.10.21(.venv,PyQt6+openai+PyMuPDF 齐全)
|
||
|
||
| 组 | 结果 | 耗时 |
|
||
|---|---|---|
|
||
| `--group logic` | **PASS 12 / FAIL 0 / SKIP 0**(EXIT=0) | 43.9s |
|
||
| `--group offscreen` | **PASS 13 / FAIL 0 / SKIP 0**(EXIT=0) | 81.9s |
|
||
| `--group all` | 25/25 PASS(EXIT=0,两组顺序执行) | ~126s |
|
||
|
||
- 日志样例:`D:/tmp/runall_win_logic2.log`、`D:/tmp/runall_win_offscreen.log`。
|
||
- offscreen 明细:smoke_offscreen 8.3s、smoke_bash_panel 30.3s、smoke_midswitch 8.7s、test_think_code_neutral 9.3s 等 13 条全 PASS。
|
||
|
||
### WSL Ubuntu-22.04 / CPython 3.12.3(系统 python3,无 PyQt6/openai/PyMuPDF,离线环境)
|
||
|
||
| 组 | 结果 | 耗时 |
|
||
|---|---|---|
|
||
| `--group all` | **PASS 4 / FAIL 0 / SKIP 21**(EXIT=0) | 0.9s |
|
||
|
||
- PASS 4 = test_copy_session、test_file_attach、test_math_extract.js、test_render_window.js(纯 stdlib/node)。
|
||
- SKIP 21 全部带明确理由:无 openai(5,禁止联网安装)、无 PyQt6(14,offscreen 组整体)、无 PyMuPDF(1)、平台不适用 win32-only(1,test_wv2_guard:msvcrt 单实例互斥是 WebView2 守卫的 Windows 专属机制,Linux 无 WebView2 链路)、无 PyQt6 的 GUI 逻辑(1,test_global_hotkey_platforms 含 QShortcut 构造)。
|
||
- 证明:聚合器在 Linux/3.12 上可运行、隔离约定在 POSIX(TMPDIR)成立、诚实 SKIP 不伪装通过、JS 条目跨平台运行。
|
||
- 注:WSL 无 .venv 且离线,依赖型条目按硬约束 SKIP;在按 VERIFICATION 矩阵备齐依赖的 Linux 真机上同命令即可执行全部条目(offscreen 组仍需真实 Linux 桌面环境完成 P1-03/P1-04 的平台验证,状态不变)。
|
||
|
||
## 故意失败夹具演示(硬约束:崩溃/超时不阻断汇总、退出码反映失败)
|
||
|
||
- 临时夹具(用后即删,未提交):
|
||
- 崩溃夹具(`raise RuntimeError`)→ 聚合器记 **FAIL** 并继续跑后续健康测试。
|
||
- 超时夹具(`time.sleep(60)` + budget 5s)→ 聚合器记 **TIMEOUT** 并继续。
|
||
- 演示运行:`总计 3: PASS 1 FAIL 1 TIMEOUT 1`,退出码 **1**,完整汇总 + 失败命令清单正常打印。
|
||
- 演示日志:`D:/tmp/runall_fixture_demo.log`;夹具文件已删除,仓库无残留(已 rg 复核)。
|
||
|
||
## 等价性(硬约束:单文件命令仍可直接运行,输出与聚合子进程一致)
|
||
|
||
- `test_copy_session.py`:
|
||
- 独立运行:`===== 54/54 PASS ===== / ALL PASS`
|
||
- 聚合子进程(保留子日志 tail):`===== 54/54 PASS ===== / ALL PASS` —— 逐字一致。
|
||
- 其余条目同理(聚合即 `subprocess.run([sys.executable, ...原命令...])`,命令形态未变)。
|
||
|
||
## 聚合入口暴露并修复的两个真实缺陷
|
||
|
||
1. **`tests/test_compaction_persist.py` T9 陈旧断言**(跨平台共同项,Windows 上 FAIL):
|
||
- 断言用 `assertIn('msg["role"] not in', src)` 硬编码变量名,而 `ui/views/main_window.py` 现行代码用 `m["role"] not in`(P1-01/P2-01 期间变量名演化,测试未跟进)。
|
||
- 修复:改为变量名无关的语义断言(同时接受 `msg["role"] not in` / `m["role"] not in` 两种等价写法,检查"摘要标记行被过滤"这一语义本身)。
|
||
- 验证:单跑 41/41 PASS;聚合 logic 组 12/12 PASS。
|
||
2. **`core/agent/compaction.py` dataclass 不可哈希默认值(Python 3.11+ 崩溃,真跨平台 bug)**:
|
||
- `CompactionPreparation.settings: CompactionSettings = DEFAULT_COMPACTION_SETTINGS`:默认值是 eq-dataclass 实例(`__hash__=None`)。Python ≤3.10 的 dataclass 只拒 list/dict/set 默认值 → 合法;Python 3.11+ 追加"不可哈希默认值"检查 → **import 即 ValueError**。
|
||
- 影响面:Ubuntu 24.04 自带 CPython 3.12(VERIFICATION 矩阵明确支持的 Linux 目标)—— 任何 import `core.agent` 的模块/测试在 3.11+ 全灭。由聚合入口在 WSL/3.12 上首次系统性暴露。
|
||
- 修复(1 行,语义完全等价):`settings: CompactionSettings = field(default_factory=lambda: DEFAULT_COMPACTION_SETTINGS)` —— 仍返回同一共享默认实例,3.10 行为不变。
|
||
- 验证:3.10 本地 `test_compaction_persist` 41/41 + `run_tests` 41/41 无回归;WSL/3.12 上 `core.agent` 链 import 通过(3 个 openai 依赖条目越过 compaction 后才在 openai 处 SKIP,证明 3.12 兼容)。
|
||
|
||
## 注册表元数据修正(诚实 SKIP 的前提)
|
||
|
||
- `test_tool_params.py` / `test_bash_stream.py` / `test_cross_platform_shell.py`:补 `openai` 依赖(均 import `core.agent.tools` → 顶层 `from openai import OpenAI`)。
|
||
- `test_global_hotkey_platforms.py`:补 `pyqt6` 依赖(含 QShortcut/overlay 构造)。
|
||
- `test_wv2_guard.py`:标 `platform:win32`(T4 msvcrt 跨进程互斥为 Windows 专属;非 win32 上 `acquire_instance_lock()` 按设计返回 None)。
|
||
|
||
## 边界遵守
|
||
|
||
- 未动任何独立测试命令与既有入口(`run_tests.py` 原样保留)。
|
||
- 未要求 pytest/npm/网络;聚合器零第三方依赖。
|
||
- 默认聚合未启动任何真实 API / 人工诊断 / 真实桌面 / 凭据脚本(见排除清单)。
|
||
- 未读取真实配置(`data/config.json` 零访问);未删除用户运行数据;夹具用后即删。
|
||
- 产品代码改动仅 2 处且均为聚合暴露的缺陷修复:`tests/test_compaction_persist.py`(测试断言更新)+ `core/agent/compaction.py`(1 行 3.11+ 兼容)。
|
||
|
||
## 复现命令
|
||
|
||
```text
|
||
python tests/run_all.py --list
|
||
python tests/run_all.py --group logic
|
||
python tests/run_all.py --group offscreen
|
||
python tests/run_all.py --group all
|
||
python tests/run_all.py --only test_copy_session --keep-logs
|
||
```
|