OpenWork 测试体系:不阻塞无关工作的 PR 门槛设计与 CI 演进
2026/9/13 11:39:49 网站建设 项目流程

OpenWork 测试体系:不阻塞无关工作的 PR 门槛设计与 CI 演进

【免费下载链接】openworkThe open-source alternative to Claude Cowork (powered by opencode)项目地址: https://gitcode.com/GitHub_Trending/ope/openwork

导读:本文以 OpenWork(基于 opencode 的开源 Claude Cowork 替代品)的官方测试策略文档 docs/testing.md 为核心,深入剖析其"两层测试"体系——合并门槛只跑pnpm test(别名test:core)精选核心套件,更广泛的回归交给每日定时 CI;同时结合仓库内 .github/workflows/ci-tests.yml、各包package.json与 evals/vitest.config.ts 等源码证据,还原变更分类(lane)机制、失败案例审计与"失败必须诊断修复、不重试"的工程纪律。读完你将掌握:如何在本仓库正确搭建测试环境、核心套件与扩展套件分别覆盖什么、PR 合并到底被哪些检查卡住,以及 CI 运行时间与覆盖范围之间如何取舍。


一、为什么需要"不阻塞无关工作"的测试策略

大型 monorepo 的典型困境是:一次无关紧要的改动(比如改文档、更新模型快照)触发整套全量回归,PR 排队、CI 变慢,而真正有价值的覆盖反而被淹没在噪音里。OpenWork 通过一份审计数据做出了改变:

对 2026 年 8 月 28 日至 9 月 3 日(UTC)期间 OpenWork Tests 运行记录进行审计,632 次运行中有 159 次失败(其中 17 次等待审批);在 615 个成功/失败结果中,失败率高达25.9%

这是运行次数统计(含同一分支的重复推送),不是经过归因的 flake 率,但足以说明:默认门槛过宽,让大量无关改动为脆弱测试买单。抽样失败日志显示不同的问题需要不同的修法(详见本文第五节),于是仓库把合并门槛收敛为"两条独立的 Linux job",把广覆盖挪到夜间任务,让"大部分改动只跑最小必需验证"成为默认路径。

这套策略沉淀为 docs/testing.md 这份文档,并落地在 .github/workflows/ci-tests.yml 中。


二、本地环境准备:与 CI 完全一致的运行时固定

在提交 PR 前,先运行pnpm test(别名pnpm test:core)。为了让本地结果与 CI 一致,仓库通过 .github/actions/setup-tests/action.yml 固定了全部运行时,本地应与之对齐:

运行时固定版本作用
Node.js24运行 pnpm、测试脚本
Bun1.3.14执行 app / server / den-api 的bun test
pnpm11.4.0包管理器(根 package.json 声明packageManager: "pnpm@11.4.0"
OpenCode见 constants.json(v1.18.30真实引擎,需在 PATH 上

安装两个工作区

仓库是双工作区结构,两个工作区都要安装依赖:

# 根工作区 pnpm install --frozen-lockfile # evals 工作区(独立 lockfile) pnpm --dir evals install --frozen-lockfile

CI 中的 setup-tests 会做同样的事,并额外从 OpenCode 的 GitHub Releases 下载与constants.json中版本匹配的二进制放入 PATH(Linux 用opencode-linux-x64-baseline.tar.gz,macOS 用opencode-darwin-arm64.zip等),随后用opencode --version校验。

核心套件无需任何外部依赖

这是核心套件与扩展套件的关键差异:核心套件使用本地 fixtures 和脚本化 providers,不需要云账号、provider key、Docker daemon 或正在运行的 Den 数据库。也就是说,克隆仓库、装好依赖、放好 opencode 二进制,pnpm test:core就能在纯本地确定性执行——这正是它能作为每个 PR 默认门槛的前提。


三、合并门槛:openwork-tests-required检查的两个独立 Linux job

openwork-tests-required检查保留原有名称并采用fail-closed(失败即封锁合并)策略。对于普通代码改动,它要求两条独立的 Linux job 全部通过:

Job 1:核心回归(Core regressions)

pnpm test:core驱动。从根 package.json 的test:core定义可以看到它由五个子套件串联而成:

"test:core": "pnpm --filter @openwork/app test:core && pnpm --filter openwork-server test:core && pnpm --filter @openwork-ee/den-api test && pnpm --filter @openwork/desktop test:core && pnpm --dir evals run test:core"

五个子套件的覆盖面与文档描述一一对应:

  1. 客户端(app):会话准入、流式与重连、权限状态、附件、provider 凭据、Connect 对账。见 apps/app/package.json 的test:core,包含tests/session-admission-terminal-invariant.test.tstests/session-sync-lifecycle.test.tstests/session-sync-permissions.test.tstests/attachment-file-part.test.tstests/cloud-provider-credentials.test.tstests/connect-policy-reconciler.test.ts等 18 个文件,通过bun test --isolate运行。

  2. 服务端真实路由(server):线程(headless threads)、群组(session groups)、代理(opencode proxy)、文件夹权限、上传审批、artifact I/O、云配置、引擎驱逐与热重载、token 作用域、导出安全。见 apps/server/package.json 的test:core,运行headless-threads.e2e.test.tssession-groups.e2e.test.tsopencode-proxy.e2e.test.tsauthorized-folders.e2e.test.tsinbox-upload-approval.e2e.test.tsartifact-files.e2e.test.tscloud-mcp-reconcile.e2e.test.tsruntime-config-patch-reload.e2e.test.tsengine-instance-eviction.e2e.test.tstokens.test.tsworkspace-export-safety.test.ts等 14 个文件。

  3. Den 认证(den-api):见 ee/apps/den-api/package.json 的test脚本,覆盖api-keysbearer-sessioncloud-provider-materialization-read-retryremote-session-capability-wireorganization-capabilitiesauth-organization-metadata以及 gateway-deployment 路由。

  4. 桌面端(desktop):工作区持久化、归档、链接、凭据密钥、自动化执行、进程韧性、TLS。见 apps/desktop/package.json 的test:core,包含workspace-store.test.mjsworkspace-archive.test.mjsconnect-link.test.mjssecure-vault-key.test.mjsautomation-runner.test.mjsprocess-resilience.test.mjsruntime-ca.test.mjsruntime-chain-repair.test.mjs等 12 个文件(pretest:core会先构建 headless-threads)。

  5. 真实引擎旅程(evals):三个既有真实引擎旅程额外检查线程审批记忆(thread approvals replay)、**有效权限归属(effective permissions attribution)**与PDF 模型路由。见 evals/package.json 的test:core

vitest run --config vitest.config.ts --project pr \ specs/thread-approvals-replay.test.ts \ specs/effective-permissions-attribution.test.ts \ specs/pdf-attachments-model-routing.test.ts \ specs/route-session-list.test.ts

其中prproject 定义在 evals/vitest.config.ts:includespecs/**/*.test.ts../scenarios/**/*.test.ts,排除e2elive规格(live 规格被视为挂接系统的故障信号,除非显式点名否则不纳入)。以 route-session-list.test.ts 为例,它直接 import 客户端源码(apps/app/src/react-app/shell/route-workspaces.tssidebar/utils.ts),用合成会话数据验证路由会话列表与归档分区逻辑,体现了"规格直连实现、不依赖运行环境"的写法。

CI 中核心 job(.github/workflows/ci-tests.yml 的openwork-tests-core)还会追加运行测试框架自身的检查:

node --test evals/scripts/journey-ci.test.mjs evals/scripts/flake-report.test.mjs

Job 2:打包(Packaging)

打包 job(openwork-tests-build)验证"测试源码解析与打包后的 Node 插件解析不同"这一风险点,包含四步:

# 1. outbound-access 声明校验 pnpm check:outbound-access # 2. 按打包方式构建服务端 Node 目标插件(模拟打包解析路径) pnpm --filter openwork-server build # 3. 校验打包后的 Fast 模块消费 node --test packages/types/tests/packaged-cloud-model-fast.test.mjs # 4. Electron 主进程对 IPC 契约的类型检查 pnpm --filter @openwork/desktop typecheck:electron

随后还会运行虚拟显示引导(xvfb)与安装包 smoke 测试(packaged-smoke.mjs --server-built),并上传 evidence 与耗时数据。一条测试失败不能阻止这条独立 job 报告打包回归——两条 job 相互独立,正是为了让"打包坏没坏"不被核心测试的噪音掩盖。

变更分类:snapshot-only 与 docs-only 走专门验证

openwork-tests-required不是无脑要求全绿,而是依据**变更分类(lane)**放行。ci-tests.yml 的classify-changesjob 用 GitHub Script 检查变更文件:

  • snapshot:唯一变更文件是ee/apps/gateway/src/models/base.json(模型快照),只跑validate-models-snapshot(校验快照结构:provider 有 id/name/models,模型有 modalities.input/output 与 limit);
  • docs:全部变更都在packages/docs/下,只跑validate-docs(用 Mintlify 校验站点构建、断链、锚点与图片);
  • full:其余所有情况,要求核心与打包两条 job 通过。

openwork-tests-required聚合 job 在if: always()下运行,根据 lane 断言对应 job 的成败矩阵(例如 full lane 要求CORE_RESULT=success && BUILD_RESULT=success && SNAPSHOT_RESULT=skipped && DOCS_RESULT=skipped),为分支规则集提供单一稳定的检查名:始终上报、按分类判定。同 PR 新推送会取消过时的运行(concurrency.cancel-in-progress),dev 分支推送仍跑核心与打包检查。模型快照与文档专用验证保持既有通道不变。

核心套件的选择纪律

文档明确指出:test:core脚本就是选择机制——没有第二套清单、没有选择生成器、没有覆盖率棘轮、也没有"断言清单与自身一致"的测试。要扩大覆盖,正确做法是"为某个核心失败模式扩展既有测试",而不是新增簿记类检查。只有满足以下条件才应往门槛里加测试:

  • 能捕获关键旅程中的可观测回归;
  • 在声明了前置条件下确定性运行;
  • 值得付出运行成本。

禁止向门槛加入:源码文本/布局断言、测试运行器包装器、簿记检查。核心测试一旦失败,必须被诊断并修复,而不是重试到绿或静默忽略。


四、更广覆盖:夜间全量回归与pnpm test:extended

核心门槛刻意保持窄,那么其余套件去哪了?同一个 OpenWork Tests 工作流通过openwork-tests-extendedjob 在Linux 与 macOS 双平台上运行全量套件:

  • 触发方式:每日 UTC 07:37 定时(cron37 7 * * *),或对选定分支通过Run workflow手动触发;
  • fail-fast: false:即使前面某个套件失败,后续套件照常运行并各自上报;失败仍会把该次运行标红;
  • 覆盖清单:app 全量、server 全量、den-api 全量、desktop 全量、release 脚本测试(node --test scripts/release/*.test.mjs)、测试框架检查(pnpm test:eval-runner)、PR 规格(pnpm sdk:build && pnpm evals:pr)、引擎 smoke(pnpm test:e2e)、服务端插件构建与 Electron typecheck。

本地复现这套全量回归用:

pnpm test:extended

从根 package.json 可见其定义:

pnpm --filter @openwork/app test && pnpm --filter openwork-server test && \ pnpm --filter @openwork-ee/den-api test && pnpm --filter @openwork/desktop test && \ node --test scripts/release/*.test.mjs && pnpm test:eval-runner && pnpm evals:pr && pnpm test:e2e

打包部分可以单独复现:

# 服务端真实 Node-target 插件构建 pnpm --filter openwork-server build # Electron IPC 契约类型检查 pnpm --filter @openwork/desktop typecheck:electron

权责转移与注意事项

把广泛套件移出统一门槛,意味着门槛之外的回归可能先由定向验证或夜间运行发现。因此文档强调:

  • 改动某个包内部逻辑时,应运行该包的完整测试命令(而非只跑核心子集);
  • 改动测试框架本身时,运行pnpm test:eval-runner
  • 发布前检查 macOS 夜间运行结果——macOS 不再是 PR 前置条件,发布风险由夜间任务兜底;
  • 现有的 Daytona E2E 与夜间 flake-report 工作流保持不变。

"跳过即不完整"原则

一个被跳过的旅程就是不完整的覆盖,即使 runner 以成功退出。文档明确禁止把"包含跳过"的运行描述为"完整证明"——这条原则保证了夜间全量任务不会用静默 skip 粉饰太平。


五、为什么改变:审计数据与四个典型失败案例

回到开头的审计:632 次运行、159 次失败、25.9% 失败率。抽样失败日志揭示了四种不同性质的问题,需要四种不同的修法

案例失败现象根因与处置
9 月 3 日spec-impactspec-quarantine清单断言在双 OS 失败,而同批其余 115/116 个 spec 通过特定规格已移除;把测试框架簿记移出默认门槛,防止同类壁垒在其他地方重建
8 月 28 日一个兼容性规格又派生了一个测试 runner,底层失败被包装断言遮蔽属于 runner 包装器问题,印证"不给门槛加测试运行器包装器"的禁令
PR #4442共享套件在"引擎退役时序"断言上失败(与 dev 运行同因)竞态已由 PR #4439 独立修复;核心覆盖仍包含真实引擎驱逐与重载行为,广泛测试予以保留
PR #4442 的 SDK 检查schema 生成在无数据库的情况下连接127.0.0.1:3306的 MySQL这是 SDK 改动自身的setup 依赖,应在 SDK 变更中修复,而不是以此为由压制 schema-drift 校验

文档特别说明:本次 CI 清理不修复该分支——即它只负责调整门槛结构,不替失败的改动补丁。另外强调:本次改动不增加"测试的测试",也不新增任何测试文件;运行时间收益必须在上线后实测,把四个广覆盖 PR job 收敛为两个聚焦 job,本身并不构成"特定墙钟时间必然缩短"的证据。这种克制表述本身就是工程严谨性的体现。


六、从源码看这套体系的落地细节

1. 运行时固定是可审计的

setup-tests/action.yml 不仅装运行时,还用node -econstants.json读出opencodeVersion,动态拼接下载 URL——引擎版本只在一个地方声明,CI 与文档引用同一来源。缓存键同样引用constants.jsonpnpm-lock.yaml与 sidecar 准备脚本,确保引擎变化时缓存自动失效。

2. 核心 job 的 15 分钟超时是"聚焦"的硬约束

openwork-tests-coreopenwork-tests-build都设置了timeout-minutes: 15,扩展 job 为 30 分钟。这从 CI 层面强制核心套件保持"窄而快",任何把门槛拖慢的回归都会直接撞上超时。

3. 规格层与实现层的边界

evals/vitest.config.ts 将prproject 的 alias 指向../apps/app/src/,使规格可以直接引用客户端源码(如 route-session-list.test.ts 引入route-workspaces.tslistRouteSessionsreadRouteSessionsWithRetry),同时把*.e2e.test.ts*.live.test.ts排除在 PR 门槛之外——e2e 需要真实堆栈(runner/prepare-stack.ts全局 setup、testTimeout: 600_000),live 规格则是挂接生产系统时才用的信号。这种"规格(spec)/端到端(e2e)/在线(live)"三级划分正是门槛窄、覆盖广的落地形态。

4. "失败必须修复"的 CI 兑现

ci-tests.yml 中没有任何continue-on-error或重试包装:扩展 job 用if: (success() || failure())让后续套件继续跑、各自上报,但结果仍计入红标;openwork-tests-required对 lane 矩阵做严格断言,任何非预期组合直接exit 1。这从流水线层面杜绝了"重试到绿"的侥幸文化。


七、实践总结:如何正确使用这套测试体系

  1. 提交前pnpm test(即test:core),确保本地运行时与 setup-tests 固定版本一致(Node 24、Bun 1.3.14、pnpm 11.4.0、opencode 版本见 constants.json),并先执行两个工作区的pnpm install --frozen-lockfile
  2. 核心失败时:诊断并修复,不要重试、不要跳过、不要静默忽略。
  3. 改动包内部时:运行该包的完整测试命令(如pnpm --filter openwork-server test)。
  4. 改动测试框架时:运行pnpm test:eval-runner
  5. 发布前:检查 macOS 夜间运行结果;如需本地全量复现,运行pnpm test:extended
  6. 扩覆盖时:为既有核心失败模式扩展测试,而不是新增簿记/包装器/文本断言,更不要建立第二套测试清单。

这套体系的本质是:用变更分类把验证成本精确匹配到改动风险上,用两条独立 Linux job 守住"核心回归 + 打包契约"两个最关键的失败面,用夜间双平台全量回归承接广覆盖,再用"失败必须修复"的纪律保住每个检查信号的可信度——最终让 CI 既快、又准、还不撒谎。


参考路径速查

  • 测试策略文档:docs/testing.md
  • CI 工作流(lane 分类、核心/打包/扩展 job、required 聚合):.github/workflows/ci-tests.yml
  • 运行时安装与固定:.github/actions/setup-tests/action.yml
  • 引擎版本声明:constants.json
  • 根脚本(test/test:core/test:extended/test:eval-runner):package.json
  • 各子套件定义:apps/app/package.json、apps/server/package.json、apps/desktop/package.json、ee/apps/den-api/package.json
  • 真实引擎旅程与规格配置:evals/package.json、evals/vitest.config.ts
  • 核心规格示例:evals/specs/route-session-list.test.ts

【免费下载链接】openworkThe open-source alternative to Claude Cowork (powered by opencode)项目地址: https://gitcode.com/GitHub_Trending/ope/openwork

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询