跳转至

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/ 中的集成测试覆盖:

测试 support 会创建临时 session/socket/repo/command,必须保证失败后也清理,并避免并行测试共享默认 session。

16.7 Keyboard corpus

键盘协议组合爆炸,不适合每个 case 手写测试。fixtures 包括:

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 测试揭示的架构不变量

从测试主题反推,维护者最在意:

  1. identity 稳定:公开 ID、TerminalId、session generation 不能混;
  2. 旧事件无权覆盖新事实:sequence/operation id/render baseline;
  3. 资源所有权明确:detach/handoff/update 不误杀 PTY;
  4. 跨平台能力诚实:不支持就显式降级;
  5. 热路径结构受控:I/O/进程查询/大 clone 不回流;
  6. 用户文件可逆编辑:config/integration/plugin registry 不覆盖未知内容;
  7. 协议可演进: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 适合贡献的切入点

按风险从低到高:

  1. 新增/修正 Agent manifest fixture 和 explain 测试;
  2. 配置参考/diagnostic 与 schema 同步;
  3. 新 Integration target 的幂等 config editor + asset tests;
  4. Plugin action/pane 的 validation 和错误语义;
  5. worktree/API 的 stale completion测试;
  6. render profiling/fallback 可观测性;
  7. TerminalRuntime/PaneRuntime 边界重构;
  8. 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 可以被概括为六个相互咬合的设计决定:

  1. 服务端拥有长寿命事实与 PTY,客户端拥有设备。
  2. 可持久状态、活跃 runtime 和视图 attachment 由稳定身份连接,而不是揉成一个对象。
  3. Agent 状态来自多证据显式仲裁,所有异步来源都必须证明新鲜度。
  4. 磁盘恢复重建结构,Live Handoff 转移活资源;两者都以失败回滚为一等路径。
  5. 重量 I/O 外移,主循环保持单写者;队列、信号、帧和客户端各自有背压语义。
  6. 测试不仅验证输出,还守住架构、供应链、迁移和跨平台能力边界。

真正可迁移的不是某个 Rust enum,而是这套思考方式:先识别长期资源和唯一 owner,再为每个异步边界设计 identity、sequence、commit point 与 rollback。终端、Agent、Git 和远程只是它在 Herdr 中的具体载体。

接下来可用源码地图按问题定位文件,用术语与数据字典快速对照身份和协议,用证据附录复现本文数字与基线。