diff --git a/packages/bash/bash-local/tests/executor.spec.ts b/packages/bash/bash-local/tests/executor.spec.ts index a09d8bb72c..c5cce5e6e6 100644 --- a/packages/bash/bash-local/tests/executor.spec.ts +++ b/packages/bash/bash-local/tests/executor.spec.ts @@ -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') diff --git a/packages/bash/bash-local/tests/run.spec.ts b/packages/bash/bash-local/tests/run.spec.ts index 214d043cb6..4f06fefadc 100644 --- a/packages/bash/bash-local/tests/run.spec.ts +++ b/packages/bash/bash-local/tests/run.spec.ts @@ -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 { +async function waitForPidFile(path: string, timeoutMs = 5_000): Promise { 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()