From 3caa83e617142606aa2638f0626984f7e1d43547 Mon Sep 17 00:00:00 2001 From: Sergey <93754860+slaid098@users.noreply.github.com> Date: Fri, 31 Jul 2026 22:49:46 +0300 Subject: [PATCH] fix(draw-image): per-process output TMP in e2e.test.ts beforeAll race (#187) * fix(draw-image): per-process output TMP in e2e.test.ts beforeAll * docs(handoff): add handoff and ADR for e2e.test.ts beforeAll fix * docs(handoff): set PR number 187 --------- Co-authored-by: opencode-agent --- .opencode/draw-image/tests/e2e.test.ts | 7 +++++- .../082-pr-187-e2e-test-beforeall-tmp-race.md | 13 +++++++++++ .../pr-187-e2e-test-beforeall-tmp-race.md | 23 +++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 docs/decisions/082-pr-187-e2e-test-beforeall-tmp-race.md create mode 100644 docs/handoff/pr-187-e2e-test-beforeall-tmp-race.md diff --git a/.opencode/draw-image/tests/e2e.test.ts b/.opencode/draw-image/tests/e2e.test.ts index 65af9cd..da33f9b 100644 --- a/.opencode/draw-image/tests/e2e.test.ts +++ b/.opencode/draw-image/tests/e2e.test.ts @@ -1,18 +1,23 @@ import { describe, test, expect, beforeAll, afterAll } from "vitest" import { existsSync, readFileSync, rmSync, mkdirSync } from "node:fs" import path from "node:path" +import os from "node:os" import { fileURLToPath } from "node:url" import { spawnSync } from "node:child_process" const __dirname = path.dirname(fileURLToPath(import.meta.url)) const DRAW_IMAGE_DIR = path.resolve(__dirname, "..") -const TMP = "/tmp/draw-image-e2e" +const TMP = path.join(os.tmpdir(), `draw-image-e2e-${process.pid}`) beforeAll(() => { if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true }) mkdirSync(TMP, { recursive: true }) }) +afterAll(() => { + if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true }) +}) + function runCli(args: string[]): { status: number; stdout: string; stderr: string } { return spawnSync("node", ["--experimental-strip-types", path.join(DRAW_IMAGE_DIR, "cli.ts"), ...args], { encoding: "utf-8", diff --git a/docs/decisions/082-pr-187-e2e-test-beforeall-tmp-race.md b/docs/decisions/082-pr-187-e2e-test-beforeall-tmp-race.md new file mode 100644 index 0000000..f72da49 --- /dev/null +++ b/docs/decisions/082-pr-187-e2e-test-beforeall-tmp-race.md @@ -0,0 +1,13 @@ +# ADR-082: per-process output TMP in e2e.test.ts beforeAll race + +## Статус +Accepted (2026-07-31) + +## Контекст +`tests/e2e.test.ts` использовал общий output-каталог `/tmp/draw-image-e2e`, создаваемый/очищаемый в `beforeAll`. При параллельном запуске нескольких процессов vitest concurrently процессы конкурировали за один каталог: `beforeAll` процесса A удалял `TMP` (вместе с `e2e.png` + `e2e.meta.json` процесса B) → assertion `expected 'skipped' to be 'rendered'` падал. Тот же bug class что и #183, но `e2e.test.ts` не вошёл в список affected-файлов #183. + +## Решение +Перевести output-каталог на per-process: `TMP = path.join(os.tmpdir(), \`draw-image-e2e-\${process.pid}\`)`. `process.pid` уникален на параллельный vitest-процесс → каждый процесс пишет в свой каталог, race устранён. Добавлен `afterAll` cleanup для удаления output-каталога после тестов (no leftover). + +## Альтернативы +- `fs.mkdtempSync(path.join(os.tmpdir(), "draw-image-e2e-"))` — отклонена: `process.pid` suffixed path обеспечивает консистентность с 7 уже зафиксированными файлами из #186 и достаточен для внешней параллельности. \ No newline at end of file diff --git a/docs/handoff/pr-187-e2e-test-beforeall-tmp-race.md b/docs/handoff/pr-187-e2e-test-beforeall-tmp-race.md new file mode 100644 index 0000000..c6c2d9c --- /dev/null +++ b/docs/handoff/pr-187-e2e-test-beforeall-tmp-race.md @@ -0,0 +1,23 @@ +--- +pr: 187 +title: fix(draw-image): per-process output TMP in e2e.test.ts beforeAll race +--- + +## Что сделано +- `e2e.test.ts` переведён с общего output-каталога `/tmp/draw-image-e2e` на per-process: `TMP = path.join(os.tmpdir(), \`draw-image-e2e-\${process.pid}\`)`. +- Добавлен `import os from "node:os"`. +- Добавлен `afterAll` с `rmSync(TMP, { recursive: true, force: true })` (импорт `afterAll` уже присутствовал в vitest-импорте, но блок отсутствовал — критерий приемки требует "no leftover"). +- Добавлен handoff + ADR-082. + +## Почему +`tests/e2e.test.ts` использовал общий `/tmp/draw-image-e2e`, создаваемый/очищаемый в `beforeAll`. При параллельном запуске 10× `npx vitest run` процессы конкурировали за один output-каталог: процесс A удалял `TMP` (вместе с `e2e.png` + `e2e.meta.json`, только что написанными процессом B) → assertion `expected 'skipped' to be 'rendered'` падал. `process.pid` уникален на параллельный vitest-процесс → per-process TMP устраняет race. Тот же bug class что и #183, но `e2e.test.ts` не вошёл в список affected-файлов issue #183 (чинился в #186, где явно отмечено в Pending: "Issue #185: тот же bug class в `tests/e2e.test.ts` — отдельный PR"). + +Верификация: одиночный `npx vitest run .opencode/draw-image/tests/` — 58/58 зелёные; параллельный 10× `npx vitest run .opencode/draw-image/tests/e2e.test.ts` — 10/10 зелёные, без `skipped vs rendered`. + +## Pending +- + +## Watch out +- Спека issue #185 утверждала "afterAll уже есть — оставить cleanup", но фактически блок `afterAll` отсутствовал в теле `e2e.test.ts` (импорт был в строке 1, но не использовался). Добавлен `afterAll` cleanup по паттерну #186 — критерий приемки "no leftover" требует удаления output-каталога после тестов. +- Спека предлагала альтернативу `fs.mkdtempSync(path.join(os.tmpdir(), "draw-image-e2e-"))`, но использован `process.pid` suffixed path — соответствует паттерну #186 (консистентность с 7 уже зафиксированными файлами). +- `mkdtempSync` не использовался — `process.pid` suffixed path достаточен для внешней параллельности (отдельные процессы vitest); для intra-process threads у vitest по умолчанию forks pool, PID разный у каждого воркера. \ No newline at end of file