Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ fi
`
} else {
sh += `\
${shProg} ${args} ${shTarget} ${progArgs}"$@"
exec ${shProg} ${args} ${shTarget} ${progArgs}"$@"
exit $?
`
}
Expand Down
34 changes: 33 additions & 1 deletion test/e2e.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { spawnSync } from 'node:child_process'
import { spawn, spawnSync } from 'node:child_process'
import fs from 'node:fs'
import path from 'node:path'
import { describe, test, snapshot } from 'node:test'
Expand All @@ -11,6 +11,7 @@ import tempy from 'tempy'
import { cmdShim } from '../index.js'

const describeOnWindows = process.platform === 'win32' ? describe : describe.skip
const describeOnPosix = process.platform === 'win32' ? describe.skip : describe

describeOnWindows('create a command shim for a .exe file', () => {
test('shim files', async (t) => {
Expand Down Expand Up @@ -57,3 +58,34 @@ describeOnWindows('sh shim wrapping a .cmd target invoked from Git Bash', () =>
)
})
})

describeOnPosix('sh shim binstub uses exec', () => {
// Regression for the binstub bug: without `exec`, the shell process
// wraps the wrapped binary, so signals sent to the shim do not reach
// the wrapped process and the binary's PID differs from the shim's
// spawn PID. With `exec`, the shell process is replaced in place and
// the wrapped binary inherits the shim's PID.
test('wrapped binary inherits the shim\'s PID (proves exec replaced the shell)', async () => {
const tempDir = tempy.directory()
// process.execPath is a no-shebang native binary, so cmdShim hits the
// non-shLongProg branch of generateShShim — the one that needs `exec`.
const shim = path.join(tempDir, 'shim')
await cmdShim(process.execPath, shim)

const proc = spawn('/bin/sh', [shim, '-p', 'process.pid'], {
stdio: ['ignore', 'pipe', 'pipe'],
})
const shimPid = proc.pid
let stdout = ''
proc.stdout.on('data', (chunk) => { stdout += chunk })
const exitCode = await new Promise((resolve) => proc.on('exit', resolve))

assert.equal(exitCode, 0, `shim exited ${exitCode}; stdout=${stdout}`)
const reportedPid = Number(stdout.trim())
assert.equal(
reportedPid,
shimPid,
`expected child to inherit shim PID via exec — got reported=${reportedPid}, shim=${shimPid}`
)
})
})
2 changes: 1 addition & 1 deletion test/e2e.test.js.snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ case \`uname\` in
;;
esac

"$basedir/foo" "$@"
exec "$basedir/foo" "$@"
exit $?

`;
Expand Down
6 changes: 3 additions & 3 deletions test/test.js.snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ case \`uname\` in
;;
esac

"/.pnpm/nodejs/16.0.0/node" "$basedir/src.env" "$@"
exec "/.pnpm/nodejs/16.0.0/node" "$basedir/src.env" "$@"
exit $?

`;
Expand Down Expand Up @@ -818,7 +818,7 @@ case \`uname\` in
;;
esac

"$basedir/src.exe" "$@"
exec "$basedir/src.exe" "$@"
exit $?

`;
Expand Down Expand Up @@ -857,7 +857,7 @@ case \`uname\` in
;;
esac

"$basedir/src.exe" "$@"
exec "$basedir/src.exe" "$@"
exit $?

`;
Expand Down
Loading