import { once } from 'node:events' import { mkdir, mkdtemp, realpath, rm, symlink } from 'node:fs/promises' import { tmpdir } from 'node:os' import { basename, dirname, join } from 'node:path' import { spawnProcess } from '@orca/process-host' import { describe, expect, it } from 'vitest' import { parseDarwinExecutablePath, readShellProcessReadiness, resolveInstalledShellExecutablePaths, resolveShellExecutablePath } from './shell-process-readiness' describe('shell process readiness', () => { it('extracts the primary text image from macOS lsof output', () => { expect(parseDarwinExecutablePath('p42\nftxt\nn/bin/zsh\nftxt\nn/usr/lib/zsh/zle.so\n')).toBe( '/bin/zsh' ) }) it.runIf(process.platform === 'darwin')( 'reads a child process executable path from real lsof on macOS', async () => { const executable = process.execPath const child = spawnProcess({ program: executable, args: ['-e', 'setTimeout(() => {}, 10_000)'], stdio: 'ignore' }) try { await once(child, 'spawn', { signal: AbortSignal.timeout(5_000) }) if (child.pid === undefined) { throw new Error('Fixture process has no PID') } const readiness = await readShellProcessReadiness(child.pid) expect(readiness?.executablePath).toBe(await realpath(executable)) } finally { if (child.exitCode === null && child.signalCode === null) { const closed = once(child, 'close', { signal: AbortSignal.timeout(5_000) }) child.kill() await closed } } } ) it.skipIf(process.platform === 'win32')( 'resolves bare shell commands through the spawn PATH', async () => { const root = await mkdtemp(join(tmpdir(), 'orca-shell-path-')) const link = join(root, 'shell-name') await symlink(process.execPath, link) try { await expect(resolveShellExecutablePath('shell-name', dirname(root), root)).resolves.toBe( await resolveShellExecutablePath(process.execPath, dirname(root), root) ) } finally { await rm(root, { recursive: true, force: true }) } } ) it.skipIf(process.platform === 'win32')( 'uses the POSIX exec default when PATH is unset', async () => { await expect(resolveShellExecutablePath('sh', process.cwd(), undefined)).resolves.toBe( await resolveShellExecutablePath('/bin/sh', process.cwd(), '') ) } ) it.skipIf(process.platform === 'win32')( 'resolves relative shell paths against the PTY cwd', async () => { const root = await mkdtemp(join(tmpdir(), 'orca-relative-shell-')) const bin = join(root, 'bin') await symlink(dirname(process.execPath), bin) try { await expect( resolveShellExecutablePath(`./bin/${basename(process.execPath)}`, root, '') ).resolves.toBe(await resolveShellExecutablePath(process.execPath, root, '')) } finally { await rm(root, { recursive: true, force: true }) } } ) it.skipIf(process.platform === 'win32')( 'skips searchable directories that shadow a later PATH executable', async () => { const root = await mkdtemp(join(tmpdir(), 'orca-shadowed-shell-')) const first = join(root, 'first') const second = join(root, 'second') await mkdir(join(first, 'shell-name'), { recursive: true }) await mkdir(second) await symlink(process.execPath, join(second, 'shell-name')) try { await expect( resolveShellExecutablePath('shell-name', root, `${first}:${second}`) ).resolves.toBe(await resolveShellExecutablePath(process.execPath, root, '')) } finally { await rm(root, { recursive: true, force: true }) } } ) it.skipIf(process.platform === 'win32')( 'lists every PATH installation of a shell name, deduplicated and canonical', async () => { const root = await mkdtemp(join(tmpdir(), 'orca-installed-shells-')) const first = join(root, 'first') const second = join(root, 'second') const missing = join(root, 'missing') await mkdir(first) await mkdir(second) await symlink(process.execPath, join(first, 'shell-name')) await symlink(process.execPath, join(second, 'shell-name')) try { const canonical = await resolveShellExecutablePath(process.execPath, root, '') await expect( resolveInstalledShellExecutablePaths('shell-name', root, `${first}:${missing}:${second}`) ).resolves.toEqual([canonical]) } finally { await rm(root, { recursive: true, force: true }) } } ) it.skipIf(process.platform === 'win32')( 'omits a same-name executable that no PATH entry reaches', async () => { const root = await mkdtemp(join(tmpdir(), 'orca-offpath-shell-')) const onPath = join(root, 'bin') const offPath = join(root, 'dropped') await mkdir(onPath) await mkdir(offPath) await symlink(process.execPath, join(onPath, 'shell-name')) await symlink(process.execPath, join(offPath, 'shell-name')) try { await expect( resolveInstalledShellExecutablePaths('shell-name', root, onPath) ).resolves.not.toContain(join(offPath, 'shell-name')) } finally { await rm(root, { recursive: true, force: true }) } } ) })