opencode-config/docs/decisions/080-pr-184-draw-image-test-helper-input-svg-race.md
Sergey 8c00346b76
fix(draw-image): per-process temp dir in renderToPng test helpers (#184)
* fix(draw-image): per-process temp dir in renderToPng test helpers

* docs(handoff): add handoff and ADR for renderToPng temp dir fix

* docs(project-map): note per-process temp dir fix in renderToPng helpers

* docs(handoff): set PR number

* docs(handoff): replace PR number placeholder in frontmatter

---------

Co-authored-by: opencode-agent <agent@opencode.local>
2026-07-31 22:28:04 +03:00

3.5 KiB
Raw Blame History

ADR-080: Per-process temp dir in renderToPng test helpers

Статус

Accepted (2026-07-31)

Контекст

Тестовые хелперы renderToPng в tests/render.integration.test.ts и tests/render.optional.integration.test.ts рендерили SVG через двухфазный процесс:

  1. writeFileSync(path.join(TMP, "input.svg"), svg) — запись в единственный fixed-path файл в общем TMP-каталоге (/tmp/draw-image-integration, общий для всех параллельных vitest-процессов).
  2. spawnSync("node", ["render.mjs", tmpSvg, outPath]) — дочерний процесс читает файл и рендерит PNG через sharp.

При параллельном запуске нескольких vitest-процессов (внешняя параллель — npx vitest run × N concurrently) процессы разделяли один input.svg: один процесс обнулял/перезаписывал файл, пока другой его читал → render.mjs падал с Input Buffer is empty. Это тестовый аналог бага #160 (продакшн cli.ts), но в хелпере, а не в CLI. Воспроизведено: 10 параллельных прогонов → run 9 упал с Input Buffer is empty.

Решение

Использовать уникальный temp-каталог на каждый вызов renderToPng через mkdtempSync под системным os.tmpdir() с PID в префиксе:

const tmpDir = mkdtempSync(path.join(os.tmpdir(), `draw-image-test-${process.pid}-`))
const tmpSvg = path.join(tmpDir, "input.svg")
try {
  writeFileSync(tmpSvg, svg)
  // ... spawnSync render.mjs ...
} finally {
  if (existsSync(tmpDir)) rmSync(tmpDir, { recursive: true, force: true })
}

и удалять каталог в finally-блоке после рендера (cleanup гарантирован даже при throw). render.mjs не изменён — он уже принимает path к SVG как argv, уникальность обеспечивает хелпер.

Альтернативы

  • PID-суффикс на файл вместо каталога: path.join(os.tmpdir(), \draw-image-test-input-${process.pid}.svg`)— как вcli.ts(#160). Достаточен для race между процессами, но НЕ решает race между threads/воркерами одного vitest-процесса (одинprocess.pid, несколько параллельных вызовов renderToPng). mkdtempSync` даёт уникальность на каждый вызов, закрывая оба случая (граничный случай из спеки #177).
  • Уникальный каталог на процесс без mkdtempSync: path.join(os.tmpdir(), \draw-image-test-${process.pid}`)+mkdirSync— требует ручной гарантии уникальности и cleanup;mkdtempSync` атомарно создаёт уникальный каталог за один вызов.
  • Передача SVG через stdin в render.mjs: избегает temp-файла целиком, но требует переписывания render.mjs (чтение из stdin вместо readFileSync(argv)), что выходит за рамки спеки #177 и ломает контракт «render.mjs принимает path как argv».