Files
admin/.wiki/concepts/windows-docker-test-harness-gotchas.md

5.0 KiB
Raw Blame History

title, type, tags, sources, related, updated
title type tags sources related updated
Windows docker-тест-харнесс: грабли верификационных скриптов concept
windows
testing
node
docker
vitest
sqlite
gotchas
concepts/sched-fnf-r8-verification-stand.md
2026-08-18

Windows docker-тест-харнесс: грабли

Собрано из верификационных прогонов против docker-контейнеров на Windows-хосте (стенд sched F&F r8, 2026-08-18). Переиспользуемо для любой проверки «локальные репро-скрипты против живых docker-БД».

1. rmSync удаляемого sqlite-файла — EBUSY на Windows

new DatabaseSync('x.db') создаёт файл, и rmSync('x.db') ПОСЛЕ открытия падает EBUSY (errno -4082): Windows не удаляет открытый файл (на POSIX unlink открытого — ок, на Windows — нет).

Правило: rmSync ДО DatabaseSync. Порядок в тесте:

rmSync('x.db', { force: true });
const db = new DatabaseSync('x.db');

Симптом-ловушка: тот же EBUSY бывает, если файл держит ВИСЯЩИЙ node-процесс прошлого прогона (см. п.4) — сначала убить процессы, потом чинить порядок.

2. vitest 10s-таймаут vs медленный docker-DDL

На этом хосте DDL в docker-mysql8 стоит ~2001500ms на запрос (CREATE TABLE, ALTER), а createMysqlStorage (миграции v1→v5 на свежей БД) — ~11s. vitest-дефолт 10s на тест → каждый mysql-тест падает в beforeEach по таймауту, БЕЗ assertion-ошибок. Выглядит как регрессия, а это окружение.

Варианты: --testTimeout=60000, либо (лучше для стенда) не гонять тяжёлый contract-сьют на mysql/pg/mariadb через vitest — покрывать те же пути отдельными node-скриптами (adapters-spotcheck, adapter-migration-test).

3. Фейк-часы в тестах движка — монотонность и изоляция

Инжектируемый now() даёт детерминизм, но:

  • Часы должны идти МОНОТОННО. Прыжок назад (например, после тика 10:03 гнать once-расписание на 10:01) ломает: расписание «уже» выстрелило catch-up'ом на более позднем тике, nextRunAt=null. Планируй все времена по возрастанию.
  • Отдельный DB на сценарий. Общий DB + прыгающие часы → соседняя задача становится due на «чужом» тике и стреляет (мешает счётчикам/онRunFinal).
  • rmSync в начале каждого прогона — иначе повторный запуск видит старые раны (флак «запусти ещё раз — упало»).
  • Блокирующий раннер + runOnce = дедлок, если release после await. Тик не await'ить, дать микро-паузу на claim, release, потом await.

4. Висельники node-процессов держат файлы/порты

После таймаутов и убитых прогонов node-процессы живут и держат sqlite-файлы (→ EBUSY) и соединения. tasklist | grep node врёт (UTF-16 binary); правильно:

Get-CimInstance Win32_Process -Filter "Name='node.exe'" | Select ProcessId,CommandLine

Убивать точечно по CommandLine (репо-путь), НЕ всех — рядом крутятся dev-серверы юзера (nuxt, pi-агенты, wiki-graph). Таймаут bash убивает шелл, но не дерево node-детей — чистить руками.

5. Секвенции assert'ов: «ожидание» vs «поведение»

Половина «багов» в репро-скриптах — неверные ожидания в тесте, а не движок:

  • кумулятивные счётчики (retryCount = consumed-попытки, не «текущая»);
  • тай-брейки сортировок (priority-равные → id ASC);
  • событийные хуки на терминальных ранах (success тоже);
  • легаси-форматы данных (mongo: config.data не data, paused/disabled обязательны — иначе синтез даёт null и строка не в due).

Перед репортом о «находке» — перепроверить, что ожидание соответствует доке (это часто и есть ответ: поведение документировано, тест был неверен).