--- summary: "共享构建锁、隔离测试状态和 home,以及 JSON 报告合并" title: "测试运行器内部结构" read_when: - "运行泄漏状态、保留锁定或丢失报告" - "您需要多项目运行的机器可读结果" --- ## 共享测试状态和进程辅助工具 TypeScript 工具使用本机 `typescript` 包。 `pnpm tsgo` 通道直接解析其可执行文件;语法和语义工具使用其不稳定的 API 和明确的进程生命周期。包根导出版本元数据,而 AST、文件系统和编译器 API 位于 `typescript/unstable/*` 下。打包声明构建运行相同的本机编译器,并进行语义检查,并在捆绑之前验证其完整源代码和包清单收据。 tsdown 包装器默认声明一次构建为一种配置,将本机编译器进程与现有的 Node 堆预算结合起来。仅运行时构建保持其并行性,并且显式 `--concurrency` 值保留 tsdown 自己的行为。 Code Mode直接执行JavaScript,不使用该编译器;其 TypeScript-style 工具声明是面向模型的文档。 `build-all`、独立 tsdown 构建、tsgo、SDK 声明准备、包边界检查和依赖 lint 使用 `.artifacts/dist-artifacts.lock` 的签出本地所有权。所有权涵盖清理、生成、缓存恢复以及使用这些输出的检查;独立结帐保持独立。竞争命令打印等待消息并等待实时所有者,而没有获取截止日期。编译器和构建执行超时不变。独立的 tsgo 运行序列化,包括仅源检查;核心测试分片运行程序在一个所有者内部保留其显式并发性。当这些命令运行时,请勿手动删除 `dist`。突然的所有者或嵌套包装器退出,或未经验证的子清理,会保留锁。丢失或无法验证的所有者 PID,或者记录的子清理失败,会立即导致获取失败而不会回收任何内容。 PID的死亡并不能证明超然的后代就此停止。在手动删除废弃的锁定目录之前,请检查其 `owner.json` 并验证所有关联的构建、编译器和 lint 进程(包括分离的后代)已停止;然后重试该命令。 运行时耗时的测试通过显式构建所有者来准备签出工件,而不是通过使用 `--version` 启动 CLI 来准备。准备工作重用源运行程序新鲜度检查和签出工件所有权,无需更新程序服务或数据库维护托管。当前的工件不需要可写的签出或服务检查。 `build-all` 指纹制作投入于现有邮票中。测试准备可以在源刷新或仅测试更正之后重用一致的运行时,包括新的私有传输提交。普通 CLI 和不可变部署 HEAD 检查仍然严格; UI E2E 准备也保留其当前的头部检查。更改的生产输入、构建配置、依赖项、编译器标识或所需的缺失输出仍然需要准备。跳过静态资源的部分后期构建无法满足需要这些资源的读者。 E2E 检查之前的完整构建应使用 `OPENCLAW_BUILD_PRIVATE_QA=1 pnpm build`;标记到构建命令的范围,以便其工件满足最强的测试先决条件。 在编写之前,自动准备需要经过验证的工件分离或观察到的离线管理网关。在 Linux 上,它使用现有的本机管理器绑定来读取加载的命令位置,而不读取服务环境文件。本机 `GetUnit` 未加载结果表示未加载运行时,而不是缺少其保存的定义。卸载的保存单元不会使原本可写的源签出变得不可变;这是准入时检查,而不是服务启动排除或沙箱。物理共享的 `dist` 路径、不可读的工件路径、不完整的发现和未知的服务状态永远不会授予重建权限。不可变的部署和已知的实时重叠仍然被拒绝。普通单独的工作树不需要新的 CLI 标志或配置;当无法建立本机分离时,使用 [ 现有的隔离转轮 ](/help/testing/suites#network-isolated-local-e2e)。此检查不是沙箱。显式 `pnpm build`、自动源 CLI 重建和实际更新发布保留其现有准入策略。 在子加入和工件所有权解决后,Lint 在 stderr 上报告其最终失败,包括在清理不确定时保留所有权。独立的Oxlint及其分片CLI以`[oxlint] FAILED (exit N)`结尾; `pnpm lint` 拥有整个管道,并以一个 `[lint] FAILED (exit N)` 结束。分片进度将 `passed` 与 `failed (exit N)` 区分开来,并且 stdout 仍然可用于机器可读的工具输出。成功的运行没有失败预告片。在子执行期间转发的信号和分片超时会使命令失败;整个主机丢失或`SIGKILL`的报告过程可以防止最终线路。 本地插件 lint 使用 `packages/plugin-sdk/dist` 中的本机 SDK 声明。专用包边界编译器还使用 `.artifacts/extension-package-boundary/plugins` 中的七个插件 API 树。每个声明和编译所有者都会验证其使用的源内容、继承的配置、选定的编译器和完整的输出清单。不相关的现有源或测试编辑保留缓存命中。解析拓扑变化保守地无效,包括声明的根之外的新候选模块。过时的声明在清除其私有输入收据后会获得完整的本机发射;成功发出的清单会驱动过时的声明修剪。输出丢失或被篡改会使所有者失效。内容记录位于 `.artifacts/extension-package-boundary` 下,在打包构建清理之外。私有 `.inputs.json` 收据包含规范化的结账相关源和清单路径,而不是本机 `.tsbuildinfo` 状态。热运行会验证记录而不发出声明。 打包声明构建和包边界记录仅接受签出拥有的输入真实路径,包括编译器库、继承的配置、依赖项链接和包清单。这些路径共享 `compileNativeProject`,它使用固定本机编译器的异步 API 进行检查和内存中声明发射。它的文件系统回调使结账之外的候选者在本机分辨率可以读取它们之前显得丢失。直接捕获源和包清单读取;准入不依赖于解析解析痕迹。编译器版本固定在 `package.json` 中,因为此 API 不稳定。配置和请求的语义检查在发射之前运行。声明错误来自内存中的发出结果,避免了仅用于诊断的单独声明转换。任何错误都会阻止工件发布。 即使祖先目录包含 `node_modules`,嵌套物理工作树也受其自己的 `pnpm install --frozen-lockfile` 支持。祖先依赖项无法满足缺少的本地输入或更改发出的声明。当本地 pnpm 链接的目标停留在结帐内时,仍受支持。解析外部的本地链接仍然会失败并显示 `Declaration input escapes checkout`,而不会发布成功记录或修剪过时的声明。外部候选者仍然无法访问,即使它是返回结账的符号链接。温暖记录使用相同的输入检查。不要过滤编译器收据或移植声明来绕过检查。 声明的结帐联结和平台路径别名映射到同一本机根以进行验证和实际快照读取。本地声明准备还将编译器的 `PWD` 与其工作目录对齐,因此 shell 别名不会更改发出的清单路径。来自子目录的调用仍然使用包含签出作为所有权边界。其他`pnpm tsgo`通道继续使用原生CLI;它的包装器不会创建或重用共享的外部安装。 Canvas A2UI 资产构建使用共享构建工件缓存,包括当连续更新候选者使用不同签出时的 `BUILD_ALL_CACHE_ROOT`。缓存会一起验证并恢复两个方言包。 Canvas 源、其包清单、锁定文件、工作区依赖项设置、补丁、构建脚本、Node 版本和构建环境使重用无效;不相关的根包脚本则不会。 `OPENCLAW_BUILD_CACHE=0` 强制编译。显式 `OPENCLAW_A2UI_BUNDLE_OUT` 目标绕过共享缓存。稀疏结帐保留其现有的完整预构建捆绑行为。 Cold A2UI 默认将本机 Rayon 和 Rolldown 工作池构建为四个线程,保留显式的 `RAYON_NUM_THREADS` 和 `ROLLDOWN_WORKER_THREADS` 覆盖。 打包的 SDK 声明属于由完整版本、包版本和 `ciArtifacts` 版本共享的一个分阶段所有者。它在未命中时序列化两个标准 tsdown SDK 组,并缓存其完整的分阶段生成。每个成功的编译器都通过私人分阶段收据提供其源代码和包清单成员资格;缺少收据或在编译过程中更改输入会导致无法发布。共享输入快照策略可验证消耗的字节、继承的配置、生成器和清单输入以及解析拓扑,而无需在命中时启动编译器。缓存命中恢复到新的暂存中,并在发布之前通过相同的条目和相关声明关闭检查。所有 tsdown 声明构建(八个 SDK/unified 组、工作区包和 AI 包)使用相同的有界编译器作为本地声明准备和包边界检查。成功的构建需要签出拥有的编译器库和声明依赖项;不支持共享外部安装。编译器收据保留完整的源代码和包清单成员身份,包括 JSON 输入。默认类型根保留在签出范围内,显式类型根必须是本地的,并且有效的根 `package.json` 限制源包范围查找。 共享快照策略仍然验证消耗的字节和解析拓扑。编译期间源和命名空间的更改会阻止接受。新的本地候选模块使缓存记录无效,祖先安装的出现和删除也是如此。外部检查总是会发现丢失的文件,因此以后对祖先包内容的更改无法进入编译器的文件系统视图。每个发出的声明必须在成功的编译器成员资格中拥有一个源映射所有者。捆绑器在其原始源路径下使用这些声明;仅当其子级稳定后,私有编译器阶段才会被删除。编译器配置、本机可执行文件和 API 文件、输入字节和解析拓扑参与缓存失效。运行时模块分辨率不变。 本地准备永远不会覆盖打包的声明或写入工作区转发桥。 插件 SDK 声明准备和 `scripts/run-tsgo.mjs` 要求子工作在报告成功之前完成。在 POSIX 上,每个进程组都会验证自己的托管进程组:剩余的子进程将被终止,并且命令会失败,而不是允许工件标记或下游检查继续进行。 POSIX 进程组不会检测故意离开该组的后代。 在 Windows 上,托管命令和网关测试实例使用保留的内核作业。仅当请求 Windows 命令时才加载平台代码; planner 导入独立于已安装的应用程序包。加载在生成之前完成,因此侦听器注册与子级创建保持同步。工具使用 Node 内置函数解析其工作线程 URL,并重用核心中的本机作业绑定。启动器在启动命令之前加入该作业。 Cleanup等待空Job、leader退出、输出关闭;仅领导者退出并不能证明后代完成。失败的终止会报告观察到的幸存 PID,并在作业仍未解决时保留资源声明。正常的领导者退出也会终止剩余的工作成员。最终确定在关闭作业句柄之前记录其结果,包括失败时;单独关闭句柄不会验证终止或释放资源声明。拥有自己的 IPC 频道的现有调用者保留直接启动合同;即使领导者及其管道已关闭,没有所属作业的失败的任务终止仍不确定。 `run-vitest`(包括项目分片)、插件批次、`test-live`(包括实时分片)、`run-vitest-profile` 和 TUI PTY 观察程序通过 `TMPDIR`、`TMP` 为每个 Vitest 调用提供一个拥有的临时命名空间,和 `TEMP`。在 Vitest 启动之前,隔离调用还会接收该命名空间内的本机 `HOME` 和 `USERPROFILE`。这可以保护工作线程使用的主回退、命名内置导入和导入时捕获;仅更改工作线程的 JavaScript `process.env` 不会更改本机线程主查找。每个工人和每个测试固定装置的家仍然是分开的。安装的 Corepack 和 Playwright 浏览器缓存保留其调用者选择的位置。 网关端口声明保留在所有封闭的 Vitest 命名空间之外的公共临时目录中,通过其显式资源所有者找到。因此,并行调用共享端口所有权,而固定装置将其保留的套接字交给子进程;删除一个调用的文件无法删除另一装置的端口声明。 在共享池端口上绑定进程内或生成的网关的固定装置将保留该端口的选择声明,直到网关关闭。网关在启动时会重试繁忙的端口,因此无人认领的设备可以在切换期间占用另一个设备的端口。 `getDeterministicFreePortBlock` 是支票,不是租赁;进程内网关 E2E 夹具使用 `acquireGatewayE2ePortBlock` 和 `startClaimedGateway`。 实时感知设置仍会加载原始配置文件并在请求时暂存实时状态。有界调用工件承载着该设置的原始位置;它不授予实时访问权限,并且密封设置从不咨询它。已知的密封选择会忽略环境现场和真实家庭标志。已知的完全实时感知的选择保留了明确的 `OPENCLAW_LIVE_USE_REAL_HOME` 行为。如果其选择混合了家庭策略或无法分类(包括自定义配置和不明确的项目选择器),则在配置加载之前会拒绝显式的真实家庭实时调用。使用 `node scripts/run-vitest.mjs ` 在没有 `LIVE`、`OPENCLAW_LIVE_TEST`、`OPENCLAW_LIVE_GATEWAY` 和 `OPENCLAW_LIVE_USE_REAL_HOME` 的情况下运行密封测试,然后使用 `node scripts/test-live.mts -- ` 单独运行预期的实时选择。启动器不会拆分运行或更改监视、过滤器或报告语义。 命名空间包含隔离的 home、它们的 JIT 缓存、SDK/shared-home 分配根和后备 SQLite 状态;它的生命周期跨越共享工作文件和模块重置。在 POSIX 分离启动中,父进程仅在子进程组停止、输出管道关闭并且嵌套资源所有者释放其待处理声明(包括通过和失败运行、子进程崩溃、捕获 `SIGINT`/`SIGTERM` 信号以及支持的看门狗终止)后删除该命名空间。命名空间之外的显式状态、配置文件输出和镜像工件保持不变。失败或未经验证的组加入或未解决的嵌套声明保留命名空间并报告手动恢复的确切路径。嵌套命名空间、设备生命周期和托管命令在承认工作之前注册临时文件系统所有权。发布需要积极的完成证据;捕获的清理失败、模块重置、工作程序退出或中间运行程序崩溃无法释放待处理的声明或其祖先。托管命令保留其现有的输出消耗合同; Windows Job命令也加入内核Job完成。失败的最终确定永远不会释放所有权。在手动删除报告的确切目录之前,停止所有剩余的编写器。 Windows 和非分离启动分配相同的隔离本机主目录,但在子退出和管道关闭后保留其命名空间并包含带有诊断的声明,因为无法验证后代完成。原始外部调用不会获得此边界。强迫父母或主管死亡(例如`SIGKILL`)可以阻止清理;故意逃离所属集团的未登记后代仍不属于本合同范围。包装器不会清除旧目录或根据名称、年龄或 PID 推断所有权。在每个承认的组加入后,CI 分片运行程序还会删除其默认的包含文件和转换缓存临时目录。调用者提供的暂存和持久缓存根仍归调用者所有。未经验证的后代完成会保留分片暂存目录并报告其确切路径。这是家庭隔离,而不是文件系统沙箱:显式绝对路径、`os.userInfo()` 帐户查找、具有剥离或替换家庭变量的子项以及有意的真实家庭实时执行仍处于其保护之外。 Codex 应用程序服务器固定装置等待代理和共享状态 SQLite 案例之间的引流。他们的文件拆卸耗尽了共享磁盘预算扫描工作人员的资源,在文件期间保留了重用性,并在隔离分叉关闭之前将其释放。 - `src/test-utils/openclaw-test-state.ts`:当测试需要隔离的 `HOME`、`OPENCLAW_STATE_DIR`、`OPENCLAW_CONFIG_PATH`、配置 fixture、工作区、代理目录或身份验证配置文件存储时,可在 Vitest 中使用。 - `pnpm test:env-mutations:report`:tests/harnesses 的非阻塞报告,直接改变 `HOME`、`OPENCLAW_STATE_DIR`、`OPENCLAW_CONFIG_PATH`、`OPENCLAW_WORKSPACE_DIR` 或相关环境密钥。使用它来查找共享测试状态帮助程序的迁移候选者。 - `test/helpers/openclaw-test-instance.ts`:用于需要运行中的网关、CLI 环境、日志捕获以及统一清理的进程级 E2E 测试。 - 源 `scripts/lib/docker-e2e-image.sh` 的 Docker/Bash E2E 通道可以将 `docker_e2e_test_state_shell_b64