test(ci): сторож пропусков кричит ::error:: под Actions (#2871) (#2876)
All checks were successful
Deploy / changes (push) Successful in 8s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 4m24s
Deploy / build-worker (push) Successful in 4m55s
Deploy / deploy (push) Successful in 1m50s

This commit is contained in:
bot-backend 2026-08-13 21:05:54 +00:00
parent 01b5e73ea4
commit a068e4b612
2 changed files with 55 additions and 2 deletions

View file

@ -96,15 +96,24 @@ def pytest_sessionfinish(session, exitstatus) -> None:
unlisted = sorted(_observed_skips - _allowed_skips())
if not unlisted:
return
print(
f"\nНЕУЧТЁННЫЙ ПРОПУСК ({len(unlisted)}): проверка не исполнилась и не "
head = (
f"НЕУЧТЁННЫЙ ПРОПУСК ({len(unlisted)}): проверка не исполнилась и не "
f"объявлена в {_SKIP_ALLOWLIST_PATH.name}:"
)
print(f"\n{head}")
for nodeid in unlisted:
print(f" - {nodeid}")
print(
"Почини тест либо внеси его в skip_allowlist.txt с причиной — "
"пропуск без записи неотличим от пройденной проверки."
)
# #2871: под Actions дублируем в ::error:: — иначе сообщение тонет.
# 13.08 этот сторож четыре прогона подряд ронял job'у совершенно правильно,
# а его строка лежала посреди тысячи других (обычный print, по-русски) —
# и поиск по «FAILED / ERROR» её не находил. Причину искали три часа
# в диске, раннере, покрытии и кэше. Сторож, который роняет прогон,
# обязан кричать так, чтобы его нашли.
if os.environ.get("GITHUB_ACTIONS") or os.environ.get("CI"):
print(f"::error::{head} " + "; ".join(unlisted))
if exitstatus == 0:
session.exitstatus = 1

View file

@ -0,0 +1,44 @@
"""Проверка, что сторож пропусков кричит под Actions (#2871)."""
from __future__ import annotations
import types
import tests.conftest as ct
def _run_guard(monkeypatch, capsys, *, ci: bool, observed: set[str]) -> str:
monkeypatch.setattr(ct, "_observed_skips", observed)
monkeypatch.setattr(ct, "_allowed_skips", lambda: set())
monkeypatch.delenv("GITHUB_ACTIONS", raising=False)
monkeypatch.delenv("CI", raising=False)
if ci:
monkeypatch.setenv("GITHUB_ACTIONS", "true")
session = types.SimpleNamespace(exitstatus=0)
ct.pytest_sessionfinish(session, 0)
return capsys.readouterr().out, session.exitstatus
def test_guard_emits_error_annotation_under_actions(monkeypatch, capsys) -> None:
out, rc = _run_guard(monkeypatch, capsys, ci=True, observed={"tests/x.py::test_y"})
assert "::error::" in out, "под Actions сторож обязан подниматься в аннотации"
assert "tests/x.py::test_y" in out
assert rc == 1
def test_guard_stays_quiet_locally(monkeypatch, capsys) -> None:
"""Контроль: локально ::error:: не нужен, человеческое сообщение остаётся."""
out, rc = _run_guard(monkeypatch, capsys, ci=False, observed={"tests/x.py::test_y"})
assert "::error::" not in out
assert "НЕУЧТЁННЫЙ ПРОПУСК" in out
assert rc == 1
def test_guard_silent_when_all_skips_declared(monkeypatch, capsys) -> None:
"""Контроль: без незадекларированных пропусков сторож молчит и не роняет."""
monkeypatch.setattr(ct, "_observed_skips", set())
monkeypatch.setattr(ct, "_allowed_skips", lambda: set())
session = types.SimpleNamespace(exitstatus=0)
ct.pytest_sessionfinish(session, 0)
assert capsys.readouterr().out == ""
assert session.exitstatus == 0