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 () => {
|
test("cleans up multiple backgrounded processes", async () => {
|
||||||
const file = makeFile([
|
const file = makeFile([
|
||||||
{ command: "sleep 300 >/dev/null 2>&1 & P1=$!; sleep 300 >/dev/null 2>&1 & P2=$!; echo $P1 $P2" },
|
{ 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) {
|
if (verbose) {
|
||||||
// Save original stderr to fd 3 before merging stderr into stdout
|
// 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 {
|
} else {
|
||||||
lines.push("exec 2>&1")
|
lines.push("exec 2>&1 9>&1")
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < commands.length; i++) {
|
for (let i = 0; i < commands.length; i++) {
|
||||||
|
|
@ -68,11 +69,21 @@ function buildScript(commands: Command[], sentinel: string, verbose: boolean): s
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
lines.push(`printf '${VERBOSE_MARKER}${i}\\n' >&3`)
|
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(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
|
// Sentinel: printf to avoid echo interpretation issues
|
||||||
// Format: __SHOUT_SENTINEL_<exitcode>_<index>__
|
// Format: __SHOUT_SENTINEL_<exitcode>_<index>__
|
||||||
lines.push(
|
lines.push(
|
||||||
`printf '\\n${sentinel}%s_${i}__\\n' "$?"`,
|
`printf '\\n${sentinel}%s_${i}__\\n' "$__shout_ec"`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user