1
0

linux-execve.spec.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. import { afterEach, describe, expect, it, vi } from 'vitest'
  2. afterEach(() => {
  3. vi.doUnmock('@deepseek-ai/dsh-lazy-require')
  4. vi.resetModules()
  5. })
  6. describe.skipIf(process.platform !== 'linux')('Linux libc execve binding', () => {
  7. it.each([undefined, 'pipe'] as const)('preserves inherited stdio with control %s and reports execve errno', async (control) => {
  8. const nativeExecve = vi.fn(() => -1)
  9. const nativeFcntl = vi.fn((fd: number, command: number) => {
  10. if (command === 2) return 0
  11. return fd === 7 ? 1 : [1, 0, 5][fd]
  12. })
  13. const func = vi.fn((declaration: string) => declaration.includes('execve')
  14. ? nativeExecve
  15. : nativeFcntl)
  16. const load = vi.fn(() => ({ func }))
  17. const errno = vi.fn(() => 2)
  18. vi.doMock('@deepseek-ai/dsh-lazy-require', () => ({
  19. createLazyRequire: () => () => ({ errno, load }),
  20. }))
  21. const { loadLinuxExecve } = await import('../src/linux-execve.ts')
  22. const execve = loadLinuxExecve()
  23. expect(loadLinuxExecve()).toBe(execve)
  24. expect(load).toHaveBeenCalledExactlyOnceWith(null)
  25. expect(func.mock.calls).toEqual([
  26. ['int execve(const char *pathname, const char **argv, const char **envp)'],
  27. ['int fcntl(int fd, int cmd, int arg)'],
  28. ])
  29. let failure: unknown
  30. try {
  31. execve('/missing/tool', ['tool', 'literal arg'], { A: '1', EMPTY: '' }, control)
  32. } catch (error) {
  33. failure = error
  34. }
  35. expect(nativeFcntl.mock.calls).toEqual([
  36. [0, 1, 0],
  37. [0, 2, 0],
  38. [1, 1, 0],
  39. [2, 1, 0],
  40. [2, 2, 4],
  41. ...control === 'pipe' ? [[7, 1, 0], [7, 2, 0]] : [],
  42. ])
  43. expect(nativeExecve).toHaveBeenCalledExactlyOnceWith(
  44. '/missing/tool',
  45. ['tool', 'literal arg', null],
  46. ['A=1', 'EMPTY=', null],
  47. )
  48. expect(errno).toHaveBeenCalledOnce()
  49. expect(failure).toMatchObject({
  50. code: 'ENOENT',
  51. errno: -2,
  52. syscall: 'execve',
  53. path: '/missing/tool',
  54. })
  55. expect(failure).toBeInstanceOf(Error)
  56. expect((failure as Error).message).toContain("ENOENT: no such file or directory, execve '/missing/tool'")
  57. })
  58. it('reports failure to read descriptor flags before replacing the process', async () => {
  59. const nativeExecve = vi.fn()
  60. const nativeFcntl = vi.fn(() => -1)
  61. const func = vi.fn((declaration: string) => declaration.includes('execve')
  62. ? nativeExecve
  63. : nativeFcntl)
  64. const errno = vi.fn(() => 9)
  65. vi.doMock('@deepseek-ai/dsh-lazy-require', () => ({
  66. createLazyRequire: () => () => ({ errno, load: () => ({ func }) }),
  67. }))
  68. const { loadLinuxExecve } = await import('../src/linux-execve.ts')
  69. expect(() => loadLinuxExecve()('/bin/tool', ['tool'], {})).toThrow(expect.objectContaining({
  70. code: 'EBADF',
  71. errno: -9,
  72. syscall: 'fcntl',
  73. }))
  74. expect(nativeFcntl).toHaveBeenCalledExactlyOnceWith(0, 1, 0)
  75. expect(nativeExecve).not.toHaveBeenCalled()
  76. expect(errno).toHaveBeenCalledOnce()
  77. })
  78. it('reports failure to clear close-on-exec before replacing the process', async () => {
  79. const nativeExecve = vi.fn()
  80. const nativeFcntl = vi.fn()
  81. .mockReturnValueOnce(1)
  82. .mockReturnValueOnce(-1)
  83. const func = vi.fn((declaration: string) => declaration.includes('execve')
  84. ? nativeExecve
  85. : nativeFcntl)
  86. const errno = vi.fn(() => 5)
  87. vi.doMock('@deepseek-ai/dsh-lazy-require', () => ({
  88. createLazyRequire: () => () => ({ errno, load: () => ({ func }) }),
  89. }))
  90. const { loadLinuxExecve } = await import('../src/linux-execve.ts')
  91. expect(() => loadLinuxExecve()('/bin/tool', ['tool'], {})).toThrow(expect.objectContaining({
  92. code: 'EIO',
  93. errno: -5,
  94. syscall: 'fcntl',
  95. }))
  96. expect(nativeFcntl.mock.calls).toEqual([
  97. [0, 1, 0],
  98. [0, 2, 0],
  99. ])
  100. expect(nativeExecve).not.toHaveBeenCalled()
  101. expect(errno).toHaveBeenCalledOnce()
  102. })
  103. })