16 · 测试版图、演进痕迹与设计评价¶
大型并发终端项目的真实架构,往往最清楚地写在测试里:哪些迟到事件必须拒绝、哪种旧 snapshot 仍要恢复、哪个平台路径不能退化。固定快照按 Rust 测试属性扫描有 3458 个测试入口,这个密度说明 Herdr 正用 characterization tests 锁住快速演进中的行为。
16.1 测试规模¶
| 指标 | 固定快照统计 |
|---|---|
src/ Rust 文件 |
243 |
src/ Rust 行数 |
220,337 |
#[test] |
3,114 |
#[tokio::test] |
344 |
| Rust 测试入口合计 | 3,458 |
#[ignore] |
1 |
tests/ Rust 集成测试文件 |
24 |
scripts/test_*.py 维护测试文件 |
11 |
| screen detection manifest | 19 |
这些是文本口径,用于理解规模,不等同 cargo nextest list 在特定 target/cfg 下的可运行用例数。复现命令见证据附录。
16.2 just test 和 just check¶
justfile 把日常验证分层:
just test
cargo nextest --locked
Python maintenance tests
UI hot-path architecture test
Bun integration/plugin asset tests
just check
fmt/check + clippy -D warnings
nextest / CI tasks
Windows target lint from Unix/macOS
maintenance tests
--locked 保证依赖解析与 Cargo.lock 一致;nextest 提供并行和更清晰的慢/失败输出;Windows cross-target clippy 在非 Windows 开发机提前发现 cfg(windows) 漂移。
16.3 六层测试金字塔¶
flowchart TB
E2E["真实进程/PTY/server/client/handoff E2E"]
Int["tests/ crate-level integration"]
State["状态机与领域 action tests"]
Parse["parser/manifest/config/protocol fixtures"]
Struct["Python 静态架构与仓库一致性守卫"]
CI["跨 OS/package/release pipelines"]
CI --> Struct --> Parse --> State --> Int --> E2E
它不是单纯“越上面越少”。Herdr 很多单元测试就在大型源文件内部,用私有 helper构造复杂状态;维护脚本则横跨仓库检查 Rust 单测不易表达的结构约束。
16.4 纯数据结构测试¶
适合快速、穷举边界:
- BSP split/resize/navigation;
- version/semver/channel parsing;
- wire serialize/deserialize 和大小上限;
- worktree porcelain parser;
- config/keybinding overlay;
- manifest gates/priority/regions;
- public ID parse/alias;
- snapshot migration。
这类测试应该没有真实 sleep/网络,能大量覆盖反例。例如 layout 测 NaN ratio、missing target 和 first/second child方向,比只截一张 UI 图更稳定。
16.5 状态机测试¶
Herdr 最有价值的一批测试围绕时间顺序:
Agent 仲裁¶
- stale hook sequence 被拒绝;
- process exit 后旧 generation 不复活;
- full-lifecycle hook 与 visible blocker precedence;
- transcript
skip_state_update保留已有状态; - Done 只由 pane seen 投影。
API wait¶
- setup 窗口发生事件不丢;
- probe 已反映的事件不重复发;
- pane identity 更换返回 not running;
- client disconnect 取消 wait;
- prompt 必须观察到 state sequence 推进。
Worktree¶
- stale operation id 不清除新 pending;
- dirty 需要 force;
- unrelated replacement directory 不被删除。
Client render¶
- queue 失败不推进 baseline;
- reconnect/full repaint 恢复;
- nonforeground size 不 resize PTY。
状态机测试的核心技巧是直接排列事件,而不是依赖真实调度“碰巧”制造 race。
16.6 真实 PTY 与进程测试¶
Parser 单测不能证明 OS ownership。tests/ 中的集成测试覆盖:
server_headless.rs:server 生命周期和交互;multi_client.rs:多个 client/所有权;detach_reattach.rs:断开后进程持续与重连;live_handoff.rs:FD/runtime 接管和回滚;auto_detect.rs:daemon 自动发现;tests/cli/:真实 API socket、CLI surface 和协议 guard。
测试 support 会创建临时 session/socket/repo/command,必须保证失败后也清理,并避免并行测试共享默认 session。
16.7 Keyboard corpus¶
键盘协议组合爆炸,不适合每个 case 手写测试。fixtures 包括:
keyboard_protocol_corpus.tsv;- macOS/Linux terminal variants;
- Windows enhanced input/ConPTY probes。
Corpus 方式让新增一个 terminal emulator 的真实序列成为数据行,并统一跑 raw decoder/normalized key/VT encoder链路。
16.8 Python 维护守卫¶
11 个 scripts/test_*.py 覆盖不适合普通 Rust 单测的仓库契约:
| 守卫 | 保护内容 |
|---|---|
| agent detection manifest check | manifest schema/version/rule一致性 |
| changelog | release note 格式与版本 |
| config reference check | 配置文档和 Rust model 同步 |
| docs translation parity | 多语言文档结构同步 |
| integration asset tests | bundled 资产/marker/入口 |
| Windows ConPTY package | 打包只使用受控来源 |
| preview | manifest、asset、checksum/build metadata |
| Unix installer | 安装脚本行为 |
| vendored libghostty-vt | patch/source pin 完整性 |
| vendored portable-pty | fork/patch 来源与 Windows loader |
| UI hot path architecture | 禁止已知昂贵调用回流 |
静态守卫的优势是快且明确;风险是依赖源码文本形状,重构时需同步更新。它们适合保护“禁止依赖/调用”这类负向架构规则。
16.9 JS/TS 资产测试¶
Integration 资产不是“复制进去就算完成”。Pi/OMP/OpenCode 等 JavaScript/TypeScript hook 用 Bun 直接测试:
- Agent event payload 到 Herdr API request 的映射;
- session replacement;
- start/stop/blocked sequence;
- environment 缺失和网络/API 失败;
- 不重复报告和 teardown。
这样能在 Agent 尚未真实安装时验证 bundled code,且测试的正是最终被 include_str! 发布的文件。
16.10 跨平台 CI¶
ci.yml 包含 Linux、macOS、Windows matrix,并单独验证 Windows ConPTY package。发布相关工作流还覆盖:
- stable/preview artifact;
- x86_64/aarch64 和三平台组合;
- SHA-256 生成;
- preview manifest/archive;
- Nix;
- Windows bundled/system/invalid ConPTY paths。
Live Handoff 是 Unix capability,因此 Windows matrix 显式过滤/替代相关用例,而不是把失败 test 粗暴 ignore。
16.11 Vendored 依赖为何要结构测试¶
Herdr vendored/patches portable-pty 与 libghostty-vt。这带来能力和修复控制,也带来供应链/升级风险:
- upstream commit/patch 是否仍对应;
- build flags/SIMD 在 target 上是否一致;
- 生成 FFI bindings 与 C layout 是否匹配;
- Windows loader是否只从打包的受控位置加载 DLL;
- Cargo.lock/path patch是否意外回到 crates.io 版本。
维护脚本把这些“仓库形状”变成测试,防止一次依赖升级绕过本地安全补丁。
16.12 测试揭示的架构不变量¶
从测试主题反推,维护者最在意:
- identity 稳定:公开 ID、TerminalId、session generation 不能混;
- 旧事件无权覆盖新事实:sequence/operation id/render baseline;
- 资源所有权明确:detach/handoff/update 不误杀 PTY;
- 跨平台能力诚实:不支持就显式降级;
- 热路径结构受控:I/O/进程查询/大 clone 不回流;
- 用户文件可逆编辑:config/integration/plugin registry 不覆盖未知内容;
- 协议可演进:version/schema/fixture/migration共同守边界。
这七条比模块目录更接近 Herdr 的架构宪法。
16.13 演进痕迹:从 Pane-centric 到 Terminal-centric¶
源码同时有 PaneRuntime、TerminalRuntime wrapper、PaneState 和 server runtime registry。结合 headless server 体量,可以推断当前正在进行的迁移:
flowchart LR
Old["旧:Pane 同时拥有布局 + PTY + VT"] --> Mid["当前:PaneState 附着 TerminalId\nTerminalRuntime 包装 PaneRuntime"]
Mid --> Future["方向:server-owned terminal components\nview 只引用稳定 identity"]
这是基于类型/调用关系的设计解释,不代表上游 roadmap。它解释了:
- 为什么有些方法仍以 pane 命名却操作 terminal;
- 为什么单进程
App与HeadlessServer有相似循环; - 为什么 runtime wrapper 很薄;
- 为什么新 API/事件越来越依赖 TerminalId/session identity。
16.14 当前架构的成熟点¶
可靠性边界成熟¶
- handoff 有显式 rollback;
- update 尊重 server/package manager owner;
- worktree force cleanup证明归属;
- wait 处理 setup race 和 identity reuse;
- render baseline在 send 成功后提交。
这些都是“正常路径之外”的工程质量。
可观测状态设计成熟¶
Agent 检测没有执着单一 parser,而是数据化规则、解释输出、source authority、sequence/generation。对于快速变化的外部 CLI,这是务实架构。
性能规则被制度化¶
有界队列、合并 render signal、retained fallback、scale benchmark 和静态 hot-path 守卫形成组合,而不是只靠手工 profile。
16.15 当前架构的压力点¶
大型协调文件¶
server/headless.rs、app/mod.rs、pane/terminal.rs、terminal/state.rs 都很大。风险不是行数本身,而是一次跨域修改可能同时碰 ownership、render、protocol 和 tests。
双运行循环¶
monolithic 与 headless 共享很多语义但有独立 orchestration。长期可考虑把核心 server loop 抽成统一 command/effect engine,单进程路径通过 in-process client/backend 复用。
TerminalState 子域聚合¶
Agent authority、session resume、title/progress/metadata 和 lifecycle 都集中。可以按“检测仲裁状态”“恢复描述”“共享呈现元数据”进一步组合成命名子结构,降低不变量跨距。
Plugin 信任边界¶
当前 plugin 是本地受信命令模型。若未来扩大到远程分发,需要明确权限、签名/来源和 sandbox;不能仅靠 manifest validation。
这些是源码审计评价,不是缺陷宣判。任何拆分都应先保留当前密集 characterization tests。
16.16 适合贡献的切入点¶
按风险从低到高:
- 新增/修正 Agent manifest fixture 和 explain 测试;
- 配置参考/diagnostic 与 schema 同步;
- 新 Integration target 的幂等 config editor + asset tests;
- Plugin action/pane 的 validation 和错误语义;
- worktree/API 的 stale completion测试;
- render profiling/fallback 可观测性;
- TerminalRuntime/PaneRuntime 边界重构;
- handoff/protocol 变更。
越靠后,越需要先跑跨平台、真实 PTY 和 Live Handoff 测试,而不只是本地 unit suite。
16.17 修改代码前的验证矩阵¶
| 改动 | 最低验证 |
|---|---|
| layout/UI action | 相关 unit + hot-path guard + headless render |
| terminal input/VT | keyboard corpus + PTY integration +三平台 cfg |
| Agent detection | manifest lint + fixtures + TerminalState arbitration |
| API schema | schema tests + CLI protocol guard + wait race tests |
| persistence | current + legacy fixture + future version拒绝 |
| worktree | 临时真实 Git repo + dirty/force/stale op |
| remote/update | manifest/checksum/path tests + running server plan |
| handoff | Unix E2E + failure injection + PTY process identity |
| vendored dependency | maintenance guard + target builds/packaging |
16.18 全书结论¶
Herdr 可以被概括为六个相互咬合的设计决定:
- 服务端拥有长寿命事实与 PTY,客户端拥有设备。
- 可持久状态、活跃 runtime 和视图 attachment 由稳定身份连接,而不是揉成一个对象。
- Agent 状态来自多证据显式仲裁,所有异步来源都必须证明新鲜度。
- 磁盘恢复重建结构,Live Handoff 转移活资源;两者都以失败回滚为一等路径。
- 重量 I/O 外移,主循环保持单写者;队列、信号、帧和客户端各自有背压语义。
- 测试不仅验证输出,还守住架构、供应链、迁移和跨平台能力边界。
真正可迁移的不是某个 Rust enum,而是这套思考方式:先识别长期资源和唯一 owner,再为每个异步边界设计 identity、sequence、commit point 与 rollback。终端、Agent、Git 和远程只是它在 Herdr 中的具体载体。