alchemy 跨版本升级回归测试:cross-version-test 剖析与实战指南
2026/9/14 16:00:54 网站建设 项目流程

alchemy 跨版本升级回归测试:cross-version-test 剖析与实战指南

【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code

导读

本篇技术指南围绕 alchemy-effect 仓库中 test/cross-version-test 这一专项测试夹具展开,介绍它如何通过"原地升级同一个 Cloudflare Worker 应用"的方式,系统性地捕获 alchemy 从 v2.0.0-beta.39 到当前主干分支的版本间升级回归。读完本文,你将理解 alchemy 账户级 Cloudflare state store 的版本戳(version-stamp)机制、两种升级路径(逐级升级与直接跳级)的编排逻辑,掌握该测试的运行命令、全部 CLI 参数、已知坏路径的根因,以及如何为它新增一个测试版本。

背景:为什么要做跨版本升级测试

alchemy 的 Cloudflare 集成依赖一个账户级、带版本戳的单例——Cloudflare state store(由alchemy cloudflare bootstrap负责在版本之间迁移)。由于它是账户级共享资源,任何栈在部署时都会与它交互,而 alchemy 本身处于 2.0.0 beta 的快速迭代期(state store 从 v4 一路演进到 v7),旧版本客户端读新格式、新版本客户端写旧格式,都可能产生线上不兼容

cross-version-test 的核心思路非常朴素但有效:部署一个"最小可验证"应用——一个 state store + 一个 Worker,然后用一系列 alchemy 版本原地升级同一个应用(固定 Stack 名、固定 Worker 物理名、同一账户),每次升级后都去验证线上 Worker 是否真的在服务新版本,从而把版本间升级路径上的回归暴露出来。

测试编排:run.ts 的两大场景

编排入口是 run.ts,它串行(back to back,绝不并行)执行两类测试,因为它们共享同一个账户级 state store 和同一个固定 Worker 名,并行必然互相踩踏:

场景一:upgrade —— 逐级 1-by-1 升级

先部署最老的版本,然后在同一个应用上按顺序逐级升级:v4 → v5 → v6 → v7 → current,让 state store 一个版本一个版本地向上走。这模拟了生产环境中最常见的渐进式升级节奏。

场景二:jump —— 直接跳到最新

对每个旧版本,先部署它,然后跳过中间版本直接升级到最新(当前分支),例如 state store 从 v4 一步跳到 v7。这模拟了"多年不升级、一次性大跨越"的场景,专门考验大跳级时的读写兼容性。

每次都验证:marker 机制

两个场景在每一次部署之后都会断言:线上 Worker 返回的marker必须等于刚部署版本的 marker,以此证明运行中的代码确实被原地替换了,而不是旧版本还在服务。每个阶段的 Worker 都会烘焙一个专属 marker,例如 01-beta.39/src/worker.ts 中的const MARKER = "01-beta.39",以及 05-current/src/worker.ts 中的const MARKER = "05-current"。Worker 的响应体同时携带alchemy版本号与stateStoreVersion,方便事后核对。

每个场景独占全新 state store

每个"单元"(整个 upgrade 链算一个单元,每个 jump 各自是一个单元)都拥有一个全新的 state store:runner 在单元前后都会拆除 store(Worker + secrets)。也就是说 upgrade 链在自己的 store 上跑完并销毁,然后每个 jump 再部署全新的 store。若想加快速度、降低隔离性,可以传--reuse-store跳过拆除、跨单元共享一个 store。

目录布局:五个阶段的独立应用

cross-version-test/ run.ts # 编排器 —— 按顺序执行各阶段 test/ 01-beta.39/ # alchemy@2.0.0-beta.39 (npm 上最后一个带 state store v4 的发布) 02-beta.44/ # alchemy@2.0.0-beta.44 (npm 上最后一个带 state store v5 的发布) 03-beta.45/ # alchemy@2.0.0-beta.45 (npm 上最后一个带 state store v6 的发布) 04-beta.59/ # alchemy@2.0.0-beta.59 (npm 上最新 v2,state store v7) 05-current/ # 当前分支(workspace 源码,state store v7)

每个阶段目录都是一个独立的 alchemy 应用,由三部分构成:

  • alchemy.run.ts——所有阶段完全相同,见 05-current/alchemy.run.ts:固定使用Alchemy.Stack("CrossVersionApp", ...)、同一个 Cloudflare state store、同一个 Worker 逻辑 ID。注释中明确说明:"共享 Stack 名 + state store + Worker 逻辑 ID(外加 stage 与账户),正是让后续每次部署变成对同一应用的原位升级而非新建应用的关键"。
  • src/worker.ts—— 仅marker不同;旧版本(如 beta.39)的mainimport.meta.filename,而 HEAD 分支用import.meta.url(见 worker.ts 注释)。Worker 固定物理名cross-version-test-worker,保证每次都命中同一个 Cloudflare Worker。
  • test/integ.test.ts—— 一个示例风格的集成测试(详见下文)。

npm 阶段与 workspace 阶段

  • npm 阶段(01020304:在package.json精确锁定alchemy版本,并同时锁定匹配的effect版本,通过bun install获得各自隔离的node_moduleseffect的锁定至关重要:alchemy 浮动在effect4.0 beta 线上,旧 alchemy 配上过新的effect会直接崩溃。例如 01-beta.39/package.json 锁定"effect": "4.0.0-beta.66",03-beta.45/package.json 锁定"effect": "4.0.0-beta.74"。锁定值取该 alchemy 版本的 peer-dependency 下限:beta.39 & beta.44 →effect@4.0.0-beta.66;beta.45 →beta.74;beta.59 →beta.84。文档特别记录了反面教材:beta.45 搭配effect≥ beta.84 会因SchemaAST变更而崩溃。
  • workspace 阶段(05-current:package.json 中刻意不声明任何依赖,通过 monorepo 根目录的node_modules解析alchemy/effect,从而运行当前分支源码packages/alchemy)。编排器在此跳过bun install,直接调用 workspace 的 alchemy CLI。

所有阶段共享同一身份——Stack("CrossVersionApp")、固定 Workername、同一账户——这正是"每次部署都是原地升级"的原因。另外,TEST 2 使用<stage>-jump作为阶段名,以便与 TEST 1 的 state 相互隔离。

运行方式与全部参数

在仓库根目录(.repos/alchemy-effect)执行:

# 从仓库根目录运行 —— 依次执行两个测试 bun run test/cross-version-test/run.ts --profile <cloudflare-profile> # 或 ALCHEMY_PROFILE=<cloudflare-profile> bun run test/cross-version-test/run.ts # 只运行其中一个测试 bun run test/cross-version-test/run.ts --profile <p> --test upgrade bun run test/cross-version-test/run.ts --profile <p> --test jump

需要说明的是,README 中记录的是--test参数形式;而当前 run.ts 源码 实际实现的是--group <g>(取值sequentialjump),两者对应同一能力——只跑某一类升级路径,实际使用以源码为准。

Flags 一览

Flag默认值含义
--profile <p>$ALCHEMY_PROFILECloudflare 认证 profile(~/.alchemy/profiles.json)。必填(无默认值,防止误操作错误账户)。
--group <g>全部只运行某一组的边:sequentialjump(README 中记作--test <names>upgradejump)。
--stage <s>xver应用的 alchemy 阶段名(TEST 2 使用<stage>-jump)。
--only <dirs>全部逗号分隔的阶段目录(如01-beta.39,02-beta.44)。
--no-installnpm 阶段跳过bun install(复用已有安装)。
--keep保留最后部署的应用与 state store(跳过最后一个单元的拆除)。
--reuse-store跨单元复用同一个 state store(更快、隔离性更差),而不是每单元一个全新 store。
--settle <sec>25state store 拆除后、重新部署前等待 workers.dev 传播删除的时间(秒)。避免全新 bootstrap 看到刚删除 Worker 的陈旧版本。
--boot-retries <n>3bootstrap 期间对瞬时 404/500/version-not-ready 的重试次数。

编排器的健壮性设计(源码层面)

run.ts 中有几处值得注意的实现细节:

  • 强制通过(force-through)模式EDGES中每条升级边都在自己的全新 store 上独立尝试,失败只记录、绝不中断整个运行,最后打印 PASS/FAIL 汇总和机器可读的===RESULTS_JSON===块,供下游解析(见 run.ts)。标记某条边为 KNOWN BAD 的方式就是把它从EDGES里注释掉并附上原因。
  • CI=true 防挂起:子进程环境注入CI: "true",让 state store 版本不匹配时的交互式 Yes/No 提示直接失败退出,而不是在非 TTY 管道上永久挂起;同时stdio关闭 stdin,任何漏网提示读到 EOF 都会取消而非阻塞(见 run.ts)。
  • 本地 staging 状态清理clearLocalBootstrapState会删除<stageDir>/.alchemy/state/CloudflareStateStore,防止上次被中途 kill 的 bootstrap 在云端资源已拆除后"恢复"到陈旧的本地后端(只有 bootstrap 用本地状态,应用本身走远端 store,所以删除是安全的)。
  • 瞬态错误重试TRANSIENT正则覆盖Decode error / not ready / version not ready / not found / 404 / 500 / 502 / 503 / fetch failed / ECONN / ETIMEDOUT等,配合--settle--boot-retries吸收部署传播窗口;而真正的非瞬态失败(如 v5→v6 的500 GET … DecodeError)在第一次尝试就会抛出。
  • 部署采用--adopt:接管已存在的同名固定 Worker 而不是报错,是"原地升级"得以成立的关键。

每版本独立集成测试

每个阶段目录还自带独立的test/integ.test.ts(与examples/*/test/integ.test.ts结构一致):在beforeAll中部署该栈、对 Worker 发 HTTP 请求并断言线上marker、在afterAll中销毁。以 05-current/test/integ.test.ts 为例,它使用独立的stage: "integ",避免与编排器的xver状态互相污染,并通过最多 20 次、每次 1 秒的轮询容忍 workers.dev 路由传播的瞬态 404/5xx。

在阶段目录内单独运行:

cd test/cross-version-test/test/05-current ALCHEMY_PROFILE=<profile> bun test # bun test 有缓冲,运行结束前无实时输出

注意:这些测试通过账户级 state store部署,因此 store 必须先处于与该目录 alchemy 匹配的版本(v4beta.39、v5beta.44、v6beta.45、v7beta.59/current)。要么先运行run.ts(它会按阶段 bootstrap),要么手动先 bootstrap:

cd test/cross-version-test/test/01-beta.39 bun run alc -- cloudflare bootstrap --profile <profile> # 把 store 升到 v4 bun test

拆除 state store

run.ts会在单元之间自动拆除 state store(除非--reuse-store),最后还会移除最后一个单元的 store(除非--keep),所以一次正常运行后账户是干净的。

手动移除 state store(alchemy-state-storeWorker、bearer-token 与 encryption-key 两个 secrets、以及可能已变空的 Secrets Store)的方法——例如在--keep运行之后或运行被中断之后:

cd test/cross-version-test/test/05-current # 使用当前分支的 CLI bun run alc -- cloudflare teardown --profile <profile>

cloudflare teardowncloudflare bootstrap的逆操作(与这套测试夹具同期加入)。它是幂等的,且只删除 alchemy 创建的资源——如果 Secrets Store 中还持有外来 secrets,会被原样保留。

⚠️ 必须使用专用 Cloudflare 账户

Cloudflare state store 是账户级的,会被该账户/profile 上的每个栈共享。阶段01会 bootstrap state storev4,如果账户上原本已有更高版本,这会降级store——可能破坏使用同一账户的其他栈。请务必把它指向专用/一次性 Cloudflare 账户。

runner 会在单元之间和运行结束时拆除 state store,因此正常一次运行后账户是干净的。此外,由于--profile没有默认值、缺省即报错退出(run.ts),也天然防止了误操作到错误账户。

已知坏路径(Known-bad upgrade paths)

有两条升级路径被确认是坏的,因此不做测试(在run.tsEDGES中注释掉)。两者都是确定性的、多次运行均可复现:

路径状态原因
v4 → v5✅ 可用
v5 → v6已知坏v6(beta.45)读不了 pre-v6 的 state——500 GET DecodeError
v6 → v7✅ 可用
v5/v6/v7 → worktree✅ 可用
v4 → worktree已知坏v4 store 无法升级到当前—— 写入时415

细节:

  • v5 → v6(beta.44 → beta.45):store 升到 v6 之后,读取 v5 及更早写入的记录会报500 GET … DecodeError。beta.45 的 legacy-record 读取(createdAt/updatedAt 重塑,PR #427)是坏的;在 v7 中已修复——v5 → worktree(跳过 beta.45)可用。不要让 pre-v6 的 state 经过 beta.45。
  • v4 → worktree(beta.39 → current):当前 state-store 客户端写不了 v4 格式的 store——升级在PUT …/StateStoreEncryptionKey上报415 Unsupported Media Type。v4(beta.37–39)早于 v5 的 RPC state-store 重写,其 store HTTP API 与当前版本在 wire 层面不兼容。应先把 v4 store 升到 ≥v5(v5/v6/v7 → worktree均可用)。

其余路径全部通过。bootstrap 一个刚(重新)部署的 state-store Worker 时出现的瞬时404/500属预期现象(bootstrap hoist 时 Worker 尚未就绪),会被逐步重试吸收。

扩展:新增版本或场景

新增一个test/NN-<label>/目录(拷贝现有目录即可):

  1. package.json中锁定对应的alchemy版本(npm 阶段还要同步锁定匹配的effect版本);
  2. src/worker.ts中按需设置markermain形式(旧版用import.meta.filename,HEAD 用import.meta.url);
  3. run.tsSTAGES数组中追加条目;
  4. 保持Stack("CrossVersionApp")与 Workername完全一致,确保升级的是同一个应用。

版本 → state store 版本对照表

alchemystate store 版本锁定的effect备注
beta.29 … 301引入版本门控(#156)
beta.31 … 322
beta.33 … 363
beta.37 … 394beta.66最后一个带 v4 的发布 = beta.39
beta.40 … 445beta.66最后一个带 v5 的发布 = beta.44
beta.456beta.74唯一带 v6 的发布
beta.46 … 597beta.84beta.46 升到 v7(PR #477);npm 上最后一个 v7 = beta.59(= 最新)
当前分支7workspace

这张对照表不仅是测试夹具的配置依据,也是 alchemy 生产升级排期时的权威参考——它直接标出了哪些版本之间可以无缝升级、哪些必须绕行(如 v5 不能经过 beta.45 直达 current)。

小结

cross-version-test 的价值在于把"升级路径回归"从偶发事故变成可重复、可断言、可机器解析的确定性测试:固定身份原地升级 + marker 校验 + 每单元全新 store + force-through 汇总,构成了一个对 beta 期快速迭代的框架而言极其实用的安全网。理解它的编排逻辑、运行参数与已知坏路径,既能帮助你安全地使用 alchemy 的 Cloudflare 集成,也为在 monorepo 中构建同类"跨版本升级回归"测试提供了可复用的范式。

相关资源:README · 编排器 run.ts · 当前分支 Worker · 当前分支集成测试

【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code

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

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

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

立即咨询