---
pr: 184
title: fix(draw-image): per-process temp dir in renderToPng test helpers
---

## Что сделано

- `tests/render.integration.test.ts`: хелпер `renderToPng` переписан — вместо общего `path.join(TMP, "input.svg")` (где `TMP = "/tmp/draw-image-integration"` — общий для всех параллельных vitest-процессов) используется уникальный temp-каталог на каждый вызов через `mkdtempSync(path.join(os.tmpdir(), \`draw-image-test-${process.pid}-\`))`, внутри которого пишется `input.svg`.
- `tests/render.integration.test.ts`: добавлен `finally`-блок с `rmSync(tmpDir, { recursive: true, force: true })` — temp-каталог удаляется после рендера даже при ошибке/exit.
- `tests/render.integration.test.ts`: импорты очищены — `require("node:fs")` заменён на статический `writeFileSync`/`mkdtempSync` из `node:fs`, добавлен `import os from "node:os"`.
- `tests/render.optional.integration.test.ts`: аналогичный фикс хелпера `renderToPng` (тот же паттерн `TMP/input.svg`) — уникальный temp-каталог через `mkdtempSync` с PID + cleanup в `finally`.

## Почему

Тестовый хелпер `renderToPng` в обоих integration-тестах писал временный SVG в жёстко заданный `path.join(TMP, "input.svg")` где `TMP` — общий каталог для всех параллельных vitest-процессов. При параллельном запуске (CI матрица, `npx vitest run` × N в параллель, несколько vitest-воркеров) процессы конкурировали за один `input.svg`: один процесс обнулял/перезаписывал файл через `writeFileSync`, пока другой его читал через `render.mjs` → `render.mjs` падал с `Input Buffer is empty`. Это аналогично багу #160 (продакшн `cli.ts`), но в тестовом хелпере. Уникальный temp-каталог на вызов (`mkdtempSync` с PID в префиксе) + cleanup в `finally` устраняют race и гарантированно удаляют leftover. `mkdtempSync` выбран вместо PID-суффикса на файл, т.к. решает и race между процессами, и между threads одного процесса (каждый вызов `renderToPng` = свой уникальный каталог).

## Pending

—

## Watch out

- **Баг вне scope (создан issue #183)**: при верификации параллельным запуском 10× `npx vitest run` обнаружен отдельный race на общих output-каталогах `beforeAll` (`/tmp/draw-image-integration`, `/tmp/draw-image-cleanup`, `/tmp/draw-image-idempotency` и т.д.) — `beforeAll` делает `rmSync(TMP, recursive)` и процесс удаляет output PNG другого процесса → `existsSync(outPath)` = false / idempotency `skipped` вместо `rendered` / `unable to open for write`. Этот race НЕ связан с `input.svg`/`Input Buffer is empty` и чинится отдельно в #183. Критерий приёмки #177 выполнен: `Input Buffer is empty` полностью устранён во всех 10 параллельных прогонах.
- **Одиночный прогон ×10 стабильно зелёный** (58/58 каждый прогон) — проверено последовательно.
- `render.mjs` не изменён (контракт «принимает path к SVG как argv» сохранён).
- Продакшн-код `cli.ts` не тронут (исправлен в #160).