1
0

process-main.spec.ts 3.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. import { Duplex, PassThrough } from 'node:stream'
  2. import { expect, it, onTestFinished } from 'vitest'
  3. import { JsonChannel } from '../src/channel.ts'
  4. import { runNodeMain } from '../src/process.ts'
  5. import type { ProgramProcess } from '../src/process.ts'
  6. import { decodeCodeJsonWire, encodeCodeJsonWire } from '../src/json-wire.ts'
  7. function endpoints() {
  8. const first = new PassThrough()
  9. const second = new PassThrough()
  10. const child = Duplex.from({ readable: first, writable: second })
  11. const host = Duplex.from({ readable: second, writable: first })
  12. child.on('error', () => {})
  13. host.on('error', () => {})
  14. onTestFinished(() => { child.destroy(); host.destroy() })
  15. return { child, host }
  16. }
  17. function processState(): ProgramProcess { return { env: { FIXTURE_SECRET: 'test' }, stdout: { write: () => true }, stderr: { write: () => true }, exitCode: undefined } }
  18. it('clears process environment, dispatches a binding reply and flushes the terminal frame', async () => {
  19. const { child, host } = endpoints()
  20. const state = processState()
  21. const nativeEnvironment = state.env
  22. nativeEnvironment.SystemRoot = 'C:\\Windows'
  23. nativeEnvironment.PATH = '/native/bin'
  24. nativeEnvironment.TMP = 'C:\\sandbox-temp'
  25. nativeEnvironment.TEMP = 'C:\\sandbox-temp'
  26. const messages: Record<string, unknown>[] = []
  27. const peer = new JsonChannel(host, 4096, (raw) => {
  28. const message = raw as Record<string, unknown>
  29. messages.push(message)
  30. if (message.type === 'ready') void peer.send({ type: 'boot', data: { code: 'console.log("ready"); return await tools.echo({});', namespaces: [{ global: 'tools', names: ['echo'] }], maxOutputBytes: 1024 } })
  31. if (message.type === 'call') void peer.send({ type: 'reply', id: message.id, ok: true, value: encodeCodeJsonWire(42) })
  32. }, () => {})
  33. onTestFinished(() => { peer.close() })
  34. await runNodeMain(child, 4096, state)
  35. expect(state.env).toEqual({})
  36. expect(state.env).not.toBe(nativeEnvironment)
  37. expect(Object.getPrototypeOf(state.env)).toBeNull()
  38. expect(nativeEnvironment).toEqual({ SystemRoot: 'C:\\Windows', PATH: '/native/bin', TMP: 'C:\\sandbox-temp', TEMP: 'C:\\sandbox-temp' })
  39. expect(state.exitCode).toBeUndefined()
  40. expect(decodeCodeJsonWire(messages.find(message => message.type === 'done')?.value)).toBe(42)
  41. })
  42. it.each([0, -1, 1.5, 4294967296])('rejects an invalid bootstrap frame limit %i', async (limit) => {
  43. const { child } = endpoints()
  44. await expect(runNodeMain(child, limit, processState())).rejects.toThrow('invalid control message limit')
  45. })
  46. it('rejects an unexpected first control frame', async () => {
  47. const { child, host } = endpoints()
  48. const peer = new JsonChannel(host, 4096, () => { void peer.send({ type: 'reply' }) }, () => {})
  49. onTestFinished(() => { peer.close() })
  50. await expect(runNodeMain(child, 4096, processState())).rejects.toThrow('expected program boot')
  51. })
  52. it('records an I/O failure before the boot frame', async () => {
  53. const { child, host } = endpoints()
  54. const state = processState()
  55. const peer = new JsonChannel(host, 4096, () => { peer.close() }, () => {})
  56. await expect(runNodeMain(child, 4096, state)).rejects.toBeInstanceOf(Error)
  57. expect(state.exitCode).toBe(1)
  58. })
  59. it('contains program writes that exceed queued control output', async () => {
  60. const { child, host } = endpoints()
  61. const state = processState()
  62. const peer = new JsonChannel(host, 1024, (raw) => {
  63. if ((raw as { type: string }).type === 'ready') void peer.send({ type: 'boot', data: { code: 'for(let i=0;i<30;i++) console.log("x".repeat(100));', namespaces: [], maxOutputBytes: 8000 } })
  64. }, () => {})
  65. onTestFinished(() => { peer.close() })
  66. await runNodeMain(child, 1024, state)
  67. expect(state.exitCode).toBe(1)
  68. })