在本地运行测试
常规本地顺序¶
pnpm test:changed用于变更范围的 Vitest 验证。pnpm test <path-or-filter>用于单个文件、目录或显式目标。- 仅在你有意需要完整的本地 Vitest 套件时使用
pnpm test。
仓库封装器会将普通的未过滤 Gateway-server 运行模式选择和扩展的完整套件计划划分为每个进程最多 50 个测试文件。 随着清单增长,这会限制非隔离模块图,同时不改变工作进程并发数、堆限制或单个文件边界。原始 Vitest 和现有的单次调用选择(例如显式目标、覆盖率、报告输出、bail 和 watch 模式)保留其现有行为。
扩展的完整套件运行会将基础设施和宿主拥有的 SQLite 测试拆分为最多 64 个文件的批次。 每个批次都会将隔离的 fork 工作进程保持在现有完整套件的工作进程预算内。聚焦选择和 watch 模式保留其通常的路由。
创建真实托管 worktree 的测试必须满足 容量和磁盘空间要求, 包括为可执行设置脚本预留的额外空间。在整个运行期间保持该空间可用。
项目运行器会为单独的 --help 或 -h 请求打印封装器用法。
复合请求(包括 --help --no-help)遵循原生 Vitest 选项语义。
现有的 UI 目录目标仍限定在该目录范围内,即使与显式 E2E 测试文件组合也是如此。测试保留其所属的共享、隔离或浏览器 lane。需要整个 lane 覆盖的 UI 源/支持文件目标(例如共享样式或设置文件)仍使用更宽泛的回退;如果你希望运行范围受限,请使用目录或显式测试文件。
当一次性路由运行仅针对显式测试文件路径时,每个选中的 Vitest 调用必须发现至少一个测试文件。排除所有选中文件会失败,即使该 lane 通常允许空运行。若要有意允许该结果,请使用 pnpm test <test-file-path> -- --passWithNoTests。使用 --passWithNoTests=false 可显式要求非空发现。更宽泛的选择器和源派生选择保留其 lane 默认值。
通过 scripts/run-vitest.mjs 的显式 --config 运行保留其更严格的命名文件策略,并且不允许空的命名文件运行。插件的 --allow-no-tests 和 --allow-empty-after-exclude 控制保持不变。
Codex 和其他链接/稀疏 worktree 可以运行本地测试和检查。工具要求依赖项已准备就绪;它不会创建到主 checkout 的隐式链接。当另一个任务使用现有借用安装时,保持其不变。源安装的 pnpm:devPreinstall 检查会在正常依赖协调之前,拒绝 checkout 根模块目录及其 .pnpm 目录中的借用链接。它不会检查每个工作区包的依赖项或锁定路径以应对并发替换;--ignore-scripts 会跳过它,并且替代的 pnpm 目录设置不会经过全面验证。当依赖项就绪后,使用上述常规命令。为避免 pnpm 针对现有共享安装的包管理器预检,请使用这些直接 Node 测试框架:
- 在依赖项就绪时进行有界的聚焦验证:
node scripts/run-vitest.mjs <path-or-filter>。 - 变更的 typecheck/lint/guard 验证:
node scripts/check-changed.mjs。
新的源安装会从 pnpm store 克隆包文件,当文件系统无法克隆时回退为复制。因此,不同的 checkout 保持独立的文件元数据:在其他地方安装依赖项不会通过共享硬链接更改活动编译器输入的修改历史。现有硬链接安装不会被最新的 pnpm install 转换;对于隔离验证,请使用新的任务拥有的 checkout 和安装。不要重新安装借用的依赖项,也不要在另一个任务使用安装时替换它。
对于 Control UI 路由测试,运行 node scripts/run-tsgo-core-test-shards.mjs ui 以检查 fixture 类型;node scripts/run-tsgo.mjs -p tsconfig.ui.json 检查生产 UI 代码并排除测试。针对加载器所需的能力对路由 fixture 进行类型检查,而不是将部分 fixture 断言为完整的应用上下文。在生命周期测试中保留真实的选择能力,以便 agent 范围变更和订阅清理遵循应用行为。
对于远程环境验证,直接调用 node scripts/crabbox-wrapper.mjs。
在链接 worktree 中避免本地 pnpm crabbox:run,因为 pnpm 可能在远程封装器启动之前协调依赖项。
核心命令¶
在 Node 24.16+ 或 Node 26.1+ 上运行测试工具链,以匹配打包的运行时下限。较旧的 Node 绑定可能会在嵌入的 NUL 字符处截断 SQLite TEXT 值。
要在 Node 和 Bun 上比较相同的 Vitest 选择,请使用现有封装器:
OPENCLAW_VITEST_RUNTIME=node pnpm test <path-or-filter>
OPENCLAW_VITEST_RUNTIME=bun pnpm test <path-or-filter>
安装由 .github/actions/setup-test-bun/action.yml 固定的确切 Bun fork 构建,以获得可比较的结果。这会选择实际的 Vitest 进程和工作进程,同时保留 Node 用于编排和编译器准备。它不使用 Bun 的原生测试运行器。仅使用 bun run 不会为测试选择 Bun。Node 仍是本地默认。
对于 CI Control UI 比较,先运行完整的 Node 选择,然后运行其兼容的 Bun 分区:
OPENCLAW_NODE_TEST_CONFIGS_JSON='["ui/vitest.config.ts"]' \
OPENCLAW_NODE_TEST_VITEST_ARGS_JSON='["--maxWorkers", "3"]' \
OPENCLAW_CI_TEST_RUNTIME_POLICY=dual \
node --import tsx scripts/ci-run-node-test-shard.mts
Bun 分区有意排除两个完整的 GC 敏感文件,这些文件仍由 Node 覆盖。直接使用 OPENCLAW_VITEST_RUNTIME=bun 运行完整的 UI 配置也会运行这些当前不兼容的断言。
测试进程及其 CLI fixture 保持启用 Sparkplug 基线编译,但同步运行它。这避免了 Node 24 关闭死锁:后台编译器等待主线程垃圾回收,而 process.exit 会 join 该编译器。共享的 Node 参数策略负责这项仅限测试的缓解措施;生产 CLI 退出行为、断言和截止时间保持不变。
The script erasability gate uses Node's strip-only parser, including when package
checks run under Bun. It selects an installed Node runtime and skips Bun's node shim.
The test toolchain pins stable Vitest 5.0.1, including its browser and coverage
packages. Use describe(name, { concurrent: false }, callback) for ordered
suites. Await asynchronous assertions, keep vi.mock/vi.hoisted at module
scope, and perform actions whose mock calls you assert inside the test.
OpenClaw sets clearMocks: false, so setup and beforeAll calls are preserved.
Clear or reset each assertion's owned mock actions explicitly as needed.
Name patterns spanning suites use suite > test; native JSON retains its
space-joined fullName, so evidence readers match ancestorTitles plus title.
Filesystem transform caching uses test.fsModuleCache and
test.fsModuleCachePath; the existing OPENCLAW_VITEST_FS_MODULE_CACHE and
OPENCLAW_VITEST_FS_MODULE_CACHE_PATH controls retain their ownership and
disable behavior. Cache-key plugins use defineCacheKeyGenerator.
The jsdom lanes optimize Lit and its exported subpaths together through
deps.optimizer.client. CodeMirror and Lezer stay in Vite's module graph so
editor classes and parser properties retain one dependency identity.
When NODE_COMPILE_CACHE is configured, test launchers preserve it for Vitest
and its workers. Vitest disables bytecode caching in workers and their child
processes for V8 and custom coverage providers; explicit
NODE_DISABLE_COMPILE_CACHE=1 still disables caching for the entire invocation.
Inline projects inherit root configuration in Vitest 5, including concatenated
setup and include arrays. The four UI E2E resource projects declare
extends: false because each supplies its complete inventory and setup.
Maintained JavaScript tooling wrappers and root package commands load TypeScript
through scripts/tsx.mjs, using tsx's ESM entry. This preserves native loading of
compiled ESM plugins and their import-only dependencies, including when loaded
through require(). Source TypeScript imports and tsconfig path aliases remain
available.
These launchers retain tsx's in-process transform cache and Node's module cache.
They skip tsx's shared disk cache before the loader starts, and child tooling
inherits that policy. This cache policy does not clean
existing temporary directories, Node or Vitest caches, or other global caches. Standalone
pnpm ui:build starts natively and runs its post-build validators directly with Node.
Those validators do not load tsx or require TSX_DISABLE_CACHE in the invoking shell.
Raw external tsx and node --import tsx invocations outside these launchers are unchanged.
Node Vitest workers also preload scripts/tsx.mjs once per worker. Vitest still
owns test module mocks, while native plugin SDK imports use Node's source module
graph with TypeScript syntax and .js-to-.ts resolution. Bun uses its native
TypeScript loader. This keeps source-host tests from relying on Jiti to evaluate
another copy of the host SDK.
Scheduler-owned project runs on macOS and Linux reuse filesystem transforms within exclusive slots, including serial runs that mix configurations. Each Vitest configuration keeps separate directories. A slot stays owned through preflight, retries, and verified child/group completion; uncertain cleanup retires it. Same-config serial runs without scheduler assignment, explicit isolated cache paths, watch runs, and Windows retain their existing cache ownership. Concurrent invocations still need separate cache roots.
Control UI builds report size budgets without enforcing them. Run
pnpm ui:check-performance after a build to enforce absolute budgets, or
pnpm ui:check-performance:base <base-commit-sha> to build and compare both
revisions with the same toolchain. See Control UI size budgets.
源测试与子进程构建¶
Non-watch runs through pnpm test or scripts/run-vitest.mjs keep Vitest tests
and runtime parents on TypeScript. Importing a declared subprocess entrypoint
compiles the fixed test entry set and its workspace dependencies into one fresh
invocation directory under .artifacts/vitest-workers/.
The declared application entries run as plain Node JavaScript without a
TypeScript loader: SQLite read-only snapshots, database verification, Tailscale
route ownership, the service relay, its POSIX and Windows anchors, the memory
plugin's KNN child, session transcript archive and reconciliation workers, and
managed GitHub credential resolution. The same generation also compiles the fake-backend TUI
fixture's four runtime roots together: the real TUI, embedded reply producer,
reply metadata reader, and outbound normalizer. Shared chunks preserve their
module and WeakMap identity. Prepared TUI fixtures are compiled to .mjs and run
as JavaScript without a TypeScript loader. Direct source fixtures
remain .mts: Node launches them with --import tsx, while Bun handles their
syntax natively. The session-identity PTY tests load real provider policies, so
their runtime prerequisite prepares the built host SDK before Vitest workers start.
Existing package build entry paths and Vitest source parents stay unchanged. The
CLI fork-recovery regression also compiles the real CLI entry and its concurrent
rebind's session accessor and binding helper together. Both processes use the same
runtime graph while retaining the durable-write race and process-exit assertions.
Doctor process output tests with bundled plugins disabled reuse that compiled CLI
inside one lazily created package fixture per test run, keeping real UI checks on
fixture-owned assets and each scenario’s state separate. Standalone and watch runs
use live source inside the same fixture.
Broadcast output coverage prepares its message helper and exit finalizer together, preserving command substitution and joining its process tree before fixture cleanup.
Isolated Doctor config scripts also share the prepared config-flow, health-writer, and install-index modules. Each case still starts a fresh process with separate state; standalone and watch runs resolve the original TypeScript entrypoints.
model-catalog、Codex catalog-page 和 session model-context 工作进程也使用此编译生成集。 Model-catalog 工作进程仍属于其已准备好的模型生成集;上下文读取保留其串行工作进程池。插件 source/built 选择仍独立于工作进程编译。 其他工作线程条目和任意 source CLI 夹具仍位于此声明集之外。
agent database module-identity 测试共享编译后的 host 和 SQLite SDK 条目,同时强制对 SDK 进行单独的插件转换。其 standalone 和 watch 运行保留来自当前 source 的一次性构建,因为此回归测试专门检查打包图。两种模式使用相同的断言和子进程截止时间。
session-title 和 child-link 保留测试在此同一生成集中声明其 title-reader、session-utils 和 listing 根。每个新的堆测量子进程运行它们的 JavaScript,而不会将执行截止时间花在 TypeScript 导入上。
Native Bash output-lifecycle 夹具也在此生成集中准备真实的 tool 和 executor 根。每个场景仍使用新进程以及真实的 shell、pipe 和 spill 文件;其不变的子进程截止时间覆盖已准备好的 JavaScript 启动和输出处理,而不是重复的 TypeScript 编译。
Automatic-triage 进程夹具共享此生成集,用于准入、故障处理、执行、进程身份和重生检查。编译在就绪截止时间开始之前完成,因此子进程加载已准备好的 JavaScript。分离的 helper 使用与已安装包相同的密封租约运行时。
已知的核心 database-worker 消费者在测试进程启动前准备该调用的编译生成集,包括当某个用例动态导入其工作进程声明时。选择使用现有的 database-worker 清单,并遵循 CLI 过滤器、排除项和 include 文件。其他测试在项目和分片之间保留惰性准备。Config 导入、listing 测试、watch 运行、自定义选择以及不导入这些声明的微型测试不会急切编译工作进程。导入声明的分片通过其现有 Node IPC 通道请求外层 runner 的单次构建;急切消费者复用已完成的生成集。静态导入在模块收集期间获取它,早于夹具钩子和就绪截止时间。每个需要声明的有限调用都会为此固定条目集付出代价;准备计时与子进程执行分开报告。runner 启动一个短生命周期的原生 Node 或 Bun 编译器子进程,并在向借用者返回已验证的 manifest 之前等待其结束。编译器模块图位于该子进程中,而不是长生命周期的 runner 或 Vitest 工作进程。任何分片都不能选择不同的构建图,也不能采用另一个调用的输出。外层 runner 保留该生成集,直到子进程关闭和进程组清理完成,然后在报告成功之前对其进行验证。验证使用有界的异步 I/O 读取每个已记录的输入和输出,使 runner 在大型关闭扫描期间保持响应。调用所有者在回复之前验证每个借用者的准备,并在所有借用者关闭后再次验证;Vitest 不会在每个分片内部或其并发池关闭期间重复这些扫描。standalone Vitest 和 watch 运行保留 source 执行:编译、验证和产物删除需要仓库 runner 的所有权。丢失的所有者或失败的构建会使运行失败。处置会取消待处理的编译,并等待编译、每个借用者以及未完成的准备请求结束,然后再异步删除目录。信号处理器在删除过程中保持激活,即使大型生成集需要较长时间删除。借用者完成不会等待编译,因此子进程提前退出可能到达该取消路径。不确定的编译器或借用者等待会保留生成集并使运行失败。异常终止也可能留下未使用的目录;后续运行永远不会采用它。
每次准备都编译当前 source;checkout 的 dist/ 既不是输入也不是回退。构建错误、缺失产物以及对已记录构建输入的更改会使运行失败。编译在原生子进程夹具施加资源限制之前包含它们。第三方依赖保持外部,除了始终打包的 OpenClaw 包。fs-safe 保持外部,以便其原生 loader 从 fs-safe 自身的依赖范围解析可选平台包,包括嵌套 pnpm 安装。编译后的工作进程使用同一个已安装包;它们不复制原生二进制文件。Native 模式在 macOS、Linux 和 Windows 上默认为 auto。No-clobber Root 移动需要原生支持;Windows 安全凭据读取需要匹配的 helper 用于描述符绑定的 ACL 检查。显式 off/auto/require 设置和编程配置保留其优先级。密封的可移植工作进程包仅使用受保护的 JavaScript,并显式禁用原生加载。
watch 模式有意保留现有的 live-source 路径,包括用于 Node 子进程的 tsx 和用于 Bun 的原生 TypeScript 处理。它不创建已准备好的生成集,因此新的子进程启动会读取当前 source,而不是复用编译快照。现有的 Vitest watch 依赖跟踪仍决定测试何时重新运行。
测试 wrapper 运行以简短的 [test] passed|failed|skipped ... in ... 摘要结束;Vitest 自身的持续时间行仍作为每个分片的详细信息。
失败的调用在子进程、清理和报告发布稳定后,以一个 [test] FAILED (exit N) 行结束。直接 run-vitest.mts 调用改用 [vitest]。嵌套 runner 保留其诊断信息和退出状态;顶层 CLI 拥有最终失败行。成功运行不输出失败尾注。
| 命令 | 它的作用 |
| 命令 | 作用 |
|---|---|
| --- | --- |
pnpm test |
显式文件/目录目标会路由到作用域化的 Vitest 泳道。未指定目标的运行是全量套件验证:固定分片组会展开为叶配置以进行本地并行执行,并在开始前打印预期的分片扇出。扩展组始终展开为按扩展划分的分片配置,而不是一个巨大的根项目进程。 |
pnpm test:changed |
低成本的智能变更测试运行:来自直接测试编辑、同级 *.test.ts 文件、显式源映射以及本地导入图的精确目标。除非能映射到精确测试,否则跳过宽泛/配置/包变更。 |
OPENCLAW_TEST_CHANGED_BROAD=1 pnpm test:changed |
显式宽泛变更测试运行;当测试框架/配置/包编辑应回退到 Vitest 更宽泛的变更测试行为时使用。 |
pnpm test:force |
释放已配置的 OpenClaw 网关端口(默认 18789),然后使用隔离的网关端口运行完整套件,使服务器测试不会与正在运行的实例冲突。 |
pnpm test:coverage |
为默认单元测试泳道(vitest.unit.config.ts)输出信息性 V8 覆盖率报告;不强制任何覆盖率阈值。 |
pnpm test:coverage:changed |
仅针对自 origin/main 以来变更的文件进行单元测试覆盖率。 |
pnpm changed:lanes |
显示相对于 origin/main 的差异所触发的架构泳道。 |
pnpm check:changed |
运行本地变更格式化/类型检查/lint/守卫计划,包括针对所选路径的定向 Vitest 所有者测试。使用 pnpm test:changed 或 pnpm test <target> 进行与所触及契约匹配的额外测试验证。 |
当变更影响网关事件目录或常量、被扫描的移动源码、覆盖率声明,或该守卫、其执行辅助函数及其路由时,pnpm check:changed 还会运行移动协议事件覆盖率守卫。全泳道检查也包含它。每个网关事件都必须具有一个处理器,或针对每个移动客户端的明确批准的非消费声明。若仅运行此守卫,请使用 pnpm check:protocol-coverage。
对于原生应用变更,pnpm check:changed 使用平台范围来选择 lint:Android 选择 pnpm android:lint(Gradle ktlint 检查),而 Apple 应用变更保留 Swift lint。仅 Android 的变更不会选择 Swift lint 或其缺少工具提示。Android 框架/资源 lint 和运行时测试仍是独立检查;Kotlin lint 不会替代它们。
执行 GNU stat 和 readlink 的远程文件系统 fixture 仅在 Linux 上本地运行。共享的前导 @ 文件工具场景也在每个平台上针对可移植的仅远程桥接运行。原生 Python 辅助函数覆盖率仍单独保留,包括 macOS;这些 fixture 门控不会限制 SSH 后端的 Gateway 主机。
发现真实捆绑 provider 运行时的测试会在 scripts/lib/vitest-build-prerequisites.mts 中声明该前置条件,包括 Telegram sticker-model 选择。本地运行器和 CI 在接纳 worker 之前准备这些产物。
本地 PR 门禁¶
对于本地 PR 落地/门禁检查,运行:
pnpm check:changedpnpm checkpnpm check:test-typespnpm buildpnpm testpnpm check:docs
pnpm check --base <ref> 将 line-cap、max-lines 抑制和断言安全棘轮固定到 HEAD 与该 ref 的合并基。原生 PR 门禁会传递其候选者从捕获的 main 快照派生的 fork,因此,即使共享的 origin/main ref 已过期,继承的 main 变更也保留其豁免额度。其他检查阶段仍正常运行。
如果 pnpm test 在负载较高的主机上出现偶发失败,请先重新运行一次,再将其视为回归问题,然后使用 pnpm test <path/to/test> 进行隔离。对于内存受限的主机:
OPENCLAW_VITEST_MAX_WORKERS=1 pnpm testOPENCLAW_VITEST_FS_MODULE_CACHE_PATH=/tmp/openclaw-vitest-cache pnpm test:changed
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw