Merge remote-tracking branch 'origin/master' into codex/ask-user-question

# Conflicts:
#	packages/bash/bash-local/tests/executor.spec.ts
#	packages/bash/bash-local/tests/run.spec.ts
This commit is contained in:
Yichen Jiang
2026-07-09 00:48:45 +08:00
2 changed files with 13 additions and 9 deletions

View File

@@ -92,8 +92,8 @@ describe('LocalBashExecutor.run', () => {
it('kill escalation uses the configured graceMs (a TERM-trapping task dies by SIGKILL)', async () => {
const { bash } = await setup() // setup pins graceMs: 200 via config
const task = bash.start(bash.resolve({ command: 'trap \'\' TERM; echo trap-ready; sleep 60' }))
await readUntil(bash, task.id, 'trap-ready')
const task = bash.start(bash.resolve({ command: 'trap \'\' TERM; echo ready; while :; do sleep 60 & wait $!; done' }))
await readUntil(bash, task.id, 'ready\n')
bash.kill(task.id)
await task.done
expect(task.signal).toBe('SIGKILL')

View File

@@ -1,4 +1,4 @@
import { existsSync, mkdtempSync, readFileSync, statSync } from 'node:fs'
import { mkdtempSync, readFileSync, statSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { describe, expect, it, vi } from 'vitest'
@@ -56,13 +56,18 @@ async function waitForStdout(running: RunningBash, expected: string, timeoutMs =
throw new Error(`stdout did not include ${JSON.stringify(expected)} after ${timeoutMs}ms`)
}
async function waitForFile(file: string, timeoutMs = 5_000): Promise<void> {
async function waitForPidFile(path: string, timeoutMs = 5_000): Promise<number> {
const deadline = Date.now() + timeoutMs
while (Date.now() < deadline) {
if (existsSync(file)) return
try {
const pid = Number(readFileSync(path, 'utf8').trim())
if (Number.isSafeInteger(pid) && pid > 0) return pid
} catch {
// The child shell has not written the pid file yet.
}
await new Promise(resolve => setTimeout(resolve, 20))
}
throw new Error(`${file} did not exist after ${timeoutMs}ms`)
throw new Error(`pid file ${path} was not written after ${timeoutMs}ms`)
}
describe('runBash', () => {
@@ -116,7 +121,7 @@ describe('runBash', () => {
})
it('escalates to SIGKILL when SIGTERM is trapped', async () => {
const running = runBash(spec('trap \'\' TERM; echo ready; sleep 60', { graceMs: 200 }))
const running = runBash(spec('trap \'\' TERM; echo ready; while :; do sleep 60 & wait $!; done', { graceMs: 200 }))
await waitForStdout(running, 'ready\n')
running.kill()
const result = await running.done
@@ -128,8 +133,7 @@ describe('runBash', () => {
// group must take the sleep down with bash.
const pidFile = join(spillDir, `grandchild-${Date.now()}.pid`)
const running = runBash(spec(`sleep 60 & echo $! > ${pidFile}; wait`))
await waitForFile(pidFile)
const grandchild = Number(readFileSync(pidFile, 'utf8').trim())
const grandchild = await waitForPidFile(pidFile)
expect(grandchild).toBeGreaterThan(0)
running.kill()