mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-11-13 17:08:20 +00:00
186 lines
16 KiB
Markdown
186 lines
16 KiB
Markdown
# Решения по гейтам и тестам
|
||
|
||
> Статус: **active**. Журнал решений «что сознательно НЕ вводим» и «как читаем
|
||
> результат CI». Только про гейты, линтеры, coverage, e2e и CI — **для обычных
|
||
> правок production-кода не нужен**. Читать, когда проектируется или меняется
|
||
> сам гейт, либо когда спрашивают «почему так, а не иначе». Оперативные команды
|
||
> и пороги — в `docs/quality-gates.md`, правила нагрузки на машину — в
|
||
> `docs/agent-workflow.md`, раздел 3.
|
||
|
||
Здесь лежат решения, чтобы не пересматривать их заново. Для каждого: причина и
|
||
то, что решение пересмотрело бы. В `docs/backlog.md` такие пункты не
|
||
дублируются: backlog живёт задачами, а не историей решений.
|
||
|
||
Числа ниже — разовые замеры, а не текущие значения: помечены датой. Актуальные
|
||
цифры даёт прогон (`docs/quality-gates.md`), по правилу `AGENTS.md` в документах
|
||
нет величин, которые меняются вместе с кодом.
|
||
|
||
## 1. Детектор дублирования разметки — не вводим
|
||
|
||
Ни свой AST-правило, ни готовая зависимость (jscpd, `eslint-plugin-sonarjs`).
|
||
|
||
- Замер по корпусу `.svelte` в `components/**` и `routes/**` (сентябрь 2026):
|
||
при пороге 5 нормализованных строк — одна осмысленная пара, при пороге 3 — 17
|
||
пар, и все они шум (`</label>`, `{/each}`, `{#if children}`). Семейство
|
||
schema-контролов делит максимум 4 строки — законное сходство by design.
|
||
- Причина: построчное сравнение Svelte-разметки принципиально ненадёжно —
|
||
одинаковый контейнер с разным содержимым даёт ложное срабатывание, а намеренно
|
||
общий класс — ложную тишину. Чтобы не ловить шум, порог пришлось бы поднять до
|
||
12+ строк, и тогда правило срабатывает ровно один раз.
|
||
- Контроль остаётся на ревью (расхождение видно в диффе) и на поведенческих
|
||
e2e-проверках; извлечение общей разметки закреплено конвенцией в `AGENTS.md`.
|
||
- Пересмотреть, если появится реально «уехавшая» копипаста в компонентах или
|
||
библиотека разрастётся на уровень композиций страниц.
|
||
|
||
## 2. Линтер согласованности комментариев — не вводим
|
||
|
||
Описания инструментов различаются осознанно, и ловить это текстовым правилом
|
||
дороже, чем смотреть на ревью. Действенная часть выполнена вручную: все
|
||
`TODO`/`FIXME` перенесены в backlog, незарегистрированных пометок в коде нет;
|
||
новые запрещены правилом в `AGENTS.md`.
|
||
|
||
## 3. Линтер «изменяемых чисел» в документации — не вводим
|
||
|
||
Правило «не фиксировать числа, которые меняются вместе с кодом» живёт в
|
||
`AGENTS.md` и держится на ревью. Автоматизация отклонена по замеру (сентябрь
|
||
2026): regex «число + существительное» на корпусе Markdown дал 15 совпадений, из
|
||
которых 14 — ложные («30 дней» политики архива, «9 опций» описания поля, «4
|
||
проекта» факт из `playwright.config.ts», цитата самого правила).
|
||
|
||
- Правило всё равно неполно: ловит «M инструментов», но не «инструментов — M»,
|
||
не «более 100», не «≈120»; на каждое обходное движение нужна правка правила, а
|
||
allowlist легитимных чисел становится вторым источником долга. Худший исход —
|
||
не «шумно», а «вроде гейт, а толку нет»: срабатывания на пустом месте
|
||
забейзелят, и через месяц правило перестанет ловить настоящие нарушения.
|
||
Видимое блокирующее правило, которое на деле ничего не проверяет, хуже
|
||
отсутствия правила.
|
||
- Соблюдение правила дешевле обхода: «список — в `docs/tools-map.md`» стоит
|
||
столько же символов, сколько цифра, но не требует обновления.
|
||
- Пересмотреть, если появится источник истины, из которого число можно
|
||
**вычислить** и сравнить автоматически, без разбора прозы. Такой случай есть и
|
||
решён отдельно — следующий пункт.
|
||
|
||
## 4. Сверка `docs/tools-map.md` с реестром — автоматизирована
|
||
|
||
Правило про числа не закрывает более опасный класс расхождений: документ не
|
||
устарел числом, а **перечисляет то, чего нет в коде**. В разделе «Реализовано»
|
||
карты лежали несуществующие страницы (`png-info`, `color-wheel-generator`,
|
||
`jpg-to-png`, `watermark-image-png`), которые агент принял бы за готовые
|
||
инструменты. Счёт при этом был верный — число такой класс не ловит.
|
||
|
||
Сверка множеств отвечает на вопрос однозначно и без allowlist, поэтому
|
||
автоматизирована: `web/src/lib/registry/tools-map-doc.test.ts` парсит раздел
|
||
«Реализовано» и сравнивает slug с `PAGES` в обе стороны (slug без страницы и
|
||
страница без записи), плюс ловит повтор slug между буллетами. Формат строки
|
||
карты — всё до первого тире, slug через `/` или `,` — закреплён в самом
|
||
документе и в тесте. Сверяются именно страницы, а не инструменты: карта
|
||
описывает то, что видит пользователь, а один инструмент может обслуживать
|
||
несколько страниц (см. `docs/architecture.md`, раздел 3).
|
||
|
||
Чтобы сломанный парсер не выглядел как успех, у теста есть отдельная проверка
|
||
«разбор не пустой»: если формат карты снова поменяется, тест упадёт с сообщением
|
||
про парсер, а не молча перестанет что-либо проверять. Входит в обычный
|
||
`pnpm --dir web test`; новых гейтов и конфигов не добавляет.
|
||
|
||
Граница автоматизации: парсер читает разделы «Реализовано», «Можно добавить» и
|
||
«Идеи», но проверяет их в разных направлениях. Раздел 1 сверяется с реестром
|
||
обеими сторонами (slug без страницы и страница без записи). Разделы 2 и 3
|
||
сверяются только в одну сторону: там slug'и намеренно несуществующие, поэтому
|
||
проверяется, что **ни один из них не является реальной страницей** —
|
||
реализованный инструмент, оставшийся в плане, роняет тест. Проверять «у плана
|
||
есть запись в реестре» нельзя по построению: список плановый, а не фактический.
|
||
|
||
Плановые разделы устроены прозой, поэтому их разбор другой: берутся все токены
|
||
нужной формы из всего буллита, а не только из «головы» до тире. Следствие для
|
||
авторов карты: реализованные страницы в планах называются прозой между
|
||
буллитами, а не внутри них.
|
||
|
||
## 5. Visual snapshots — не вводим
|
||
|
||
`toHaveScreenshot` сравнивает страницу с PNG-baseline из прошлого коммита, а не
|
||
с замыслом дизайна. При активно меняющемся визуальном языке это превращает
|
||
каждый дизайн-PR в регенерацию и ревью картинок, а baseline платформенные
|
||
(снимок с Windows не совпадёт на `ubuntu-latest`). Потребность закрывают
|
||
e2e-поведение, `lint:tokens` с token audit, i18n-гейт и ручной чеклист
|
||
`docs/checklist-manual-testing.md`.
|
||
|
||
Вернуться стоит, когда визуальный язык стабилизируется; тогда первым идёт `/kit`
|
||
в двух темах, а baseline рождаются на Linux.
|
||
|
||
## 6. Fail-режим для unused-token аудита — не вводим
|
||
|
||
Остаётся предупреждением: `verify` входит в deploy-workflow, поэтому стилевой
|
||
долг блокировал бы деплой сайта. Кроме того, аудит видит только ссылки
|
||
`var(--x)`, и чтение токена из JS дало бы ложное срабатывание. Правило «новый
|
||
токен — вместе с потребителем» живёт в `AGENTS.md`.
|
||
|
||
## 7. Per-project `expect.timeout` — не задаём
|
||
|
||
Firefox и WebKit отдают результат так же быстро, как Chromium: расхождения
|
||
объясняются памятью, а не скоростью движка. Таймаут под конкретную машину —
|
||
магическое число, которое придётся подстраивать на каждом раннере, поэтому
|
||
Playwright-конфиг остаётся с дефолтами, а проблема решается лимитом воркеров.
|
||
Практические правила — `docs/agent-workflow.md`, раздел 3.
|
||
|
||
## 8. Дублирование `verify` между workflow — сознательно
|
||
|
||
`Deploy to GitHub Pages` и `Quality` оба зовут `verify`: deploy проверяет
|
||
быстрый gate и собирает сайт, `Quality` прогоняет тот же `verify` и browser
|
||
matrix. По замеру (сентябрь 2026) `verify` — единицы минут, matrix — около трёх,
|
||
поэтому дублирование дешевле условных job-и, а matrix дёшево держать на каждом
|
||
PR. Актуальные длительности — в логе прогона. Ручной
|
||
`gh workflow run quality.yml` нужен только для перепрогона старого коммита.
|
||
|
||
## 9. Политика e2e в CI
|
||
|
||
Текущее состояние: `e2e` — non-blocking baseline, запускается на каждом пуше в
|
||
`main` и на каждом PR целиком (все проекты matrix). Ночного разделения нет: при
|
||
стоимости job около трёх минут оно не окупается, поэтому новые browser-critical
|
||
спецификации просто добавляются в `browserContractTests`.
|
||
|
||
`e2e` станет blocking, когда выполнятся три условия:
|
||
|
||
1. Условия matrix пересмотрены: решено, какие спецификации обязаны идти в
|
||
`browserContractTests` и `mobileTests`, нужен ли `png-fixtures` вне Chromium
|
||
и что делать с engine-специфичными расхождениями декодеров.
|
||
2. Накопились стабильные прогоны на раннере без падений по таймингу.
|
||
3. Вместе с `continue-on-error` снимается и сам trick с общим conclusion: иначе
|
||
зелёный прогон при красном `e2e` останется возможен.
|
||
|
||
Пока условия не выполнены, `e2e` остаётся non-blocking, а его статус читается по
|
||
отдельному job — см. следующий раздел.
|
||
|
||
## 10. Чтение результатов CI после пуша
|
||
|
||
Первый реальный прогон browser matrix происходит на runner, а не на локальной
|
||
машине: локально Firefox и WebKit проходят на 1 воркере, но состав, скорость и
|
||
память Linux-раннера отличаются. Поэтому после пуша разработчика:
|
||
|
||
1. дождаться завершения workflow и посмотреть, какие job вообще запустились;
|
||
2. смотреть статусы **отдельных job**, а не общий conclusion прогона: у `e2e`
|
||
стоит `continue-on-error: true`, поэтому прогон с упавшим e2e целиком
|
||
помечается как `success` — проверено на run `36225579363` (`E2E: failure` при
|
||
`conclusion: success`);
|
||
3. если `e2e` запускался и зелёный — сообщить фактические числа passed, failed,
|
||
skipped;
|
||
4. если `e2e` не запускался, сказать это прямо: matrix на раннере не проверена,
|
||
и её можно прогнать через `gh workflow run quality.yml`;
|
||
5. красный `e2e` только из-за ресурсов раннера фиксируется как ограничение
|
||
окружения: сначала проверяются один проект и один spec
|
||
(`docs/agent-workflow.md`, раздел 3), а не увеличиваются таймауты.
|
||
|
||
## 11. Тексты страниц: в базовой локали секция `pages` пустая
|
||
|
||
Заголовки и описания страниц переводятся (`pages.<slug>.*`), а EN-строка лежит в
|
||
реестре страниц — дублировать её в `en.ts` нельзя, иначе при правке реестра
|
||
английский текст разъедется со страницей, а `dict-consistency` этого не увидит.
|
||
Поэтому `en.ts` объявляет `pages: {}` — пустую секцию, а линтер правила
|
||
`i18n/dict-consistency` не считает это нарушением: в базовой локали отсутствие
|
||
`pages.*.title/description` разрешено опцией `baseLocaleFallback`, а полнота
|
||
перевода для остальных локалей проверяется строго и юнит-тестом
|
||
(`tools-i18n.test.ts`).
|
||
|
||
Альтернатива — `allowPaths: ["pages"]` — была бы слабее: она разрешила бы пустую
|
||
секцию `pages` в любой локали, то есть пропущенный перевод страницы прошёл бы
|
||
линтер молча. Пустая секция в EN и есть то, что делает правило точным.
|