qwen-code 的 Windows CI 验证:在自托管 ECS Runner 上运行不削弱安装器覆盖的测试门
2026/9/13 12:56:05 网站建设 项目流程

qwen-code 的 Windows CI 验证:在自托管 ECS Runner 上运行不削弱安装器覆盖的测试门

【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code

本文基于 qwen-code 仓库中的设计文档 windows-ecs-ci-validation.md,系统讲解该项目如何把 Windows 测试门从托管 runner 迁移到自托管 ECS runner(ecs-win),包括跨平台测试的失败分类策略、必须保留的 Windows 安装器覆盖、ci.yml 中test_windows作业的路由与回退机制,以及维护者紧急开关MAINTAINER_ECS_RUNNER_DISABLED的运维语义。读完本文,你能掌握一条完整的"自托管 Windows CI 门"设计链路:从测试分类、workflow 路由、composite action 环境调优,到队列阻塞应急处理。

背景:为什么要迁到自托管 ECS Runner

原设计文档给出的目标只有一句话:在不削弱 Windows 特定安装器覆盖的前提下,把必需的 Windows 测试门跑在自托管 ECS runner 上

仓库中的佐证表明这不是单纯换机器,而是一次带完整安全考量的迁移:

  • CHANGELOG.md 记录了该变更的动机——Windows 合并队列测试默认跑在已验证的ecs-win自托管 runner 上,以降低对托管 runner 池的依赖;
  • ci.yml 中test_windows作业上方的注释详细解释了安全约束:runs-on表达式中专门保留了github.event_name != 'pull_request'这一分支,因为pull_request事件执行的是 PR 自身合并提交的 workflow YAML——任何被该 lane 放行的 PR 都可能在同一份 diff 里改写runs-on,"被门控的树控制了门本身,那就不再是门"。所以ecs-win池只接受未审查 PR 无法触发的触发源:审批后的合并队列(merge_group)、定时任务和手动 dispatch。

test_windows作业的完整入口条件(ci.yml):

test_windows: name: 'Test (windows-latest, Node 22.x)' needs: 'classify_pr' if: |- ${{ !cancelled() && ( github.event_name == 'merge_group' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' ) }} runs-on: '${{ vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'' && github.event_name != ''pull_request'' && fromJSON(''["self-hosted", "Windows", "X64", "ecs-win"]'') || fromJSON(''["windows-2022"]'') }}' timeout-minutes: 60

注意两个细节:作业名刻意保持为Test (windows-latest, Node 22.x)不变,以匹配 required status check 上下文;timeout-minutes: 60是作业级上限。

失败分类策略:什么能在 Windows 上跑、什么必须跳过

迁移的核心风险是:大量脚本测试依赖 POSIX 语义,直接搬到 Windows 会产生噪音失败。设计文档给出了四条分类规则,这里逐条结合仓库源码展开。

规则一:跨平台断言必须使用平台原生路径与解析后的 fixture 根。这意味着断言不能硬编码/分隔符或绝对路径假设,测试要拿到"解析后的 fixture 根"再比较。

规则二:POSIX 独占的文件系统契约,跳过仅限 Windows 无法表达 fixture 的个别用例。文档列举了四类典型契约:可执行 mode bit、文件名中的控制字节、权限失败(如chmod后断言EACCES)、替换一个仍被打开的文件(POSIX 允许、Windows 拒绝)。对应实现落在 scripts/tests/vitest.config.ts——当process.platform === 'win32'时追加排除列表:

exclude: process.platform === 'win32' ? [ ...configDefaults.exclude, 'scripts/tests/e2e-shard-retry.test.js', 'scripts/tests/security-checks-audit-retry.test.js', 'scripts/tests/pr-self-report-label.test.js', // Bash-driven workflow suites cannot run on Windows; pure // YAML-parse workflow suites still do. 'scripts/tests/qwen-*-workflow.test.js', 'scripts/tests/serve-ab-workflow.test.js', ] : [...configDefaults.exclude],

注意注释区分了两类 workflow 套件:Bash 驱动的qwen-*-workflow.test.js整体排除,而纯 YAML 解析的 workflow 套件仍然在 Windows 上运行——这正是文档第四条规则(跨平台脚本测试保持启用)的落地。

规则三:作业只跑在 Linux 的 workflow,其测试从 Windows 脚本套件中排除,Linux CI 仍是其"可达产物与权威覆盖"。即排除不是放弃覆盖,而是把权威覆盖留在 Linux 一侧。

规则四:跨平台脚本测试保持启用;POSIX 独占的 subprocess fixture 逐个加守卫,而不是排除整个可移植测试文件。这与上面的整文件排除形成对照:能守卫的用例留在文件里,避免"排除文件 → 文件里可移植用例也在 Windows 上消失"的覆盖损失。

必需的 Windows 覆盖:install-script.test.js 与九个安装器端到端用例

设计文档明确:Windows 脚本套件必须继续运行 install-script.test.js,包含全部九个 Windows 安装器端到端用例及其安全检查;其他已在 Windows 上通过的脚本测试也保持启用。

源码佐证:

  • 九个端到端用例集中在 install-script.test.js 的describe('Windows installer end-to-end')块中,通过itOnWindows(...)守卫,仅在实际 Windows 环境执行。其中一个用例的形态是:创建一个伪造的 standalone 归档 → 运行安装器 → 断言bin\qwen.cmdqwen-code\node\node.exe存在 → 断言~\.qwen\source.json内容 → 调用qwen.cmd --version验证版本输出。这解释了为什么 Windows runner 上该套件显著变慢——scripts/tests/vitest.config.ts 的注释记录了:install-script.test.js会 shell out 到node运行create-standalone-package.js,在 Windows 上会经历完整的 tar+gzip 流程且处于杀毒软件扫描之下,实测 4780ms / 1666ms / 1079ms,最慢一次恰好贴着 vitest 默认 5s 上限而 flake;后来在共享池上同一工作又慢约 5 倍,最终整套脚本套件的testTimeout定为 90s(可用QWEN_SCRIPTS_TEST_TIMEOUT_MS环境变量覆盖),并用||而非??兜底,防止 workflow 渲染的空字符串''把超时上限悄悄解除(Number('') === 0在 vitest 语义里是"无超时")。

  • 这条"必须保留"规则本身有测试守护:no-ak-integration-ci.test.js 中有用例keeps install-script.test.js out of the win32 exclude list,断言install-script.test.js不会出现在 vitest 配置的 win32 排除列表里——防止未来有人图省事把它一并排除。

  • 同一文件(no-ak-integration-ci.test.js)还锁定了runs-on路由表达式的精确文本,即上文展示的MAINTAINER_ECS_RUNNER_DISABLED三元路由,路由写法变更会先在测试里报错。

test_windows 门的环境装配:自托管分支与托管回退分支

test_windows作业的步骤按runner.environment分叉,设计文档所称"回退不是字节级等价的环境"就体现在这里。逐步骤拆解(ci.yml):

步骤自托管(ECS)行为托管(windows-2022)回退行为
Disable Git CRLF conversioncheckout之前执行git config --global core.autocrlf false(配合.gitattributeseol=lf双保险),仅在runner.environment == 'self-hosted'时运行不运行——这就是文档所说的"pre-checkout autocrlf 步骤是 self-hosted-only"
Configure Windows test environment调用仓库内 composite action configure-windows-runner改为内联的 "Point temp at a short-alias-free directory" 步骤:托管 runner 的TEMP经 8.3 短别名暴露,故把TEMP/TMP指到$RUNNER_WORKSPACE\qwen-code-temp
Verify checkout includes expected head commit两者都运行(PR 与 merge_group 事件下),用 verify-checkout-head 防止自托管 runner 的 stale checkout 静默测试错误的树同左
Node 来源self-hosted-node 复用机器预装 Node,避免 ECS 经出口代理访问 nodejs.org 下载actions/setup-node安装 Node 22.x
npm 缓存持久缓存目录${HOME}/.cache/qwen-code/npm,写入NPM_CONFIG_CACHE无此步骤
npm 限流参数两者都设fetch-retry-mintimeout 20000fetch-retry-maxtimeout 120000fetch-retries 5fetch-timeout 300000同左
运行测试npm run test:ci,并附带每 10s 采样TMPDIR磁盘/inode/内存的后台监视器,失败时 dump 全量df/meminfo 现场同左

其中 configure-windows-runner 做三件事,其描述与文档"locale env、TEMP/TMP 重定向、Git Bash 上 PATH"逐字对应:

  1. TEMP/TMP重定向到RUNNER_TEMP(自托管 runner 保持配置好的、无别名的RUNNER_TEMP路径);
  2. 导出LC_ALL=C.UTF-8,镜像 Linux 门的 locale 环境(在 Windows 上是惰性的,因为 Node 经 ICU 做 collation);
  3. C:\Program Files\Git\bin追加到GITHUB_PATH,让后续步骤能按 workflow 级默认的 bash shell 运行。

该 action 必须放在actions/checkout之后,因为仓库内./相对路径的 action 是从作业工作区解析的——这一约束在 ci.yml 的注释中有明确说明。此外还有一个跨平台细节:ci.yml 的 "Verify temp paths carry no short alias" 步骤用 Node 比较realpathSync结果时大小写不敏感——Windows 路径大小写不敏感,仅大小写差异是同一目录,不能误判为 8.3 别名;而RUNNER~1 → runneradmin这类真正的别名差异会因超出大小写差异而仍然报错。

Runner 冒烟验证:windows-runner-smoke.yml

自托管 runner 环境变更后需要一条可随时触发的验证路径。windows-runner-smoke.yml 就是这个角色,它的validate作业:

  • 只由workflow_dispatch手动触发,runs-on固定为["self-hosted", "Windows", "X64", "ecs-win"]timeout-minutes: 60
  • 刻意镜像ci.yml的 Windows 合并队列门:checkout 前先关 autocrlf,checkout 后走同一个configure-windows-runner,再走同一个verify-checkout-head(因为 smoke 走同一套带缓存的出口代理,若 checkout 缺少被 dispatch 的 head 必须响亮失败,而不是"验证了错误的树");
  • "Verify tools" 步骤逐项断言:TEMP确实被重定向到RUNNER_TEMP(不等则抛错)、Node 主版本 ≥ 22、npm/qwen/gh可用;
  • "Verify symbolic links" 步骤实测创建并读取符号链接——符号链接是 Windows runner 上测试 fixture 的常见前提;
  • 与门相同的 Node 路径(self-hosted-node)、相同格式的 npm 缓存与限流配置、相同的 bash shell 与测试环境变量(清空所有 API key、HOME/USERPROFILE指向runner.temp下的隔离目录),最后执行与门完全一致的npm run test:ci

package.json 中test:ci的构成是理解"完整验证"含义的关键:

"test:ci:workspaces": "cross-env NODE_OPTIONS=\"--max-old-space-size=3072\" npm run test:ci --workspaces --if-present --", "test:ci": "npm run test:ci:workspaces && npm run test:scripts", "test:scripts": "vitest run --config ./scripts/tests/vitest.config.ts"

即各 workspace 包的单测 + 脚本测试套件(含上文讨论的 win32 排除逻辑),这就是 Windows ECS runner 上"完整npm run test:ci"的实际范围。

验证门:什么条件下变更才算就绪

设计文档给出的验收标准分两级:

  1. 本地(适用处):格式化检查、lint 配置检查、目标化的 CLI 与 core 测试、脚本套件通过;
  2. Windows ECS runner 上:完整npm run test:ci通过。

结合上文的路由规则可以推断,本地开发者实际可复现的是第 1 级;第 2 级依赖ecs-win(或回退的windows-2022)环境特有的行为(杀毒扫描下的耗时、符号链接、短别名等),因此被放在门的一端作为最终裁决。

运维:runner 离线时的队列阻塞与紧急开关

这是设计文档中最具实战价值的部分,原文的完整语义如下,配合 ci-platform-lanes.test.js 等测试中对同一开关的引用逐条展开。

问题:离线不报错,而是排队。ecs-winrunner 离线时,作业不是失败,而是进入队列等待——这会阻塞整个 merge queue,直到维护者介入。因为runs-on是标签匹配,没有 runner 在线就永远等不到匹配。

开关:MAINTAINER_ECS_RUNNER_DISABLED仓库变量。将其设为true后,runs-on表达式的左支短路失败,Windows 门回落到windows-2022。但该开关有两个必须理解的边界:

  1. 已入队的运行不会重新求值runs-on已经排在离线ecs-win标签下的 run 会继续等待,而且timeout-minutes不计入排队时间——所以翻转开关后,必须手动取消或重跑这些已排队的 run,否则它们仍会挂住。
  2. 回退环境不是字节级等价。pre-checkout 的 autocrlf 步骤和configure-windows-runner的整套调优(locale env、TEMP/TMP 重定向、Git Bash 上 PATH)都是 self-hosted-only,托管回退跑的是"迁移前的作业形态"——因此回退期间的绿色结果只能证明主干未被破坏,不能证明 ECS 配置本身有效(ECS 配置要靠 windows-runner-smoke.yml 单独验证)。

容量:单台健康但繁忙的ecs-win也会串行化队列。ecs-win池只有一台机器,而托管windows-2022池原本可并行处理合并队列条目;迁到单台自托管机后,并发队列条目必须排队等这一台机器。从 ci.yml 中 Linux 侧的对应处理可以印证这类权衡的普遍性:ECS 共享主机上仅安装依赖就可能超过 30 分钟,因此integration_no_ak作业的超时按是否落在ecs-qwen上动态取 60 或 30 分钟。

小结与深入阅读路径

这条 Windows CI 设计可以用三句话概括:用分类规则把不可移植的测试隔离掉而不是砍掉 Windows 门;用一条镜像门的 smoke workflow 持续验证自托管 runner 本身;用一个仓库变量开关在 runner 离线时把门打回托管池,并接受"回退不等价、排队需人工清"的两个已知代价。

如需继续深入,建议按以下顺序阅读仓库文件:

  • docs/design/windows-ecs-ci-validation.md:本文的设计原文;
  • ci.yml:test_windows作业完整定义(路由、分叉步骤、测试执行);
  • windows-runner-smoke.yml:runner 冒烟验证全流程;
  • .github/actions/configure-windows-runner/action.yml:自托管 Windows 环境调优的 composite action;
  • scripts/tests/vitest.config.ts:win32 排除列表与超时策略;
  • scripts/tests/install-script.test.js:九个 Windows 安装器端到端用例;
  • scripts/tests/no-ak-integration-ci.test.js:对路由表达式与排除列表的测试守护。

【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code

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

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

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

立即咨询