Skip to content

test(tui): pin the runner locale and the update host so the suite is machine-independent - #137

Merged
fengzhi09 merged 2 commits into
mainfrom
fix/capabilities-locale-and-platform
Sep 30, 2026
Merged

fengzhi09 merged 2 commits into
mainfrom
fix/capabilities-locale-and-platform

Conversation

@fengzhi09

Copy link
Copy Markdown
Collaborator

问题

pnpm test:capabilities 在本机红 37 条,历史工单多次记录,从未归因。

诊断(94 号工单)

根因 条数 机制
locale 未隔离 29 copy.ts:13 的 sessionMutationLocale() 回落 Intl 默认 locale、messages.ts:86 读 LANG;本机 LANG=zh_CN.utf8 使 TUI 输出中文("2 个文件 · 5分钟前"),测试硬编码英文断言
宿主平台 8 + 1 error release.ts:39-45 的 TARGETS 只有 darwin×2 / linux-x64 / windows×2;本机 linux-arm64 不在其中,release.ts:188 抛 Unsupported MCode update host: linux-arm64

0 条真缺陷。 且 CI 一直是绿的——CI runner 是 en locale + x64/darwin-arm64,两个触发条件都不满足。只有中文 locale 或 linux-arm64 开发机本地会红。

对照实验:LC_ALL=en_US.UTF-8 下那 4 个文件 449/449 全绿(单一变量坐实)。

94a:locale 隔离

scripts/run-vitest-suite.mjs spawn 时注入 LC_ALL 与 LANG = en_US.UTF-8。

⚠️ 两个都设的原因:sessionMutationLocale() 走 Intl.DateTimeFormat().resolvedOptions().locale(Node 从 LANG 推导),而 process.env.LANG 是字面量读取、不受 LC_ALL 影响。只设 LC_ALL 会漏掉 copy.ts:13、runtime/locale.ts:7、static-prompt-reader.ts:147、validation.ts:121 四条路径。

副作用核查

  • test:webapp / test:webui 是 node --test 直连,根本不经过 runner,注入到不了(最强因果隔离)——但仍全跑
  • 真正受影响的 5 个 runner 套件全跑:policy 142 pass · sandbox 26 pass/22 skip · status-contract 9 pass · windows 全 skip · capabilities 5150 pass,全 exit 0
  • 另跑 test:release-tools(73/74)——source-sync.test.mjs:1002 会 copyFileSync 本 runner 并断言参数,是唯一直接覆盖被改文件的回归
  • 反查 packages/**/*.test.* 对 LANG/LC_ALL 的引用:0 命中,无测试故意依赖非英文 locale

残留面如实记录:node-runtime.ts:850 的透传白名单会把 LANG/LC_ALL 转发给 miniapp 子进程。

94b:平台固定(偏离了原指定,理由如下)

Object.defineProperty 固定 process.platform / process.arch,afterEach 用捕获的原始 descriptor 还原。

⚠️ 实测 Node v24 的 process.platform 是 {writable:false, configurable:true} —— 直接赋值会被静默忽略,只有 defineProperty 有效。

为什么不是无条件钉死 linux/x64

CI 矩阵含 windows-latest,而产品代码在安装路径上确实读 platform(service.ts:265 的 win32 分支要求 LOCALAPPDATA、install-source.ts:87/272)。无条件钉 linux 会让该文件里 .skipIf(process.platform !== 'win32') 的 Windows 独占安装测试在 Windows 上跑进错误分支——等于拿本机修复换 Windows CI 回归。

当前写法:宿主本身是 required target 就用宿主值,否则回落 linux-x64(对应 release.ts:39 REQUIRED_V1_TARGETS 第三条真实条目)。对 CI 三平台行为逐字节不变,win32 测试照旧走真实分支;skipIf 在 collection 期求值、早于 beforeEach,不受影响。

变异验证(都精确复现)

变异 结果
去 94a 29 failed / 4 文件红,exit 1
去 94b 8 failed + 1 unhandled error,exit 1

两次还原后 md5sum -c 均「成功」,逐字节一致。

数字

Test Files Tests exit
改前 5 failed / 196 passed 37 failed / 5113 passed / 18 skipped / 1 error 1
只加 94a 1 failed / 200 passed 8 failed(全部集中在 update-service) 1
最终 201 passed 0 failed / 5150 passed / 18 skipped 0

门禁(提交后干净树,全 exit 0)

webui:typecheck · typecheck · test:webapp(1842 pass) · test:webui(2179 pass/2 skip) · check:source(4900 files) · check-docs-alignment(6/6) · test:capabilities · test:release-tools

明确未做

  • 未向 release.ts 的 TARGETS 加 linux-arm64 —— 那是发布契约变更(TARGETS + manifest + 发布制品),留产品决策
  • 未改 docs/ —— 核查确认仓库无 DEVELOPMENT.md,且 README/CONTRIBUTING/docs 检索 locale|LANG=|LC_ALL 无任何一处描述 runner 的环境行为

顺带确认:test:webui 的既有 flake 与本单无关

chat-run-mirror.check.mjs 的 waitFor timed out: turn to drain(2s 超时级联 8 条)。已 stash 掉本单两处改动、在基线上单文件循环 8 次复现 2 次失败,证明无关。建议另开工单(该 flaky 的根因已由 #132 定位为毫秒边界竞态)。

🤖 Generated with DeepSeek Harness

s39-dev added 2 commits September 30, 2026 22:08
The TUI resolves its language from the environment: sessionMutationLocale()
falls back to Intl's resolved locale, which Node derives from LANG, and
packages/tui/src/update/messages.ts reads LC_ALL/LC_MESSAGES/LANG directly.
A developer shell running LANG=zh_CN.utf8 therefore renders Chinese copy
while the suites assert the English strings they ship, and 29 capability
tests fail on a host CI never exercises.

Spawn the suite with LC_ALL and LANG set to en_US.UTF-8 so every group runs
under one locale. Pinning the two variables together covers the Intl and the
environment lookup paths, which stubbing each test would miss. Fixing it in
the runner keeps the suites aligned with CI instead of teaching every
translation-sensitive assertion about the developer's shell.

Purely test infrastructure; no product behaviour changes.
McodeUpdateService.check() calls resolveMcodeReleaseTarget() with no override,
so it reads process.platform and process.arch. release.ts publishes only
darwin-arm64, darwin-x64, linux-x64, windows-x64 and windows-arm64, and a
host outside that set — linux-arm64, for example — fails every check with
"Unsupported MCode update host" regardless of what the test asserts.

Pin both properties with Object.defineProperty in beforeEach and restore the
captured host descriptors in afterEach, so the rest of the process is
untouched. The fallback is linux-x64, a real REQUIRED_V1_TARGETS entry, so the
tests keep exercising a shipped target instead of an invented one.

Hosts that already publish keep their own platform and arch, which leaves the
win32-only install test on Windows driving the real win32 branch of
resolveMcodeInstallRoot and detectMcodeInstallSource.

Purely test infrastructure; release.ts TARGETS is unchanged, so whether
linux-arm64 should become a published target stays a product decision.
@fengzhi09
fengzhi09 merged commit 6129d80 into main Sep 30, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant