test(core): cover bundled ConPTY fallback and terminal replies - #11555
Conversation
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
✅ Qwen Triage finished — CI landed green on ✅ Qwen Triage 已完成 —— |
|
Thanks for the PR! Template looks good ✓ Problem: observed, not theoretical — and I could confirm the gap statically. The Direction: aligned. Follow-up hardening on #11497 and #11313 (both merged) for #11303 (still open), so the area's direction is already settled and there is no product question here. I skipped the external CHANGELOG signal — it is not probative for internal test quality on an already-shipped Windows fix. Size: core paths are touched ( Approach: the scope feels right and the two tests pair correctly with what was already there. Two things worth thinking about, neither a blocker:
Risk: Stage 1e matched — Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题: 是已观测到的问题,不是理论性加固——而且我从代码上静态确认了这个缺口。 方向: 对齐。这是 #11497 与 #11313(均已合入)针对 #11303(仍然 open)的后续加固,方向已经确定,不存在产品取向问题。我跳过了外部 CHANGELOG 信号——对一个已上线 Windows 修复的内部测试质量改动,它没有参考价值。 规模: 触及核心路径( 方案: 范围合理,两个新测试与已有测试形成了正确的配对。有两点值得考虑,都不是阻塞项:
风险: Stage 1e 命中—— 进入代码审查 🔍 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
Code reviewBefore reading the diff I wrote down what I'd do from the description alone: add a win32 case where the PTY spawns fine and a later step throws, asserting The new fallback test throws where it claims to, and its cleanup is load-bearing. Tracing the No cross-test pollution from the getter. This was my main isolation worry: the top-level The DA test now traverses xterm's real parser and the real render queue. Dropping The production change is behavior-preserving and I can name every consumer. The corrected comment agrees with what already shipped. Suggestions (non-blocking)
sequenceDiagram
participant P1 as Test simulation
participant P2 as PTY onData handler
participant P3 as handleOutput
participant P4 as processingChain
participant P5 as headless xterm Terminal
participant P6 as query reply forwarder
P1->>P2: feed a DA1 query as PTY output
P2->>P3: handleOutput(buffer)
P3->>P4: enqueue the render step
P4->>P5: headlessTerminal.write(chunk)
P5->>P6: onData emits the generated reply
P6->>P2: ptyProcess.write(reply)
P1->>P1: vi.waitFor asserts the reply reached the PTY
TestingThis is an unattended CI run, so no PR code was built, run, or checked out — the gate is static. The evidence below is the PR's own CI, read through the API for the reviewed commit. I did not re-fetch after the initial read and did not poll; the finalize job rewrites the table once CI settles. Green so far:
Not verified: the literal Not verified: the mutation results in the description. Those are the author's claim, not something I re-ran, and the gate cannot execute PR code to check them. Sandboxed verification would settle this: Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 中文说明代码审查在读 diff 之前,我先只根据描述写下了自己会怎么做:加一个 win32 用例,让 PTY 正常 spawn、但之后的某一步抛错,断言 新的 fallback 测试确实在它声称的位置抛错,而且它的清理是必需的。 追踪 getter 不会污染其他测试。 这是我最担心的隔离问题:顶层 DA 测试现在真正穿过了 xterm 的 parser 和真实的渲染队列。 去掉 生产代码改动保持行为不变,而且我能点名每一个消费方。 修正后的注释与已经合入的代码一致。 Suggestion(非阻塞)
测试这是一次无人值守的 CI 运行,因此没有构建、运行或 checkout 任何 PR 代码——审查是静态的。下面的证据来自 PR 自身的 CI,通过 API 读取所审查的那个 commit。我只读取了一次、没有轮询;CI 稳定后 finalize 任务会就地重写这张表。 目前已绿:
未验证:字面量 未验证:描述里的 mutation 结果。那是作者的声明,不是我重新跑出来的,而且审查门禁不能执行 PR 代码来核对。 沙箱验证可以解决这一点: — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
|
Confidence: 4/5 — the coverage gap was real and I could confirm it statically; the two Suggestions are about comment content and test legibility, not correctness. Going back to what I proposed before reading the diff: this matches it, and beats it in one place. I would not have thought to clean up On the question the gate is supposed to ask first — does the problem exist — I'm satisfied, and not on the PR's word. Before this change the What I can't confirm is that the hardening holds. A green suite is structurally incapable of showing mutation-sensitivity — it passes the same way with the guard in place, which is the only state CI ever runs. The mutation numbers in the description are the author's claim and I did not re-run them, and I could not check the In six months I'd thank whoever wrote this rather than curse them. The three edits in the test helper, the DA test, and the simulation signature are causally linked and each one is forced by the one before, which is what a small honest diff looks like. My two reservations stay where I put them in Stage 2: the injection point is positional with nothing documenting it, and the rewritten comment gave up two true statements (what
中文说明Confidence: 4/5 —— 覆盖缺口是真实存在的,而且我能静态确认;两条 Suggestion 针对的是注释内容和测试可读性,不是正确性。 回到我在读 diff 之前写下的方案:这个 PR 与它一致,并且在一处做得更好。我本来想不到要在 关于门禁最该先问的问题——问题是否真实存在——我是信服的,而且不是凭 PR 的说法。在这次改动之前, 我无法确认的是这份加固是否真的有效。绿色的套件在结构上就无法证明 mutation 敏感性——在守卫存在时它同样通过,而那正是 CI 唯一会运行的状态。描述里的 mutation 数据是作者的声明,我没有重跑; 六个月后我会感谢写这段代码的人,而不是埋怨他。测试 helper、DA 测试和 simulation 签名这三处改动是因果相连的,每一处都是被前一处逼出来的——这就是一个小而诚实的 diff 该有的样子。我的两点保留意见仍放在 Stage 2:注入点是位置性的却没有任何说明;重写后的注释放弃了两句本来正确的话(
— Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship — CI landed green after the review. ✅
chiga0
left a comment
There was a problem hiding this comment.
No blocking findings.
Approval blockers: none.
Triage: Standard — test hardening + comment correction with a small source refactoring.
What I checked:
-
Single source of truth for Windows bundled ConPTY decision:
const useBundledConpty = os.platform() === 'win32'extracted once, replacing three separateos.platform() === 'win32'checks. The three consumers (useConptyDll option, terminal query reply forwarding, post-spawn fallback guard) are causally linked — they must agree on whether bundled ConPTY is in use. A local variable at method top prevents drift. The variable is named after its semantic meaning, not the platform it tests. -
Comment accuracy for
useConptyDll: Updated to name the actual mechanism (ConptyReleasePseudoConsole), correct the process census (OpenConsole.exeis what bundled ConPTY launches, notconhost.exe), and provide guidance ("count bothconhost.exeandOpenConsole.exeor all attributable children"). Removed specific #11303 measurements (version numbers, before/after counts) that belong in that issue's thread. -
New fallback test (
does not run the fallback after the PTY has already spawned):- Uses a getter on
pidthat succeeds on first read (foractivePtys.setat line ~1570) but throws on second read (thereturn { pid: ptyProcess.pid, result }after the try/catch). - This triggers the catch block with
ptySpawned === true, exercising the else-branch that returnsexecutionMethod: 'none'. - Verifies:
mockPtySpawncalled once,mockCpSpawnNOT called (no double-execution), error message propagated. finallyblock cleans upactivePtys.delete(12345)— the PTY was registered before the throw.- The PR description confirms mutation: removing the post-spawn guard makes this test fail with one
child_processspawn.
- Uses a getter on
-
Rewritten terminal reply test (
writes xterm's device-attributes reply back to the PTY on Windows):- No longer uses
ReplyCapturingTerminal(removedmockLoadXtermHeadless.mockResolvedValueOnce). Uses the real@xterm/headlessTerminal. - Sends
\x1b[c(CSI c — Device Attributes query) viapty.onData, waits for\x1b[?1;2c(the DA reply xterm actually generates) to be written back viaptyProcess.write. vi.waitForhandles the async parser→forwarder→write chain.- The old test supplied
\x1b[?64;1;22cdirectly to the listener — a response xterm does not actually generate. The new test catches parser/protocol integration regressions. - PR description confirms mutation: disabling the forwarder makes this test fail with zero PTY writes.
- No longer uses
-
simulateExecutionasync callback support:simulation: (pty, ac) => void | Promise<void>+await simulation(...). Backwards-compatible — existing sync callbacks still work. Required by the new terminal reply test'svi.waitFor. -
Cross-check:
qwen-code-ci-botapproved. No prior review from me. Follows up on #11497 and #11313 (the ConPTY host lifecycle work reviewed in prior rounds).
Unreviewed dimensions: actual Windows runtime behavior (no Windows host available; PR description marks Windows as
Reviewed with AI assistance.
Runtime verification — PR 11555I rebuilt this locally as a real runtime environment (not a re-read of the diff) and ran the PR head ( Environment: Linux x86_64, Node 22.22.2, Vitest 3.2.7, Summary
1. Mutation matrix — teeth, and the gap on mainEach cell re-runs the whole
2. What the post-spawn guard is actually worthReal PTY execution of With the marker present: 3. The reply forwarder, end to end on a real PTYThe Windows-only gate is the only platform-specific part of the mechanism, so I forced Forwarder off: the child times out after 2005 ms (2063 ms wall). Forwarder on: it gets 4. The two corrected comment claims
5. Regression surface
Optional follow-ups (non-blocking)(a) The platform half of the same guard is still uncovered. Mutating (b) A post-spawn failure orphans the child it just started. Probing the same injected failure with (c) Cosmetic: the new test's Not verifiedReal Windows runtime: no Windows host here, so packaged Windows execution and an actual 中文版本(点击展开)本地运行时验证 — PR 11555我在本地搭建了真实运行环境(不是重读 diff),把 PR head( 环境: Linux x86_64、Node 22.22.2、Vitest 3.2.7、 结论汇总
1. Mutation 矩阵 —— 测试有没有牙齿,main 上的缺口是否真实每一格都是把生产代码做定点变异后,重跑整套
2. post-spawn 守卫到底价值几何真实 PTY 执行 3. 真实 PTY 上的端到端应答链路该机制中唯一与平台相关的只有那个 Windows 判断,因此我在编译产物里强制打开 4. 两条被纠正的注释
5. 回归面
可选后续(不阻塞)(a) 同一守卫的平台判断部分仍未被覆盖。 把 (b) post-spawn 失败会遗留刚启动的子进程。 用 (c) 细节: 新测试的 未验证部分真实 Windows 运行时:本机没有 Windows 环境,因此打包后的 Windows 执行以及真实的 |
|
Released in v0.23.3. |




What this PR does
Closes the focused test and documentation gaps left after #11497 and #11313 landed. The Windows bundled-ConPTY decision is now shared by backend selection, terminal-reply forwarding, and the pre-spawn fallback guard. The fallback coverage proves that a command is never handed to
child_processafter its PTY has already started, and the reply coverage now sends a real device-attributes query through@xterm/headlessinstead of manually invoking the forwarding listener. The ConPTY explanation also names the actualConptyReleasePseudoConsolemechanism and the correct Windows host census.Why it's needed
Before this change, removing the post-spawn guard still left all 152 tests green even though the command could execute once through PTY and then again through the fallback. The terminal-reply test also supplied a synthetic response directly to the listener, so it could not detect a parser/protocol integration regression and asserted a response that the installed xterm version does not generate. Finally, the source comment cited a
conhost.exe-only before/after count even though bundled ConPTY normally launchesOpenConsole.exe.Reviewer Test Plan
How to verify
Confirm that a Windows PTY which spawns successfully and then hits a synchronous handle-construction error returns
executionMethod: 'none'without invoking the fallback. FeedCSI cthrough the real headless terminal and confirm that its generatedCSI ? 1 ; 2 creply is written back to the PTY. The focused suite passes 153/153 tests. Mutation checks independently confirmed that removing the post-spawn marker makes the fallback test fail with onechild_processspawn, while disabling the Windows reply forwarder makes the real-parser test fail with zero PTY writes.Evidence (Before & After)
N/A — test hardening and comment correction only; no user-visible UI change.
Tested on
Environment (optional)
macOS arm64, Node 22, Vitest 3.2.7, and
@xterm/headless5.5.0. The focused suite, core typecheck, core build, targeted ESLint, and Prettier checks passed.Risk & Scope
conhost.exe/OpenConsole.exeprocess counts, and the console-attached-survivor behavior called out in fix(core): host Windows shell PTYs via bundled ConPTY to stop orphaning conhost.exe #11497's verification comment.Linked Issues
Follow-up to #11497 and #11313. Refs #11303.
中文说明
这个 PR 做了什么
补齐 #11497 和 #11313 合入后留下的定向测试与文档缺口。Windows bundled ConPTY 的判断现在由后端选择、终端应答转发和 spawn 前回退守卫共同复用。回退测试证明 PTY 已经启动后,命令绝不会再次交给
child_process;应答测试则通过真实@xterm/headless发送 device-attributes 查询,不再手工调用转发 listener。ConPTY 注释也改为说明实际的ConptyReleasePseudoConsole机制和正确的 Windows host 统计口径。为什么需要
修改前,即使删除 post-spawn 守卫,原有 152 个测试仍全部通过,但命令可能先经 PTY 执行一次,再经 fallback 重复执行。原来的终端应答测试也只是把合成响应直接交给 listener,无法发现 parser/protocol 集成回归,而且断言的响应并不是当前 xterm 版本实际生成的值。源码注释还引用了只统计
conhost.exe的前后对比,但 bundled ConPTY 通常启动的是OpenConsole.exe。审阅者测试计划
如何验证
确认 Windows PTY 成功 spawn 后,如果构造返回 handle 时同步失败,会返回
executionMethod: 'none',且不会调用 fallback。把CSI c送入真实 headless terminal,确认它生成的CSI ? 1 ; 2 c应答被写回 PTY。定向测试 153/153 通过。独立 mutation 验证表明:删除 post-spawn 标记后,fallback 测试会因出现一次child_processspawn 而失败;关闭 Windows 应答转发后,真实 parser 测试会因 PTY write 次数为 0 而失败。证据(改动前后)
N/A——仅测试加固和注释纠正,没有用户可见的 UI 变化。
测试环境
环境(可选)
macOS arm64、Node 22、Vitest 3.2.7、
@xterm/headless5.5.0。定向测试、core typecheck、core build、定向 ESLint 和 Prettier 均通过。风险与范围
conhost.exe/OpenConsole.exe进程计数,以及 fix(core): host Windows shell PTYs via bundled ConPTY to stop orphaning conhost.exe #11497 验证评论指出的 console-attached survivor 行为。关联 Issue
#11497 与 #11313 的后续。Refs #11303。