test(pty): cover a torn color query in the OSC 11 before CPR order pin

The pin drove both queries in one write, so xterm never had to assemble the
color query across chunks. Tear it mid-sequence and the OSC reply is produced
by the same chunk that carries CPR, which is the ordering the single-write
case cannot reach. Both cases invert under a held echo-safe lane.
This commit is contained in:
Brennan Benson
2026-08-28 04:36:44 -07:00
parent 59ee162ba7
commit 6e356c145c
2 changed files with 56 additions and 46 deletions
+2 -2
View File
@@ -13674,7 +13674,7 @@
"https://github.com/stablyai/orca/issues/16672"
],
"invariant": "The desktop PTY input queue retains at most 64 explicitly sourced pending terminal query replies and 4096 UTF-16 code units. Every retained reply reaches the provider as one atomic write, and the host writes each reply the moment it accepts it, so replies reach the PTY in the order they were produced with no queue that could reorder them. A reply's own echo is contained on the output side by projecting its known echo shapes; the ESC-initial verbatim shape is matched only when complete, never held as a partial, so a query torn at its own ESC is still answered. Overflow removes only the oldest query replies, never ordinary input except the documented modified-F3/CPR byte collision, and drain failures cannot clear a newer queue generation.",
"oracle": "Synchronously enqueue separate 10,000-entry OSC and DA1 reply floods before the scheduled drain and assert that only the initial immediate reply and newest 64 pending replies are written, each as one provider write, before a trailing keystroke. Derive the replies to OSC 11 followed by CSI 6n from real xterm and assert the PTY receives the exact OSC response followed by the CPR; preserve the same order for separate or legacy-coalesced host payloads. At the remote-runtime boundary, preserve separate writes around pending ordinary input, async validation, and viewport-claim buffering. Repeat behind 10,000 ordinary inputs and exercise the text ceiling, real xterm generation, provider-write failure, rejected yield, and clear/reuse generation fencing.",
"oracle": "Synchronously enqueue separate 10,000-entry OSC and DA1 reply floods before the scheduled drain and assert that only the initial immediate reply and newest 64 pending replies are written, each as one provider write, before a trailing keystroke. Derive the replies to OSC 11 followed by CSI 6n from real xterm, both in one write and with the color query torn across output chunks, and assert the PTY receives the exact OSC response followed by the CPR; preserve the same order for separate or legacy-coalesced host payloads. At the remote-runtime boundary, preserve separate writes around pending ordinary input, async validation, and viewport-claim buffering. Repeat behind 10,000 ordinary inputs and exercise the text ceiling, real xterm generation, provider-write failure, rejected yield, and clear/reuse generation fencing.",
"commands": [
"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/pty-input-write-queue.test.ts src/renderer/src/components/terminal-pane/pty-transport-input-write.test.ts src/shared/terminal-query-reply.test.ts src/shared/pty-startup-ingress-live-query-reply.test.ts src/shared/pty-startup-reply-echo-shapes.test.ts",
"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/remote-runtime-pty-batching.test.ts src/renderer/src/components/terminal-pane/remote-runtime-pty-query-reply-immediate.test.ts src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-input-coalescing.test.ts",
@@ -13705,7 +13705,7 @@
"reply count and text overflow remove only the oldest pending replies",
"real xterm OSC 10/11 query handlers remain subject to the same retention ceiling",
"retained OSC, DA1, and CPR replies stay one provider write each and both OSC and DA1 floods remain bounded",
"a real xterm OSC 11 query followed by CSI 6n reaches the PTY as the derived OSC reply followed by the CPR",
"a real xterm OSC 11 query followed by CSI 6n reaches the PTY as the derived OSC reply followed by the CPR, including when the color query is torn across output chunks",
"provider-write and yield failures settle without unhandled rejection, repeated same-generation admission, or stale-generation clearing",
"clear releases saturated reply accounting and fences in-flight validation before later input"
]
@@ -343,53 +343,63 @@ describe('pty input write queue', () => {
}
})
it('keeps xterm OSC 11 ahead of the following CPR at the PTY host', async () => {
const term = new Terminal({ cols: 80, rows: 24, allowProposedApi: true })
term.options.theme = { background: '#00131a' }
const ptyWrites: string[] = []
const generatedReplies: string[] = []
const ingress = new PtyStartupIngress({
ownerBackend: 'posix-pty',
write: (data) => ptyWrites.push(data),
onEmission: () => {}
})
const queue = createPtyInputWriteQueue({
isWritable: () => true,
write: (_id, data) => {
if (ingress.answerLiveQueryReply(data)) {
return
it.each([
['one write', ['\x1b[7;1H\x1b]11;?\x1b\\\x1b[6n']],
// The color query is torn mid-sequence, so its reply is only produced by the chunk that
// also carries the CPR query — the ordering the single-write case cannot exercise.
['a query torn across output chunks', ['\x1b[7;1H\x1b]11;', '?\x1b\\\x1b[6n']]
] as const)(
'keeps xterm OSC 11 ahead of the following CPR at the PTY host (%s)',
async (_name, outputChunks) => {
const term = new Terminal({ cols: 80, rows: 24, allowProposedApi: true })
term.options.theme = { background: '#00131a' }
const ptyWrites: string[] = []
const generatedReplies: string[] = []
const ingress = new PtyStartupIngress({
ownerBackend: 'posix-pty',
write: (data) => ptyWrites.push(data),
onEmission: () => {}
})
const queue = createPtyInputWriteQueue({
isWritable: () => true,
write: (_id, data) => {
if (ingress.answerLiveQueryReply(data)) {
return
}
ptyWrites.push(data)
},
yieldBetweenWrites: () => Promise.resolve()
})
const enqueueReply = (data: string): boolean => {
generatedReplies.push(data)
return queue.enqueueQueryReply('pty-1', data)
}
const capabilityReplies = installTerminalCapabilityReplyHandlers({
terminal: term as never,
parser: term.parser,
sendInput: enqueueReply,
isReplaying: () => false
})
const xtermReplies = term.onData(enqueueReply)
try {
for (const chunk of outputChunks) {
await writeTerminal(term, chunk)
}
ptyWrites.push(data)
},
yieldBetweenWrites: () => Promise.resolve()
})
const enqueueReply = (data: string): boolean => {
generatedReplies.push(data)
return queue.enqueueQueryReply('pty-1', data)
}
const capabilityReplies = installTerminalCapabilityReplyHandlers({
terminal: term as never,
parser: term.parser,
sendInput: enqueueReply,
isReplaying: () => false
})
const xtermReplies = term.onData(enqueueReply)
await queue.waitForDrain()
try {
await writeTerminal(term, '\x1b[7;1H\x1b]11;?\x1b\\\x1b[6n')
await queue.waitForDrain()
const oscReply = '\x1b]11;rgb:0000/1313/1a1a\x1b\\'
const cprReply = '\x1b[7;1R'
expect(generatedReplies).toEqual([oscReply, cprReply])
expect(ptyWrites.join('')).toBe(oscReply + cprReply)
} finally {
xtermReplies.dispose()
capabilityReplies.dispose()
ingress.drainAndClose()
term.dispose()
const oscReply = '\x1b]11;rgb:0000/1313/1a1a\x1b\\'
const cprReply = '\x1b[7;1R'
expect(generatedReplies).toEqual([oscReply, cprReply])
expect(ptyWrites.join('')).toBe(oscReply + cprReply)
} finally {
xtermReplies.dispose()
capabilityReplies.dispose()
ingress.drainAndClose()
term.dispose()
}
}
})
)
it('rejects one reply larger than the total reply retention budget', async () => {
const { writes, queue } = createRecordingQueue()