Skip to content

perf(cli): let tests resolve core modules individually - #10917

Open
yiliang114 wants to merge 3 commits into
mainfrom
perf/core-subpath-imports
Open

perf(cli): let tests resolve core modules individually#10917
yiliang114 wants to merge 3 commits into
mainfrom
perf/core-subpath-imports

Conversation

@yiliang114

Copy link
Copy Markdown
Collaborator

What this PR does

Teaches the cli test runner to resolve individual core modules, and moves two files off the core package root as a first end-to-end check that the mapping works.

The runner's alias list is expressed as an ordered array so it can carry a pattern entry, mirroring the wildcard subpath rule cli's tsconfig already has. The package root becomes an exact match in the process — spelled as a string it would also match everything beneath it and rewrite each subpath into a path under index.ts.

Why it's needed

Importing from the core package root pulls in its entire export graph, a bit over six hundred modules, however little of it a file actually uses. In release run 33713579913 the cli workspace reported 2223s collecting modules against 1372s running tests; core reported 546s against 251s. A file that imports the package root costs roughly 11.5s before it reaches its first assertion, where the same file importing a single module costs about 2s — and the suites that already replace the package with a mock factory, and so never evaluate it, have always run at about 1.9s.

esbuild reads tsconfig paths, so the bundle already resolves per-module imports. The test runner does not read them, and the alias list standing in for them named only four subpaths, so per-module imports did not resolve under test at all. That gap is what this PR closes; the two migrated files exist to prove it end to end before anything larger moves.

Background and measurements are in #10908.

Reviewer Test Plan

How to verify

The two migrated files should behave identically — the change is which module the same symbols come from. Their own suites cover them, and the rest of the cli suite exercises the alias change, since every test in the package now resolves the package root through a pattern entry rather than a string one. A resolution mistake here fails loudly at import time rather than subtly, so a green cli run is the signal.

Worth a reviewer's eye: the ordering in the alias array. The four named subpaths must stay ahead of the pattern entry because their targets are not derivable from their names, and the package root must remain an exact match.

Evidence (Before & After)

N/A — no user-visible behavior changes.

Tested on

OS Status
🍏 macOS ⚠️
🪟 Windows ⚠️
🐧 Linux ⚠️

Not run locally; relying on CI across the three platforms.

Risk & Scope

  • Main risk or tradeoff: the alias change affects how every cli test resolves the core package. It is a like-for-like mapping onto the same sources the previous entry pointed at, so a mistake surfaces as an import failure rather than as wrong behavior.
  • Not validated / out of scope: only two files move here. Files whose dependents replace the core package with a mock factory are deliberately untouched — once the code under test imports a module directly, such a mock stops intercepting, and those call sites need their mocks moved in the same change. About thirty test files sit in that category for the next batch alone, so they are being handled separately rather than folded in here.
  • Breaking changes / migration notes: none.

Linked Issues

Refs #10908

中文说明

这个 PR 做了什么

让 cli 的测试运行器能够解析 core 的单个模块,并把两个文件从包根导入改成按模块导入,作为端到端的第一次验证。

alias 列表改成有序数组形式,以便携带一条通配规则,对齐 cli tsconfig 里已有的通配 subpath 映射。包根在此过程中必须改成精确匹配——写成字符串时它同样会匹配其下所有子路径,把每个子路径重写成 index.ts 下的路径。

为什么需要

从 core 包根导入会拉进它的整个导出图,六百多个模块,无论调用方实际只用了多少。在 release run 33713579913 中,cli 的模块收集耗时 2223s、跑测试 1372s;core 是 546s 对 251s。一个从包根导入的文件在到达第一条断言之前要花约 11.5s,而同一文件改成按模块导入只要约 2s——那些本来就用 mock 工厂替换整个包、因而从不求值它的用例,一直是约 1.9s。

esbuild 会读 tsconfig 的 paths,所以打包时已经能解析按模块导入。测试运行器不读 paths,而代替它的 alias 列表只列了四个具名 subpath,因此按模块导入在测试里根本解析不了。本 PR 补的就是这个缺口;两个迁移文件的作用是在更大范围改动之前把链路走通。

背景和测量数据见 #10908

审查者验证计划

如何验证

两个迁移文件的行为应完全不变——变的只是同一批符号来自哪个模块。它们各自的用例覆盖了自身,而整个 cli 套件则检验了 alias 改动,因为现在包中每个测试都通过通配规则而非字符串规则解析包根。这里若有解析错误会在导入期直接报错而不是悄悄改变行为,所以 cli 跑绿就是信号。

值得审查者留意的是数组里的顺序:四个具名 subpath 必须排在通配规则之前(它们的目标路径无法从名字推导),包根必须保持精确匹配。

证据(前后对比)

N/A —— 无用户可见行为变化。

风险与范围

  • 主要风险或权衡:alias 改动影响 cli 每个测试解析 core 包的方式。它与原先那条规则指向同一批源文件,是等价映射,出错会表现为导入失败而非行为错误。
  • 未验证 / 超出范围:本 PR 只迁移两个文件。那些「依赖方用 mock 工厂替换整个 core 包」的文件被刻意跳过——一旦被测代码直接按模块导入,这类 mock 就不再拦截,这些调用点需要在同一次改动里同步迁移 mock。仅下一批就有约三十个测试文件属于这种情况,因此单独处理而不并入本 PR。
  • 破坏性变更 / 迁移说明:无。

关联 Issue

Refs #10908

…files importing the whole package

Importing from the core package root pulls in its entire export graph — a bit
over six hundred modules — however little of it a file actually uses. In a
release run the cli workspace spent 2223s collecting modules against 1372s
running tests, and a file that imports the package root costs about 11.5s
before its first assertion where one importing a single module costs about 2s.

cli's tsconfig already maps a wildcard subpath onto core's sources, so esbuild
resolves per-module imports when it bundles. Vitest does not read tsconfig
paths, and the alias list that stands in for them named only four subpaths, so
those imports did not resolve under test at all. This adds the wildcard there.

Expressing the alias list as an ordered array is what allows a pattern entry.
The package root has to become an exact match in the process: as a string it
would also match everything beneath it and rewrite each subpath into a path
under index.ts.

Two files move to per-module imports as a first check that the mapping holds
end to end. Both were picked because nothing that depends on them replaces the
core package with a mock factory — where a test does that, the mock stops
intercepting once the code under test imports the module directly, so those
call sites need their mocks moved in the same change and are left alone here.
@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR — the motivation here is real and well measured.

Template ✓ — all sections present, bilingual body complete.

Problem: observed, not theoretical. #10908 documents concrete numbers (release run 33713579913: cli spends 2223s collecting vs 1372s running; a package-root import costs ~11.5s before the first assertion vs ~2s for a per-module import). Quantified and reproducible from CI data.

Direction: aligned. CI collect-time is an active concern in this repo (#10908, and #10870 / #10869 landed in the same area this week). esbuild already resolves per-module imports through tsconfig paths while Vitest cannot — that gap is genuine, and closing it is the right move.

Size: ~6 production source lines (two import swaps in RemoteInputWatcher.ts and tipHistory.ts); the remaining ~340 changed lines are packages/cli/vitest.config.ts (test infrastructure). No size gate triggered.

Approach: the described change — ordered alias array, a wildcard mirroring the @qwen-code/qwen-code-core/* rule in tsconfig.json, package root kept as an exact match — is the right shape. But the diff carries materially more than the description says, and that's the concern:

  • The commit also drops settings that are live on main today: the RUNNER_NAME-conditioned ECS timeouts (60s) and maxWorkers: '25%', environment: 'node', the globalSetup fail-fast guard (Package-local unit tests do not run in a fresh checkout; the documented command fails with a misleading resolution error #9149, documented in AGENTS.md), the off-Linux dangerouslyIgnoreUnhandledErrors exemption, and the CI coverage gating. Several of those are pinned by scripts/tests/unit-vitest-configs.test.ts, and two run directly against this PR's own goal (jsdom for every file and always-on coverage both cost measurable wall time). The file in this commit looks like it may have been edited against an older snapshot of main — worth reconciling.
  • Four camelCase core aliases were removed (noFollowOpen, subSessionConstants, toolWriteOrigin, envVarResolver). The wildcard cannot derive them, and three still have live import sites on main (settings.ts, fast-path-settings.ts, workspace-registration-store.ts, acp-integration/service/filesystem.ts, serve/bridge-file-system-adapter.ts). The "four named subpaths" the description says must precede the pattern entry are actually at least seven.

Risk: no elevated-risk-path signals — none of the three changed files match the revert-correlated paths.

Flagging the scope mismatch before diving deeper — moving on to code review. 🔍

中文说明

感谢贡献!这个 PR 的动机是真实且有数据支撑的。

模板 ✓ —— 各节齐全,中英双语完整。

问题: 已观测到,而非理论问题。#10908 记录了具体数据(release run 33713579913:cli 模块收集 2223s 对比执行 1372s;包根导入到达第一条断言前约 11.5s,按模块导入约 2s)。量化且可从 CI 数据复现。

方向: 对齐。CI 收集耗时是仓库当前关注点(#10908,本周 #10870 / #10869 也落在同一领域)。esbuild 已通过 tsconfig paths 解析按模块导入,而 Vitest 不能——这个缺口真实存在,补上它是正确的。

规模: 约 6 行生产源码改动(两个文件的导入替换);其余约 340 行改动在 packages/cli/vitest.config.ts(测试基础设施)。未触发规模门槛。

方案: 描述中的改动——有序 alias 数组、对齐 tsconfig.json@qwen-code/qwen-code-core/* 规则的通配项、包根保持精确匹配——形态是对的。但 diff 实际携带的内容明显多于描述,这是顾虑所在:

  • 该提交同时删掉了当前 main 上生效的若干配置RUNNER_NAME 条件的 ECS 超时(60s)与 maxWorkers: '25%'environment: 'node'globalSetup 快速失败守卫(Package-local unit tests do not run in a fresh checkout; the documented command fails with a misleading resolution error #9149,AGENTS.md 有记载)、非 Linux 的 dangerouslyIgnoreUnhandledErrors 豁免、以及 CI 覆盖率门控。其中数项被 scripts/tests/unit-vitest-configs.test.ts 钉住,且有两项与本 PR 自身目标相悖(全量 jsdom 与常开覆盖率都会带来可测量的墙钟开销)。本提交里的这个文件看起来可能是基于较早的 main 快照编辑的——值得先对齐。
  • 删除了四个驼峰命名的 core alias(noFollowOpensubSessionConstantstoolWriteOriginenvVarResolver)。通配规则无法推导出它们,且其中三个在 main 上仍有活跃导入点(settings.tsfast-path-settings.tsworkspace-registration-store.tsacp-integration/service/filesystem.tsserve/bridge-file-system-adapter.ts)。描述中说"必须排在通配规则之前的四个具名 subpath",实际上至少有七个。

风险: 无高风险路径信号——三个改动文件均不命中与 revert 相关的路径。

先提出范围不一致的问题,再深入——进入代码审查。🔍

Qwen Code · qwen3.8-max

Reviewed at 8822672a847d87abfc463edb6ab3c380d072c044 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Code review

Independent baseline first: for "let tests resolve core modules individually", the minimal change is one wildcard alias mirroring the @qwen-code/qwen-code-core/* tsconfig rule, the package root kept as an exact match, every existing named alias left in place, plus one or two migrated files as proof — a ~30-line diff. The additive half of this PR matches that shape cleanly. The problem is everything else in packages/cli/vitest.config.ts.

Blockers

  1. Dropped aliases that still have consumers on main. noFollowOpen, toolWriteOrigin, and envVarResolver are removed, but five files on main import them (src/config/settings.ts, src/serve/fast-path-settings.ts, src/serve/workspace-registration-store.ts, src/acp-integration/service/filesystem.ts, src/serve/bridge-file-system-adapter.ts). The wildcard cannot derive these — it rewrites @qwen-code/qwen-code-core/envVarResolver to core/src/envVarResolver, while the file lives at core/src/utils/envVarResolver.ts (utils/no-follow-open.ts and services/tool-write-origin.ts likewise). Alias rewrites preempt normal resolution with no fallback, so every suite that transitively imports settings.ts fails at import time once this merges — exactly the "fails loudly at import time" behavior the PR description itself promises. (subSessionConstants has no import site on main, so dropping that one is fine.) The "four named subpaths" that must precede the pattern entry are actually seven.
  2. Settings deleted that are pinned by witness tests. scripts/tests/unit-vitest-configs.test.ts asserts, for the cli config: dangerouslyIgnoreUnhandledErrors === (platform !== 'linux') — the flag is deleted, so the pin fails on every platform (the exemption exists for the vitest RPC 60s-budget failure class behind Main CI failed: Qwen Code CI on 5ae363e2f906 #10438); and under RUNNER_NAME=ecs-qwen-parity the config must yield testTimeout 60000, hookTimeout 60000, maxWorkers '25%' — this PR hardcodes 15000 and deletes the rest. Both assertions fail deterministically; the scripts suite will be red.
  3. globalSetup fail-fast guard deleted. That guard is the documented fix for Package-local unit tests do not run in a fresh checkout; the documented command fails with a misleading resolution error #9149 (fresh clone / new worktree / deep clean) and is referenced in AGENTS.md's unit-test instructions. Removing it turns actionable setup errors into opaque import failures again. Nothing in the description mentions it.
  4. Two changes run directly against the PR's own perf goal. environment: 'node''jsdom' for every file — the comment on main records per-file jsdom costing 0.2–0.5s ("a tenth of the suite"), with DOM-needing files already opting in via // @vitest-environment jsdom pragmas; and coverage.enabled: true on every run — main gates coverage behind QWEN_CI_COVERAGE because v8 instrumentation costs about a fifth of suite wall time. Both make CI slower, which is the opposite of CI test time is bound by module import cost, not scheduling #10908. The new hardcoded minThreads: 8 / maxThreads: 16 also replaces the CPU-scaling default and the deliberate ECS '25%' cap ("ECS hosts run several jobs at once; leave capacity for neighboring jobs").
  5. The new import style doesn't resolve at runtime. @qwen-code/qwen-code-core/utils/debugLogger.js, /config/storage.js, /utils/atomicFileWrite.js resolve under Vitest (new wildcard alias) and in the bundle (esbuild reads tsconfig paths), but not under plain Node: core's exports map has no ./utils/* or ./config/* entry, and scripts/dev.js's loader intercepts only the exact package-root specifier. npm run dev hits ERR_PACKAGE_PATH_NOT_EXPORTED as soon as tipHistory (Tips / tipScheduler, TUI startup path) or RemoteInputWatcher loads. If path-style specifiers are the way forward, core's exports needs matching entries (and the two schemes reconciled); otherwise the named-subpath style is the established convention precisely because it resolves everywhere. The symbols themselves check out — createDebugLogger, Storage, and atomicWriteFileSync all exist at the target paths.

Minor

  • The drive-by comment edit in RemoteInputWatcher.ts goes the wrong way: useLlmStreamuseGeminiStream reverts to the pre-rename name; the hook is src/ui/hooks/use-llm-stream.js on main now. Unrelated to the alias change — suggest dropping it.
  • The overall shape — this commit's vitest.config.ts is an older state of main plus the alias array — suggests the file was edited against a stale snapshot. Rebasing alone won't reconstruct the intended diff; the deletion hunks need to be dropped, keeping only the alias restructuring and the two import migrations.

Testing

Evidence carried: the PR's own CI, read via the API at review time (unattended run — no PR code executed here). The unit suite is still in flight on this commit (~30-minute suite; not polling — the finalize job updates the table below once CI settles). Static read of scripts/tests/unit-vitest-configs.test.ts says the scripts suite fails on the deleted settings (blocker 2), and the dropped aliases should fail the cli suite at import time (blocker 1) — treat those as predictions until the run lands. Not verified: the collect-time improvement itself — it cannot be measured on this commit, since the diff re-adds the two biggest wall-time costs (jsdom-everywhere, always-on coverage) it set out to remove.

Final CI results for 8822672 (auto-updated by the triage finalize job after CI completed):

Check Conclusion
Test (ubuntu-latest, Node 22.x) ❌ failure
Classify PR ✅ success
Dependency CVE audit ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
Integration Tests (no-AK, No Sandbox) ✅ success
Lint & Static (ubuntu-latest, Node 22.x) ✅ success
OpenTUI no-flicker gate ✅ success
Secret scan (TruffleHog) ✅ success
TUI parity snapshots (ink vs opentui) ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success

One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。

Once the diff is reduced to the intended change, the perf claim's oracle is CI's own timing — the collect-vs-run split from #10908 — rather than a /verify A/B (which targets runtime behavior, not test-infrastructure timing).

中文说明

代码审查

先说独立基线:要让测试按模块解析 core,最小改动是加一条对齐 tsconfig @qwen-code/qwen-code-core/* 规则的通配 alias、包根保持精确匹配、其余具名 alias 原样保留,再迁移一两个文件作为验证——约 30 行的 diff。本 PR 的"加法"部分与这个形态完全吻合。问题出在 packages/cli/vitest.config.ts 里的其余内容。

阻塞项

  1. 删除了仍有消费者的 alias。 noFollowOpentoolWriteOriginenvVarResolver 被删除,但 main 上有五个文件在导入它们(src/config/settings.tssrc/serve/fast-path-settings.tssrc/serve/workspace-registration-store.tssrc/acp-integration/service/filesystem.tssrc/serve/bridge-file-system-adapter.ts)。通配规则无法推导出这些——它把 @qwen-code/qwen-code-core/envVarResolver 重写成 core/src/envVarResolver,而文件实际在 core/src/utils/envVarResolver.tsutils/no-follow-open.tsservices/tool-write-origin.ts 同理)。alias 重写优先于正常解析且无回退,因此合并后所有传递导入 settings.ts 的用例都会在导入期失败——正是 PR 描述自己承诺的"导入期直接报错"。(subSessionConstantsmain 上没有导入点,删它没问题。)"必须排在通配规则之前的具名 subpath"实际上是七个,不是四个。
  2. 删除了被见证测试钉住的配置。 scripts/tests/unit-vitest-configs.test.ts 对 cli 配置断言:dangerouslyIgnoreUnhandledErrors === (platform !== 'linux')——该标志被删除,此钉在所有平台都会失败(该豁免是为 Main CI failed: Qwen Code CI on 5ae363e2f906 #10438 背后的 vitest RPC 60 秒预算失败类而存在的);且在 RUNNER_NAME=ecs-qwen-parity 下配置必须给出 testTimeout 60000、hookTimeout 60000、maxWorkers '25%'——本 PR 硬编码 15000 并删除了其余项。两条断言都会确定性失败,scripts 套件会变红。
  3. globalSetup 快速失败守卫被删除。 该守卫是 Package-local unit tests do not run in a fresh checkout; the documented command fails with a misleading resolution error #9149(新克隆 / 新 worktree / 深度清理)的文档化修复,AGENTS.md 的单测说明里也引用了它。删除后,可操作的配置错误会重新变成难以理解的导入失败。描述中对此只字未提。
  4. 两处改动与本 PR 自身的性能目标直接相悖。 environment: 'node' → 全量 'jsdom'——main 上的注释记录每个文件的 jsdom 成本为 0.2–0.5 秒("十分之一的套件"),需要 DOM 的文件已通过 // @vitest-environment jsdom 声明按需启用;以及 coverage.enabled: true 全量开启——mainQWEN_CI_COVERAGE 门控覆盖率,因为 v8 插桩约占套件墙钟时间的五分之一。两者都让 CI 更慢,与 CI test time is bound by module import cost, not scheduling #10908 目标相反。新加的硬编码 minThreads: 8 / maxThreads: 16 还替换了按 CPU 伸缩的默认值和刻意的 ECS '25%' 上限("ECS 主机同时跑多个任务,要给相邻任务留容量")。
  5. 新导入风格在运行时无法解析。 @qwen-code/qwen-code-core/utils/debugLogger.js/config/storage.js/utils/atomicFileWrite.js 在 Vitest(新通配 alias)和打包(esbuild 读 tsconfig paths)下可解析,但在纯 Node 下不行:core 的 exports 映射没有 ./utils/*./config/* 条目,而 scripts/dev.js 的 loader 只拦截精确的包根说明符。npm run dev 会在 tipHistory(Tips / tipScheduler,TUI 启动路径)或 RemoteInputWatcher 加载时抛出 ERR_PACKAGE_PATH_NOT_EXPORTED。如果要以路径风格说明符作为方向,core 的 exports 需要补上对应条目(并统一两套方案);否则具名 subpath 风格之所以是既有约定,正是因为它在所有环境下都能解析。符号本身没问题——createDebugLoggerStorageatomicWriteFileSync 都存在于目标路径。

次要

  • RemoteInputWatcher.ts 里顺带的注释编辑方向反了:useLlmStreamuseGeminiStream 退回了改名前的旧名;main 上该 hook 现在是 src/ui/hooks/use-llm-stream.js。与 alias 改动无关——建议去掉。
  • 整体形态——本提交的 vitest.config.ts 等于"较早的 main 状态 + alias 数组"——表明该文件是基于过期快照编辑的。仅 rebase 无法重建预期 diff;需要删掉那些删除类 hunk,只保留 alias 重构和两个文件的导入迁移。

测试

证据说明:本节引用的是 PR 自身 CI 在审查时刻经 API 读取的真实状态(无人值守运行——此处未执行任何 PR 代码)。该提交的单测套件仍在运行(约 30 分钟;不轮询——finalize 任务会在 CI 结束后更新下表)。对 scripts/tests/unit-vitest-configs.test.ts 的静态阅读表明 scripts 套件会因被删配置而失败(阻塞项 2),被删 alias 应使 cli 套件在导入期失败(阻塞项 1)——在运行结果落地前请视为预测。未验证:收集耗时的改善本身——在本提交上无法测量,因为 diff 重新引入了它本要移除的两项最大墙钟开销(全量 jsdom、常开覆盖率)。

上表由 finalize 任务在 CI 结束后原地更新。待 diff 缩减为预期改动后,性能结论的判据是 CI 自身的时间数据(#10908 中"收集 vs 执行"的拆分),而非 /verify A/B(后者针对运行时行为,不针对测试基础设施耗时)。

Qwen Code · qwen3.8-max

Reviewed at 8822672a847d87abfc463edb6ab3c380d072c044 · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 2/5 — the idea is right and the alias-array half is well done, but the diff as committed regresses several deliberate, recently-landed settings and breaks resolution in two places; it can't merge as-is.

Stepping back: the motivation is genuine — #10908's numbers are concrete, and the gap between esbuild (reads tsconfig paths) and Vitest (doesn't) is real. My independent proposal for this problem was almost exactly the additive half of this PR: one wildcard alias mirroring the tsconfig rule, the root kept exact, existing named aliases untouched, two files migrated as proof. That part here is clean and even comes with the right ordering rationale.

What I can't sign is the rest. This commit's vitest.config.ts is effectively an older snapshot of main with the alias array grafted on: it drops the ECS timeout/worker consideration, the node-default environment, the #9149 globalSetup guard, the coverage gating, and the unhandled-error exemption — settings that exist because of specific incidents (#10438, #9149) and are pinned by scripts/tests/unit-vitest-configs.test.ts precisely so they can't leave quietly. Two of the deletions (jsdom everywhere, always-on coverage) re-add the largest wall-time costs the suite carries, which is the opposite of what a perf PR for CI time should ship. The dropped noFollowOpen / toolWriteOrigin / envVarResolver aliases still have live import sites on main, and the new path-style imports don't resolve under npm run dev (no matching core exports entries; the dev loader only intercepts the package root). In six months this merge would be the commit everyone bisects their red lane back to.

The fix is small: rebuild the commit on current main, keep only the alias restructuring (with all seven non-derivable named entries ahead of the wildcard), the two import migrations, and decide the runtime-resolution question — either add core exports entries for the path style or migrate to the existing named-subpath style. Happy to re-review as soon as that lands.

Qwen Code · qwen3.8-max

Reviewed at 8822672a847d87abfc463edb6ab3c380d072c044 · re-run with @qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yiliang114 Needs some rethinking before this can merge — full notes in the review comment above. The short version: the alias-array change itself is good, but this commit also drops settings that are live on main (ECS timeouts/maxWorkers, node-default environment, the #9149 globalSetup guard, coverage gating, the unhandled-error exemption — several pinned by scripts/tests/unit-vitest-configs.test.ts), removes three core aliases that still have import sites, and the new path-style imports don't resolve under npm run dev (no core exports entries). Rebuilding the commit on current main with only the alias restructuring + the two migrations should get it home. 🙏

中文说明

合并前需要重新考虑——完整说明见上方审查评论。简版:alias 数组改动本身没问题,但本提交同时删掉了 main 上生效的配置(ECS 超时/maxWorkers、node 默认环境、#9149 的 globalSetup 守卫、覆盖率门控、未处理错误豁免——其中数项被 scripts/tests/unit-vitest-configs.test.ts 钉住),移除了三个仍有导入点的 core alias,且新的路径风格导入在 npm run dev 下无法解析(core exports 没有对应条目)。基于当前 main 重建提交,只保留 alias 重构和两个文件的迁移,应该就能过。🙏

The previous commit was assembled from a working copy that predated main by
several weeks, so it silently reverted this file to that older state. Four
named core subpaths added since — envVarResolver, noFollowOpen,
subSessionConstants and toolWriteOrigin — disappeared with it, and the new
wildcard then claimed those specifiers and pointed them at files that do not
exist. 257 test files failed to load as a result.

All eight named subpaths are restored and kept ahead of the wildcard, with a
comment saying why that order matters and what a contributor adding a ninth
has to do. None of the eight can be derived from its specifier, so none of
them can be folded into the pattern.

The two migrated source files are rebuilt on their current contents for the
same reason; one of them had also been reverted by a line.
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

🖼️ web-shell visual preview

Rendered against a mock daemon (no real backend): the PR base vs this PR head 8b3d0b0. Only screenshots that changed are shown (flows below, if any, are head-only) — refreshes on every push.

Screenshots · before / after

⚠️ One or more scenarios failed to render on this head, so this preview may be missing views — see the workflow run. The composites below are the scenarios that did render.

workflow-page-running-dark before/after

workflow-page-running-light before/after

workflow-page-saved-dark before/after

workflow-page-saved-detail-dark before/after

workflow-page-saved-detail-light before/after

workflow-page-saved-light before/after

Full-resolution recordings (.webm) are attached to the workflow run.

Qwen Code · web-shell visuals

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

🩺 serve daemon A/B

Built the PR base vs this PR head 8b3d0b0, drove a fixed endpoint set against each, and diffed the JSON responses. Only fields that changed are shown.

No response changes against the PR base across 12 scenario(s).

Qwen Code · serve A/B

@yiliang114

Copy link
Copy Markdown
Collaborator Author

Cross-linking the plan: #10909 is the document this PR is phase ① of, and it names this exact step.

Its §6.1 (「迁移写法(已确定)」) records the resolution chain and then says the wildcard alias has to come first — "vitest alias:目前只有 4 个具名 subpath …… 没有通配。深路径导入今天在测试里会解析失败,必须先补一条通配 alias,这是 phase ① 的第一步". This PR is that step. It also confirms the §6.1 table under vitest rather than only from the tsconfig rule: ${CORE_SRC}/$1 resolves to packages/core/src/*, so the test chain lands on core's TypeScript sources and never on dist/ — which is the property §6.2 shows is load-bearing, since a /dist/... specifier misses the paths rule, falls back to exports, and produces two copies of the same module in the bundle.

I checked this PR against §6.3, the plan's largest correctness risk, and it is clean — but the reason is worth writing down.

§6.3's failure mode is that a test which mocks the core barrel silently stops intercepting once the code under test imports deeply: the suite stays green while testing something else. On this branch 138 cli test files carry vi.mock('@qwen-code/qwen-code-core', …). Of those, exactly one also references either migrated module:

  • packages/cli/src/ui/opentui/opentui-runtime.test.ts — mocks the barrel at line 54 (overriding createDebugLogger and writeRuntimeStatus) and references RemoteInputWatcher.

It is safe, and not by luck of ordering: line 42 replaces the whole ../../remoteInput/RemoteInputWatcher.js module with a stub class, so whether that module reaches createDebugLogger through the barrel or through a subpath never enters the test. The barrel mock there exists for opentui-runtime.ts's own imports, and this PR does not touch that file. The two mocks are disjoint.

The general rule that follows, for whoever writes the codemod: a barrel mock only breaks when the migrated module is the one the mock was meant to intercept through. A module that is itself stubbed is inert regardless of how it imports. That distinction is what makes the 138 tractable — the scope is not "138 files to audit" but "the subset whose barrel mock is intended to reach into a migrated module", and it should be computed per migration batch rather than up front.

Why the payoff is worth pushing on. #10909's collect > tests evidence comes from release run 33713579913. The same signature hit #10910's unit lane today (job 100630658328, ecs-qwen-hk3-16), cancelled at the 120-minute cap:

Duration 5013.58s (transform 549.37s, setup 1235.22s, collect 17994.55s, tests 6333.43s, ...)

collect at 2.8x tests, packages/cli alone taking 83 of the 120 minutes, and both of that run's two failures import-time rather than assertion-time (voice-keyterms-race.test.ts timed out at 20s inside a beforeAll whose only statement is a dynamic import). The host was not short of anything but CPU — DFSAMPLE showed load ~290 with 114–187 concurrent vitest processes on 128 cores, ~160 GB memory free, disk at 41% — and collect is the phase that starves. Raising caps, which is what #10915 and #10931 do, keeps those runs alive; this PR is the one that makes them cheaper.

Refs #10908, #10909.

中文说明

互相链接一下计划侧:#10909 就是本 PR 所属的那份文档的 phase ①,而它点名了这一步。

其 §6.1(「迁移写法(已确定)」)记录了解析链,然后说明通配 alias 必须先做 —— 「vitest alias:目前只有 4 个具名 subpath …… 没有通配。深路径导入今天在测试里会解析失败,必须先补一条通配 alias,这是 phase ① 的第一步」。本 PR 就是这一步。它同时在 vitest 下印证了 §6.1 的表格,而不只是从 tsconfig 规则推导:${CORE_SRC}/$1 解析到 packages/core/src/*,因此测试链落在 core 的 TypeScript 源码上、永不落到 dist/ —— 而这正是 §6.2 证明为关键的性质,因为 /dist/... 说明符匹配不到 paths 规则,会回落到 exports,从而在 bundle 里产生同一模块的两份副本。

我拿本 PR 对着 §6.3(该计划最大的正确性风险)核对过,结论是干净的 —— 但原因值得写下来。

§6.3 的失效模式是:一旦被测代码改成深路径导入,mock 了 core barrel 的测试就会静默失去拦截,套件仍然是绿的,但测的东西变了。在本分支上,有 138 个 cli 测试文件带 vi.mock('@qwen-code/qwen-code-core', …)。其中恰好只有一个同时引用了两个迁移模块之一:

  • packages/cli/src/ui/opentui/opentui-runtime.test.ts —— 在第 54 行 mock 了 barrel(覆盖 createDebugLoggerwriteRuntimeStatus),并且引用了 RemoteInputWatcher

它是安全的,而且不是靠顺序上的巧合:第 42 行把 ../../remoteInput/RemoteInputWatcher.js 整个模块替换成了一个 stub class,因此该模块究竟是经 barrel 还是经 subpath 拿到 createDebugLogger,根本不进入这个测试。那里的 barrel mock 是为 opentui-runtime.ts 自己的导入而存在的,而本 PR 没有触碰那个文件。两个 mock 互不相交。

由此得出的一般规则,供写 codemod 的人参考:barrel mock 只有在「被迁移的模块正是该 mock 意图经由其进行拦截的那个模块」时才会失效。 一个自身已被整体 stub 的模块,无论怎么导入都是惰性的。正是这个区分让 138 这个数字变得可处理 —— 范围不是「138 个文件要审」,而是「其 barrel mock 意图伸进某个被迁移模块的那个子集」,并且应当按每一批迁移分别计算,而不是一次性预估。

为什么这个收益值得推进。 #10909collect > tests 证据来自 release run 33713579913。同样的特征今天出现在 #10910 的单测通道(job 100630658328ecs-qwen-hk3-16),该 job 在 120 分钟上限被取消:

Duration 5013.58s (transform 549.37s, setup 1235.22s, collect 17994.55s, tests 6333.43s, ...)

collecttests2.8 倍packages/cli 单独吃掉 120 分钟里的 83 分钟,而那次运行的两个失败都发生在导入期而非断言期(voice-keyterms-race.test.ts 在一个唯一语句是动态 importbeforeAll 里 20 秒超时)。那台主机除了 CPU 之外什么都不缺 —— DFSAMPLE 显示 128 核上 load 约 290、并发 114–187 个 vitest 进程,内存空闲约 160 GB,磁盘 41% —— 而被饿死的正是 collect 阶段。抬高上限(#10915#10931 做的事)能让这些运行活下来;而本 PR 是让它们变便宜的那一个。

Refs #10908#10909

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.

3 participants