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

33 lines
No EOL
3.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 в префиксе:
```ts
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».