loader-smoke.spec.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import { existsSync } from 'node:fs'
  2. import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
  3. import { tmpdir } from 'node:os'
  4. import { join } from 'node:path'
  5. import { fileURLToPath } from 'node:url'
  6. import { describe, expect, it } from 'vitest'
  7. import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke'
  8. const configPath = '/tmp/fixture.cordis.yml'
  9. const tsconfigPath = fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url))
  10. const fixture = (name: string): string => fileURLToPath(new URL(`./fixtures/${name}.ts`, import.meta.url))
  11. // macOS realpaths temp dirs into /private; TMPDIR may live under /var or /tmp.
  12. const canonicalTempPath = (path: string): string => path.replace(/^\/private(?=\/(?:var|tmp)\/)/, '')
  13. describe('runLoaderSmoke', () => {
  14. it('isolates the process, closes stdin, captures output, and removes the cwd', async () => {
  15. const result = await runLoaderSmoke({
  16. label: 'success fixture',
  17. tempDirPrefix: 'loader-smoke-success-',
  18. binScript: fixture('success'),
  19. configPath,
  20. tsconfigPath,
  21. mode: 'src',
  22. env: { LOADER_SMOKE_MARKER: 'present' },
  23. })
  24. const output = JSON.parse(result.stdout) as {
  25. configPath: string
  26. args: string[]
  27. cwd: string
  28. dshHome: string
  29. agentsHome: string
  30. marker: string
  31. input: string
  32. }
  33. expect(output).toMatchObject({
  34. configPath,
  35. args: [configPath],
  36. marker: 'present',
  37. input: '',
  38. })
  39. expect(canonicalTempPath(output.dshHome)).toBe(canonicalTempPath(join(output.cwd, '.dsh')))
  40. expect(canonicalTempPath(output.agentsHome)).toBe(canonicalTempPath(join(output.cwd, '.agents')))
  41. expect(result.stderr).toContain('fixture stderr')
  42. expect(existsSync(output.cwd)).toBe(false)
  43. }, LOADER_SMOKE_TEST_TIMEOUT_MS)
  44. it('reuses a caller-provided cwd and leaves it in place', async () => {
  45. const cwd = await mkdtemp(join(tmpdir(), 'loader-smoke-shared-'))
  46. try {
  47. const result = await runLoaderSmoke({
  48. label: 'shared cwd fixture',
  49. cwd,
  50. binScript: fixture('success'),
  51. configPath,
  52. tsconfigPath,
  53. mode: 'src',
  54. })
  55. const output = JSON.parse(result.stdout) as { cwd: string }
  56. expect(canonicalTempPath(output.cwd)).toBe(canonicalTempPath(cwd))
  57. expect(existsSync(cwd)).toBe(true)
  58. } finally {
  59. await rm(cwd, { recursive: true, force: true })
  60. }
  61. }, LOADER_SMOKE_TEST_TIMEOUT_MS)
  62. it('passes an arbitrary bin argv and inspects world state before cleanup', async () => {
  63. let inspected = ''
  64. let marker = ''
  65. const result = await runLoaderSmoke({
  66. label: 'argv fixture',
  67. tempDirPrefix: 'loader-smoke-argv-',
  68. binScript: fixture('success'),
  69. libBinScript: fixture('success'),
  70. configPath,
  71. binArgs: ['--config', configPath, '--output-format', 'json', 'task with spaces'],
  72. tsconfigPath,
  73. prepare: cwd => writeFile(join(cwd, 'marker.txt'), 'prepared'),
  74. inspect: async (cwd) => {
  75. inspected = cwd
  76. marker = await readFile(join(cwd, 'marker.txt'), 'utf8')
  77. },
  78. })
  79. const output = JSON.parse(result.stdout) as { args: string[]; cwd: string }
  80. expect(output.args).toEqual(['--config', configPath, '--output-format', 'json', 'task with spaces'])
  81. expect(canonicalTempPath(inspected)).toBe(canonicalTempPath(output.cwd))
  82. expect(marker).toBe('prepared')
  83. expect(existsSync(inspected)).toBe(false)
  84. }, LOADER_SMOKE_TEST_TIMEOUT_MS)
  85. it('rejects a non-zero exit with captured diagnostics', async () => {
  86. await expect(runLoaderSmoke({
  87. label: 'failure fixture',
  88. tempDirPrefix: 'loader-smoke-fail-',
  89. binScript: fixture('fail'),
  90. libBinScript: fixture('fail'),
  91. configPath,
  92. tsconfigPath,
  93. })).rejects.toThrow('failure fixture exited 7 (expected 0). stdout:\n\nstderr:\nfixture failed')
  94. })
  95. it('accepts a declared expected failure exit and rejects any other outcome', async () => {
  96. // A scenario pinning a designed failure surface declares its exit code…
  97. const declared = await runLoaderSmoke({
  98. label: 'declared failure fixture',
  99. tempDirPrefix: 'loader-smoke-declared-fail-',
  100. binScript: fixture('fail'),
  101. libBinScript: fixture('fail'),
  102. configPath,
  103. tsconfigPath,
  104. expectedExitCode: 7,
  105. })
  106. expect(declared.stderr).toBe('fixture failed\n')
  107. // …and a run that succeeds instead still fails the smoke.
  108. await expect(runLoaderSmoke({
  109. label: 'unexpectedly clean fixture',
  110. tempDirPrefix: 'loader-smoke-clean-',
  111. binScript: fixture('success'),
  112. libBinScript: fixture('success'),
  113. configPath,
  114. tsconfigPath,
  115. expectedExitCode: 7,
  116. })).rejects.toThrow(/exited 0 \(expected 7\)/)
  117. })
  118. it('kills a process at its deadline and reports captured output', async () => {
  119. await expect(runLoaderSmoke({
  120. label: 'hanging fixture',
  121. tempDirPrefix: 'loader-smoke-hang-',
  122. binScript: fixture('hang'),
  123. libBinScript: fixture('hang'),
  124. configPath,
  125. tsconfigPath,
  126. processTimeoutMs: 100,
  127. })).rejects.toThrow('hanging fixture did not exit within 0.1s.')
  128. })
  129. })