runtime-tree-read-failure.spec.ts 2.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
  1. import { mkdtempSync, readFile, readFileSync, rmSync } from 'node:fs'
  2. import { tmpdir } from 'node:os'
  3. import { join } from 'node:path'
  4. import { setImmediate } from 'node:timers/promises'
  5. import { expect, it, vi } from 'vitest'
  6. import { verifyDesktopRuntime } from '../src/runtime-tree.ts'
  7. import { runtimeFixture } from './runtime-fixture.ts'
  8. vi.mock('node:fs', async importOriginal => ({
  9. ...await importOriginal<typeof import('node:fs')>(),
  10. readFile: vi.fn(),
  11. }))
  12. it('drains outstanding reads before rejecting and stops scheduling after a read failure', async () => {
  13. const root = mkdtempSync(join(tmpdir(), 'desktop-runtime-read-failure-'))
  14. const pending: ((error?: Error) => void)[] = []
  15. vi.mocked(readFile).mockImplementation((...args: unknown[]) => {
  16. const path = args[0]
  17. const callback = args.at(-1)
  18. if (typeof path !== 'string' || typeof callback !== 'function') throw new Error('unexpected readFile arguments')
  19. const complete = callback as (error: Error | null, body: Buffer | undefined) => void
  20. pending.push((error) => { complete(error ?? null, error === undefined ? readFileSync(path) : undefined) })
  21. })
  22. let outcome: Promise<unknown> | undefined
  23. try {
  24. runtimeFixture(root)
  25. let settled = false
  26. outcome = verifyDesktopRuntime(root, '1.0.0').finally(() => { settled = true }).catch((error: unknown) => error)
  27. const inFlight = pending.length
  28. expect(inFlight).toBeGreaterThan(1)
  29. const failure = new Error('runtime read failed')
  30. pending.shift()!(failure)
  31. try {
  32. // Pending callbacks keep the reads open across a complete event-loop turn.
  33. await setImmediate()
  34. expect(settled).toBe(false)
  35. expect(pending).toHaveLength(inFlight - 1)
  36. } finally {
  37. for (const complete of pending.splice(0)) complete()
  38. }
  39. expect(await outcome).toBe(failure)
  40. expect(pending).toHaveLength(0)
  41. } finally {
  42. for (const complete of pending.splice(0)) complete(new Error('test cleanup'))
  43. await outcome
  44. vi.mocked(readFile).mockReset()
  45. rmSync(root, { recursive: true, force: true })
  46. }
  47. })