跳转至

17 · 测试与开发闭环

Omarchy 的测试不是一种技术覆盖所有层。CLI router、纯数据模型、系统脚本和真实 Wayland 桌面分别使用不同的成本与隔离级别。

三层测试金字塔

flowchart TB
    Acceptance[VM 图形验收<br/>6 条 workflow]
    Shell[Shell/脚本契约<br/>190 个 runnable 文件]
    CLI[CLI router + theme pipeline<br/>1 个大 suite]
    CLI --> Shell --> Acceptance

test/all 只运行 CLI 与 shell suite,并且即使前一 suite 失败也继续,最后汇总。acceptance 明确排除在日常 all 之外,因为它需要可丢弃 VM 和真实桌面。

CLI suite 负责什么

test/cli 集中约束:

  • help/group 渲染;
  • route、alias、hidden command;
  • trailing --help 不执行目标;
  • 每个 omarchy-* executable 都有摘要;
  • removed/redundant metadata 不再出现;
  • theme template/color/OSC 与同步命令;
  • theme state migrations。

Router 是一个全局注册表,集中测试适合捕获跨命令 collision;具体命令行为则更多落到独立 shell test 文件。

shell runner 的失败粒度

test/shell 自动发现 test/shell.d/*-test.sh,跳过作为库的 base-test.sh。每个文件第一条失败 assertion 就退出,但 runner 继续剩余文件,最后汇总。

为什么不在一个文件内继续?后续 assertion 往往依赖之前建立的状态,前置失败后继续会产生噪声。为什么 runner 又继续其他文件?独立领域不应被一个 packaging failure 全部隐藏。

flowchart LR
    Runner --> A[Test file A]
    Runner --> B[Test file B]
    Runner --> C[Test file C]
    A -->|首个 assertion 失败| StopA[终止 A]
    StopA --> Runner
    B --> Runner
    C --> Runner
    Runner --> Summary[统一失败摘要]

固定快照有 191 个 *-test.sh(含 base),即约 190 个 runnable shell test 文件。

fake HOME,real source

系统脚本测试常用:

  • mktemp -d 建 fake HOME;
  • OMARCHY_PATH="$ROOT" 指向真实 checkout;
  • 在临时 bin/ 放 sudo、pacman、gsettings 等 stub;
  • stub 把参数写 call log;
  • 执行真实 bin/omarchy-*,检查 log 与生成文件。

这种方法既不修改开发机,又比 mock 被测函数更接近真实 Bash 边界。测试的世界是假的,被测脚本本身是真的。

QML 逻辑为什么能用 Node 测

MenuModel、BarModel 等把解析/合并/布局写成 plain JS,并在 Node 环境检测到 module 时导出 CommonJS。QML 正常 import 时忽略这段。

run_node_test 给 heredoc 加 assertion prelude,再让 Node 加载同一文件:

const menu = requireFromRoot("shell/plugins/menu/MenuModel.js")
const parsed = menu.parseMenuJsonc('{ "root": { "label": "Go" }, }')
assertEqual(parsed.length, 1, "trailing comma parses")

把纯逻辑从 QML component 生命周期分离,既提升可测性,也迫使数据变换具有明确输入输出。

compositor gate

只检查 WAYLAND_DISPLAY 环境变量不够:sandbox 可能继承变量却无法访问 socket,已死亡 compositor 也可能留下 socket。require_compositor 会:

  1. 检查 runtime socket;
  2. 在有 Hyprland signature 时重试 hyprctl -j monitors;
  3. 不可达则以成功 skip 结束该测试;
  4. 可达时禁 core dump,避免 QGuiApplication 断连留下垃圾。

静态契约应放在 gate 之前,只有真正需要渲染/连接的部分才跳过,避免 headless CI 失去全部覆盖。

migration 测试

迁移测试构造 legacy fake HOME,直接以 bash -euo pipefail 运行具体迁移,并至少验证:

  • legacy state 被正确转换;
  • 第二次运行仍安全;
  • 非 legacy 或已定制状态不被误改;
  • marker 语义由 runner 测试另行覆盖。

这比只断言脚本包含某条命令强,因为真正验证了状态转换。

图形 acceptance

test/acceptance 通常通过 sibling omarchy-iso 创建的 disposable VM 运行。SSH 进入后,它从 user systemd/runtime 重新发现 Hyprland signature、Wayland display 与 D-Bus,而不依赖 SSH 环境继承。

六条 runnable workflow 覆盖:session、apps、menu、panels、shell surfaces、system。测试使用:

  • hyprctl 查询窗口/monitor;
  • wtype 模拟会话内输入;
  • grim 截图与 OCR/像素证据;
  • host 通过 QMP 发送全局 key combo(guest 工具不能可靠模拟全部 compositor global binding);
  • artifact 目录收集截图和日志。

每个 workflow 有 timeout,某一项失败不阻止其余验收。视觉验证不能被纯 JS 测试替代:布局、焦点、layer surface、字体与 GPU 都是运行时事实。

什么测试放在哪里

变化 首选测试
新 CLI route/metadata test/cli
Bash 命令副作用 独立 shell test + stub world
JS parse/merge/layout Node-driven shell test
QML manifest/entrypoint 契约 静态 shell test,必要时 compositor gate
migration fake HOME 状态转换 + 二次运行
快捷键打开真实 UI VM acceptance
GPU/固件/物理音频 物理硬件验证 + 可做的结构测试

关键源码索引

最后两章把分散机制重新串成路径与经验。先看:18 · 端到端调用链。