Isolate per-command output from background procs
This commit is contained in:
parent
38b02ea21c
commit
481807255a
|
|
@ -62,6 +62,24 @@ describe("runFile", () => {
|
|||
}
|
||||
})
|
||||
|
||||
test("background process output does not leak into subsequent commands", async () => {
|
||||
const file = makeFile([
|
||||
// Start a background process that writes to stdout after a delay
|
||||
{ command: "{ sleep 0.1; echo LEAKED; } &" },
|
||||
// Wait long enough for the background output to appear
|
||||
{ command: "sleep 0.3; echo clean" },
|
||||
])
|
||||
const result = await runFile(file, defaultOpts)
|
||||
try {
|
||||
// The background command itself should have no output
|
||||
expect(result.results[0]?.actual).toEqual([])
|
||||
// The sleep command should only see its own output, not the background "LEAKED"
|
||||
expect(result.results[1]?.actual).toEqual(["clean"])
|
||||
} finally {
|
||||
await cleanupTmpDir(result.tmpDir)
|
||||
}
|
||||
})
|
||||
|
||||
test("cleans up multiple backgrounded processes", async () => {
|
||||
const file = makeFile([
|
||||
{ command: "sleep 300 >/dev/null 2>&1 & P1=$!; sleep 300 >/dev/null 2>&1 & P2=$!; echo $P1 $P2" },
|
||||
|
|
|
|||
17
src/run.ts
17
src/run.ts
|
|
@ -58,9 +58,10 @@ function buildScript(commands: Command[], sentinel: string, verbose: boolean): s
|
|||
|
||||
if (verbose) {
|
||||
// Save original stderr to fd 3 before merging stderr into stdout
|
||||
lines.push("exec 3>&2 2>&1")
|
||||
// Save the pipe on fd 9 so we can restore after each command
|
||||
lines.push("exec 3>&2 2>&1 9>&1")
|
||||
} else {
|
||||
lines.push("exec 2>&1")
|
||||
lines.push("exec 2>&1 9>&1")
|
||||
}
|
||||
|
||||
for (let i = 0; i < commands.length; i++) {
|
||||
|
|
@ -68,11 +69,21 @@ function buildScript(commands: Command[], sentinel: string, verbose: boolean): s
|
|||
if (verbose) {
|
||||
lines.push(`printf '${VERBOSE_MARKER}${i}\\n' >&3`)
|
||||
}
|
||||
// Redirect stdout+stderr to a temp file so background processes
|
||||
// from previous commands can't pollute this command's output.
|
||||
// Background processes keep their fd pointing at the old temp file,
|
||||
// which becomes orphaned after rm — their output goes nowhere.
|
||||
lines.push(`__shout_out=$(mktemp)`)
|
||||
lines.push(`exec 1>"$__shout_out" 2>&1`)
|
||||
lines.push(cmd.command)
|
||||
lines.push(`__shout_ec=$?`)
|
||||
lines.push(`exec 1>&9 2>&1`)
|
||||
lines.push(`cat "$__shout_out"`)
|
||||
lines.push(`rm -f "$__shout_out"`)
|
||||
// Sentinel: printf to avoid echo interpretation issues
|
||||
// Format: __SHOUT_SENTINEL_<exitcode>_<index>__
|
||||
lines.push(
|
||||
`printf '\\n${sentinel}%s_${i}__\\n' "$?"`,
|
||||
`printf '\\n${sentinel}%s_${i}__\\n' "$__shout_ec"`,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user