Merge remote-tracking branch 'origin/master' into fix/pty-raw-ready-wait
master already stabilized the raw-mode SIGINT scenario in 65e0ffe03 and 3be090f1d with wider scenario-owned bounds and deliberately delayed child readiness, so the merge takes master's test implementation. What this branch keeps is the contract that explains why those bounds are required: the note paragraph on the settle boundary in PtySendOperation.append, and the waitForOutput comment recording that raising its deadline alone cannot recover output the send never collected.
This commit is contained in:
@@ -41,7 +41,7 @@ function stubAgent(ctx: Context, rawId: string): Agent {
|
||||
|
||||
async function harness(
|
||||
mode: 'danger-full-access' | 'workspace-write',
|
||||
overrides: { idleSilenceMs?: number; timeoutMs?: number } = {},
|
||||
timing: { idleSilenceMs?: number; timeoutMs?: number } = {},
|
||||
) {
|
||||
const root = mkdtempSync(join(tmpdir(), 'dsh-pty-local-'))
|
||||
roots.push(root)
|
||||
@@ -54,8 +54,8 @@ async function harness(
|
||||
const fiber = await ctx.plugin(ptyLocal, {
|
||||
pollIntervalMs: 10,
|
||||
exactProbeAfterMs: 20,
|
||||
idleSilenceMs: overrides.idleSilenceMs ?? 250,
|
||||
timeoutMs: overrides.timeoutMs ?? 2_000,
|
||||
idleSilenceMs: timing.idleSilenceMs ?? 250,
|
||||
timeoutMs: timing.timeoutMs ?? 2_000,
|
||||
disposeGraceMs: 500,
|
||||
scrollbackLines: 100,
|
||||
scrollbackMaxBytes: 32_768,
|
||||
@@ -67,9 +67,11 @@ async function harness(
|
||||
}
|
||||
|
||||
// PtySendOperation.append drops output once the operation settles, so this only
|
||||
// observes a marker the child prints while the send is still active.
|
||||
async function waitForOutput(operation: PtySendOperation, expected: string): Promise<void> {
|
||||
const deadline = Date.now() + 5_000
|
||||
// observes a marker the child prints while the send is still active. A caller
|
||||
// whose child is slow to print must raise the harness `timing` bounds too;
|
||||
// extending this deadline alone cannot recover output the send never collected.
|
||||
async function waitForOutput(operation: PtySendOperation, expected: string, timeoutMs = 2_000): Promise<void> {
|
||||
const deadline = Date.now() + timeoutMs
|
||||
let output = ''
|
||||
while (!output.includes(expected) && Date.now() < deadline) {
|
||||
output += operation.readOutput().delta
|
||||
@@ -136,26 +138,25 @@ describe('pty-local real shell', () => {
|
||||
expect(() => process.kill(pid, 0)).toThrow()
|
||||
}, 10_000)
|
||||
|
||||
it('cancels a raw-mode foreground process with a real SIGINT', async () => {
|
||||
// A cold `python3` start can stay silent for longer than the 250 ms default
|
||||
// this harness uses, which settles the send as inferred_idle before the
|
||||
// interpreter prints its readiness marker; the marker then reaches only the
|
||||
// scrollback and waitForOutput sees the echoed command line alone. Raise the
|
||||
// silence bound, and the absolute bound above it, so process startup cannot
|
||||
// end the send it belongs to.
|
||||
const { ctx, agent } = await harness('danger-full-access', { idleSilenceMs: 4_000, timeoutMs: 6_000 })
|
||||
it('cancels a slow-starting raw-mode foreground process with a real SIGINT', async () => {
|
||||
const { ctx, agent } = await harness('danger-full-access', {
|
||||
idleSilenceMs: 10_000,
|
||||
timeoutMs: 15_000,
|
||||
})
|
||||
const created = await ctx.pty.spawn(agent, { type: 'shell' })
|
||||
const controller = new AbortController()
|
||||
const ready = 'RAW_READY'
|
||||
// Delay readiness beyond the shared harness's short send bound so this
|
||||
// process test owns enough slack for loaded macOS startup and shell echo.
|
||||
// The interactive shell echoes the command, so only child output may contain the readiness marker.
|
||||
const command = 'python3 -c \'import signal,sys,termios,time; signal.signal(signal.SIGINT, lambda *_: (print("SIGINT_SEEN", flush=True), sys.exit(0))); attrs=termios.tcgetattr(0); attrs[3] &= ~termios.ISIG; termios.tcsetattr(0, termios.TCSANOW, attrs); print("RAW_" + "READY", flush=True); time.sleep(60)\''
|
||||
const command = 'python3 -c \'import signal,sys,termios,time; signal.signal(signal.SIGINT, lambda *_: (print("SIGINT_SEEN", flush=True), sys.exit(0))); attrs=termios.tcgetattr(0); attrs[3] &= ~termios.ISIG; termios.tcsetattr(0, termios.TCSANOW, attrs); time.sleep(2.1); print("RAW_" + "READY", flush=True); time.sleep(60)\''
|
||||
expect(command).not.toContain(ready)
|
||||
const foreground = ctx.pty.startSend(agent, created.sessionId, {
|
||||
text: command,
|
||||
submit: true,
|
||||
signal: controller.signal,
|
||||
})
|
||||
await waitForOutput(foreground, ready)
|
||||
await waitForOutput(foreground, ready, 15_000)
|
||||
controller.abort()
|
||||
const result = await foreground.done
|
||||
expect(result.waitReason).toBe('stdin_read')
|
||||
|
||||
Reference in New Issue
Block a user