All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 3m8s
CI Trade-In / backend-tests (pull_request) Successful in 5m40s
CI / backend-tests (pull_request) Successful in 17m53s
Почему: хук пинил ruff v0.7.4, CI и `uv run ruff format` брали ruff из lock'ов (0.15.12 в backend/, 0.15.20 в tradein-mvp/). Разные версии по-разному форматируют длинные assert-сообщения — хук переписывал нетронутые строки и ронял коммит на каждом касании файла (сегодня трижды), а следующий `ruff format` возвращал как было. Дрейф нигде не краснел: `ruff format --check` намеренно не в CI. Что: rev → v0.15.20 (тег есть), `ruff==0.15.20` в обоих pyproject (с `>=` любой `uv lock --upgrade` снова уведёт lock от хука), `uv lock` — в lock'ах поменялся только ruff. Гейт test_2864_ruff_version_alignment.py: rev == версии в обоих lock'ах == пин в pyproject; на origin/main красный по значению (v0.7.4 ≠ 0.15.12 ≠ 0.15.20). `ruff check` 0.15.20 зелёный на обоих проектах. Нормализация 160 файлов под 0.15.20 — отдельным PR (только формат). Refs #2864
71 lines
3.4 KiB
Python
71 lines
3.4 KiB
Python
"""pre-commit и uv.lock держат ОДНУ версию ruff (#2864).
|
||
|
||
Хук `ruff-pre-commit` пинился на v0.7.4, а CI и `uv run ruff format` брали ruff из
|
||
lock-файлов (0.15.12 в backend/, 0.15.20 в tradein-mvp/). Старый и новый ruff
|
||
по-разному форматируют длинные `assert ..., "сообщение"` — хук на каждом коммите
|
||
переписывал нетронутые строки, следующий `uv run ruff format` возвращал как было.
|
||
`ruff format --check` намеренно не в CI-гейте, поэтому дрейф нигде не краснел.
|
||
|
||
Гейт читает три источника истины и требует равенства. Красный на origin/main по
|
||
значению: rev v0.7.4 против 0.15.12 и 0.15.20. Бампить втроём: rev в
|
||
.pre-commit-config.yaml + `ruff==X` в обоих pyproject.toml + `uv lock`.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
from pathlib import Path
|
||
|
||
КОРЕНЬ = Path(__file__).resolve().parents[2]
|
||
|
||
_REV = re.compile(
|
||
r"repo:\s*https://github\.com/astral-sh/ruff-pre-commit\s*\n\s*rev:\s*v?([\w.]+)",
|
||
)
|
||
_LOCK_RUFF = re.compile(r'^name = "ruff"\nversion = "([\w.]+)"', re.MULTILINE)
|
||
_PYPROJECT_RUFF = re.compile(r'"ruff([<>=!~]+[^"]*)"')
|
||
|
||
|
||
def _precommit_rev() -> str:
|
||
текст = (КОРЕНЬ / ".pre-commit-config.yaml").read_text(encoding="utf-8")
|
||
m = _REV.search(текст)
|
||
assert m, "в .pre-commit-config.yaml нет блока ruff-pre-commit с rev"
|
||
return m.group(1)
|
||
|
||
|
||
def _lock_version(lock: str) -> str:
|
||
m = _LOCK_RUFF.search((КОРЕНЬ / lock).read_text(encoding="utf-8"))
|
||
assert m, f"в {lock} нет пакета ruff"
|
||
return m.group(1)
|
||
|
||
|
||
def test_precommit_rev_matches_both_locks() -> None:
|
||
"""Головной: хук и оба lock'а — одна версия ruff."""
|
||
rev = _precommit_rev()
|
||
locks = {
|
||
"backend/uv.lock": _lock_version("backend/uv.lock"),
|
||
"tradein-mvp/uv.lock": _lock_version("tradein-mvp/uv.lock"),
|
||
}
|
||
расхождения = {k: v for k, v in locks.items() if v != rev}
|
||
assert not расхождения, (
|
||
f"pre-commit ruff rev=v{rev}, а в lock'ах {расхождения} — хук и "
|
||
"`uv run ruff format` будут форматировать по-разному (#2864)"
|
||
)
|
||
|
||
|
||
def test_pyproject_pins_exact_ruff() -> None:
|
||
"""Контроль от повторного дрейфа: `ruff==X` в обоих pyproject, X = rev.
|
||
|
||
С `>=` любой `uv lock --upgrade` молча уводит lock от хука — ровно так
|
||
и разъехались 0.7.4 / 0.15.12 / 0.15.20.
|
||
"""
|
||
rev = _precommit_rev()
|
||
for f in ("backend/pyproject.toml", "tradein-mvp/backend/pyproject.toml"):
|
||
m = _PYPROJECT_RUFF.search((КОРЕНЬ / f).read_text(encoding="utf-8"))
|
||
assert m, f"в {f} нет зависимости ruff"
|
||
assert m.group(1) == f"=={rev}", f"{f}: ruff{m.group(1)}, ожидался ruff=={rev}"
|
||
|
||
|
||
def test_gate_reads_real_values() -> None:
|
||
"""Контроль самого гейта: парсеры возвращают версии, а не падают на пустом."""
|
||
assert re.fullmatch(r"\d+\.\d+\.\d+", _precommit_rev())
|
||
assert re.fullmatch(r"\d+\.\d+\.\d+", _lock_version("backend/uv.lock"))
|