ci: пропуск теста обязан назвать себя — иначе прогон красный
Some checks failed
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Failing after 21s
CI Trade-In / browser-tests (pull_request) Successful in 51s
CI Trade-In / frontend-checks (pull_request) Successful in 1m31s
CI / frontend-tests (pull_request) Successful in 1m20s
CI / openapi-codegen-check (pull_request) Successful in 2m3s
CI / backend-tests (pull_request) Successful in 15m46s
Some checks failed
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Failing after 21s
CI Trade-In / browser-tests (pull_request) Successful in 51s
CI Trade-In / frontend-checks (pull_request) Successful in 1m31s
CI / frontend-tests (pull_request) Successful in 1m20s
CI / openapi-codegen-check (pull_request) Successful in 2m3s
CI / backend-tests (pull_request) Successful in 15m46s
Общее у #2722, #2729 и #2740 — не три разных бага, а один: пропуск, которого не видно. Под `pytest -q` пропуск рисуется точкой `s`, неотличимой от прогона, и проверка годами «зелёная», ничего не проверяя. Три меры, от дешёвой к жёсткой. 1. `-rs` во всех pytest-шагах (ci.yml, ci-tradein.yml backend+browser). Каждый пропуск печатает причину в лог job'а. Одна опция — и молчаливых пропусков больше нет ни одного. 2. Postgres-сервисы вместо заглушечных DSN. ci-tradein: DATABASE_URL вёл на заведомо мёртвый localhost:5432/test, и девять тестов с `_live_session()` self-skip'ались — в CI не бежали НИ РАЗУ. Именно так #2740 разъехался со схемой (houses.url стал NOT NULL), а test_gar_flats_loader падал до первого утверждения (#2744). Теперь postgis-сервис + сборка схемы из backend/data/sql/ тем же строгим циклом, что в deploy-tradein.yml (ON_ERROR_STOP, падение миграции → job RED). Единственное исключение названо вслух в коде: 077 — backfill через postgres_fdw к БД другого стека, которой в CI нет. ci.yml: plain postgres:16 (без PostGIS — tests/sql/ строят себе временные таблицы) поднимает 10 тестов SQL-логики (#17, #99, #295), не бежавших с момента написания. TEST_DATABASE_URL намеренно НЕ задан: на нём висит phantom-column gate, которому нужна копия ПРОДОВОЙ схемы, и пустой контейнер дал бы там красноту на пустом месте. Замер до включения: tradein 122с/3858 passed/10 skipped на моке против 106с/3867 passed/1 skipped на живой БД; backend 783с/4594/48 против 723с/4604/38. Живая БД не медленнее — поэтому не второй job, а починка существующего. Накладные: подъём сервиса + bootstrap схемы (219 файлов, ~20с в tradein; в backend схема не нужна вовсе). 3. skip_allowlist.txt + хук в conftest обоих сьютов. Пропущено может быть только то, что объявлено с причиной. Любой новый пропуск — дописал кто-то skipif «пока починю», отвалилась зависимость, исчезла БД — роняет прогон. Список это ещё и инвентарь: против каждой записи сказано, почему проверку нельзя выполнить здесь и где она выполняется вместо этого. Сюда же попадает xfail (pytest рапортует его как skipped), так что xfail без strict=True тоже придётся объявить. Список — надмножество сред: в CI часть записей не срабатывает, на ноутбуке без Postgres и native-libs — срабатывает; лишняя запись безвредна, пропуск без записи — нет. Проверено локально в конфигурации, которую задаёт этот PR: backend 4604 passed / 38 skipped / exit 0, tradein 3858 passed / 10 skipped / exit 0; при удалённом allowlist тот же прогон даёт exit 1 и печатает неучтённые.
This commit is contained in:
parent
a091da1aa8
commit
1fa3cb6949
6 changed files with 374 additions and 12 deletions
|
|
@ -66,16 +66,76 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
needs: changes
|
||||
if: needs.changes.outputs.backend == 'true'
|
||||
# Postgres-сервис (#2745). ДО него лэйн был mock-only: DATABASE_URL указывал на
|
||||
# заведомо мёртвый `localhost:5432/test`, и девять тестов с `_live_session()`
|
||||
# self-skip'ались — в CI они не бежали НИ РАЗУ. Так и разъехался со схемой
|
||||
# test_house_dedup_merge (#2740: houses.url стал NOT NULL), а
|
||||
# test_gar_flats_loader вообще падал до первого утверждения (#2744).
|
||||
#
|
||||
# Замер перед включением: полный сьют на mock-лэйне 122с / 3858 passed / 10 skipped,
|
||||
# тот же сьют против живой БД — 106с / 3867 passed / 1 skipped. Живая БД не
|
||||
# медленнее, поэтому НЕ добавляем второй job, а чиним этот: один прогон, на
|
||||
# девять реальных проверок больше. Накладные — только подъём сервиса и bootstrap
|
||||
# схемы (219 файлов, ~20с).
|
||||
services:
|
||||
postgres:
|
||||
# postgis, не plain postgres: tests/tasks/test_cadastral_geo_match.py
|
||||
# проверяет KNN по geometry (PostGIS_Version() в connectivity-probe).
|
||||
image: postgis/postgis:16-3.4
|
||||
env:
|
||||
# CI-локальная одноразовая БД, не секрет (тот же паттерн, что в
|
||||
# .github/workflows/ci.yml). Имя БД ОБЯЗАНО отличаться от `test`:
|
||||
# `_live_session()` считает DSN с `localhost:5432/test` заглушкой и
|
||||
# возвращает None — с ним сервис поднялся бы, а тесты всё равно скипались.
|
||||
POSTGRES_DB: tradein
|
||||
POSTGRES_USER: tradein
|
||||
POSTGRES_PASSWORD: tradein
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd "pg_isready -U tradein"
|
||||
--health-interval 5s
|
||||
--health-timeout 5s
|
||||
--health-retries 10
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./tradein-mvp/backend
|
||||
env:
|
||||
# psycopg v3 требует parseable URL на импорте; реального коннекта нет —
|
||||
# DB-тесты мокаются (mirror deploy-tradein.yml test-job).
|
||||
DATABASE_URL: postgresql+psycopg://test:test@localhost:5432/test
|
||||
DATABASE_URL: postgresql+psycopg://tradein:tradein@localhost:5432/tradein
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Bootstrap схемы tradein (backend/data/sql/*.sql)
|
||||
working-directory: ./tradein-mvp
|
||||
env:
|
||||
PGPASSWORD: tradein
|
||||
# Тот же порядок и тот же строгий режим, что в deploy-tradein.yml:
|
||||
# `ls | sort` + ON_ERROR_STOP=on, падение любой миграции → job RED.
|
||||
# Никаких «применилось как получилось»: схема в CI либо та же, что на
|
||||
# проде, либо гейта нет.
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y postgresql-client
|
||||
psql -h localhost -U tradein -d tradein -v ON_ERROR_STOP=on -q -c \
|
||||
"CREATE EXTENSION IF NOT EXISTS postgis;
|
||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||
CREATE ROLE gendesign_reader;"
|
||||
for sql_file in $(ls -1 backend/data/sql/*.sql | sort); do
|
||||
fname=$(basename "$sql_file")
|
||||
# ЕДИНСТВЕННОЕ исключение, и оно названо вслух: 077 — не DDL, а
|
||||
# backfill, читающий foreign table gendesign_rosreestr_deals из БД
|
||||
# ДРУГОГО стека через postgres_fdw. В CI второй БД нет, USER MAPPING
|
||||
# создать не из чего. На пустых таблицах backfill всё равно no-op.
|
||||
if [ "$fname" = "077_dedup_hash_plain_key_backfill.sql" ]; then
|
||||
echo "⚠ пропускаю $fname — postgres_fdw к БД gendesign, которой в CI нет"
|
||||
continue
|
||||
fi
|
||||
psql -h localhost -U tradein -d tradein -v ON_ERROR_STOP=on -q -f "$sql_file" \
|
||||
|| { echo "::error::миграция $fname не применилась"; exit 1; }
|
||||
done
|
||||
echo "✓ схема собрана: $(psql -h localhost -U tradein -d tradein -tAc \
|
||||
"SELECT count(*) FROM information_schema.tables WHERE table_schema='public'") таблиц"
|
||||
|
||||
- name: Install uv
|
||||
# Официальный standalone-инсталлер. НЕ astral-sh/setup-uv — он ломается
|
||||
# на Forgejo-runner с PEP 668 externally-managed-environment (#666 CI).
|
||||
|
|
@ -121,7 +181,12 @@ jobs:
|
|||
# NB: в deploy-tradein.yml (post-merge test-job) свой экземпляр этого
|
||||
# deselect'а — он остаётся до #2680, который правит тот файл. Расхождение
|
||||
# безвредно: pre-merge гейт тест гоняет, post-merge просто пропустит зелёный.
|
||||
run: uv run pytest -q
|
||||
#
|
||||
# `-rs` (#2745) — КАЖДЫЙ пропуск печатает свою причину в лог job'а. Без него
|
||||
# `-q` рисует пропуск точкой `s`, неотличимой на глаз от прогона: ровно так
|
||||
# девять DB-тестов «шли зелёными», ничего не проверяя. Пропуск, который не
|
||||
# называет себя вслух, со временем перестаёт быть верным.
|
||||
run: uv run pytest -q -rs
|
||||
|
||||
# Тесты браузерного сайдкара (#2722). До этого job'а они не бежали НИГДЕ:
|
||||
# ci-tradein гейтил только backend/frontend, deploy-tradein — тоже, а каталог
|
||||
|
|
@ -169,7 +234,9 @@ jobs:
|
|||
- name: Run pytest (tradein-mvp/browser)
|
||||
# Каталог без pyproject/pytest.ini → дефолтная конфигурация, ничего
|
||||
# не deselect'ится. Ожидание: 108 passed, 0 failed, 0 skipped.
|
||||
run: pytest -q
|
||||
# `-rs`: если однажды появится пропуск, он назовёт причину в логе, а не
|
||||
# растворится в строке точек.
|
||||
run: pytest -q -rs
|
||||
|
||||
frontend-checks:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
|||
|
|
@ -69,6 +69,37 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
needs: changes
|
||||
if: needs.changes.outputs.backend == 'true'
|
||||
# Postgres-сервис (#2745). Раньше DATABASE_URL указывал на заведомо мёртвый
|
||||
# хост, и весь tests/sql/ (10 тестов: #17 velocity-alerts, #99 ДДУ-индикатор,
|
||||
# #295 weighted AVG) self-skip'ался connectivity-probe'ом — в CI эти проверки
|
||||
# не бежали ни разу с момента написания.
|
||||
#
|
||||
# plain postgres:16, БЕЗ PostGIS: тесты tests/sql/ строят себе временные
|
||||
# таблицы (CREATE TEMP TABLE) и не трогают ни geometry, ни реальную схему —
|
||||
# проверено локально, 16 passed за 1.3с. Поэтому и bootstrap схемы здесь не
|
||||
# нужен, в отличие от tradein-лэйна.
|
||||
#
|
||||
# TEST_DATABASE_URL НАМЕРЕННО НЕ задаётся: на него завязан tests/integration/
|
||||
# (phantom-column gate), которому нужна КОПИЯ ПРОДОВОЙ схемы через pg_dump по
|
||||
# SSH-туннелю. Пустой сервис-контейнер дал бы там красноту на пустом месте,
|
||||
# поэтому integration остаётся честно пропущенным — с причиной в логе (`-rs`).
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16
|
||||
env:
|
||||
# CI-локальная одноразовая БД, не секрет (тот же паттерн, что в
|
||||
# .github/workflows/ci.yml). Имя БД ≠ `test`, чтобы DSN не выглядел
|
||||
# заглушкой для probe-ов в тестах.
|
||||
POSTGRES_DB: gendesign_ci
|
||||
POSTGRES_USER: gendesign
|
||||
POSTGRES_PASSWORD: gendesign
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd "pg_isready -U gendesign"
|
||||
--health-interval 5s
|
||||
--health-timeout 5s
|
||||
--health-retries 10
|
||||
defaults:
|
||||
run:
|
||||
working-directory: backend
|
||||
|
|
@ -76,10 +107,7 @@ jobs:
|
|||
# TESTING=1 активирует RBAC-bypass (app/main.py rbac_guard пропускает
|
||||
# запросы при settings.testing=True) — иначе 401 на всём /api/v1.
|
||||
TESTING: "1"
|
||||
# Stub DSN: psycopg v3 требует parseable URL на импорте; реального коннекта
|
||||
# нет — DB-тесты мокаются, real-DB тест (tests/sql/) self-skip'ается через
|
||||
# connectivity-probe к этому хосту (5432 недоступен → skip).
|
||||
DATABASE_URL: postgresql+psycopg://test:test@localhost:5432/test
|
||||
DATABASE_URL: postgresql+psycopg://gendesign:gendesign@localhost:5432/gendesign_ci
|
||||
REDIS_URL: redis://localhost:6379/0
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
@ -136,10 +164,13 @@ jobs:
|
|||
# но --ignore — belt-and-suspenders на случай сбора фикстур).
|
||||
# tests/integration self-skip'ается через requires_test_db (skipif на
|
||||
# TEST_DATABASE_URL, который тут не задан) → НЕ игнорим, оно чисто skip'ается.
|
||||
# tests/sql/ mv_layout self-skip'ается через Postgres-connectivity probe
|
||||
# (5432 недоступен в этом mock-lane) → SKIP. Это intended.
|
||||
# tests/sql/ теперь РЕАЛЬНО ИДУТ — postgres-сервис выше (#2745).
|
||||
# PDF-тесты ИДУТ (libpango выше). Target: 0 failed, skips OK.
|
||||
#
|
||||
# `-rs` (#2745): каждый оставшийся пропуск печатает причину. Под `-q` без
|
||||
# него пропуск неотличим от прогона — именно так проверка тихо перестаёт
|
||||
# исполняться и об этом узнают, когда на неё надо опереться (#2722/#2729/#2740).
|
||||
#
|
||||
# Coverage-gate (#68): --cov=app меряет покрытие пакета app/.
|
||||
# --cov-fail-under=65 → job RED если покрытие упало ниже baseline
|
||||
# (измерено 2026-06: mock-lane сьют ~71%, см. [tool.coverage] в pyproject;
|
||||
|
|
@ -148,7 +179,7 @@ jobs:
|
|||
# coverage.xml — артефакт для будущего Codecov/Coveralls upload (#68 badge).
|
||||
# term-missing → видно непокрытые строки прямо в job-логе.
|
||||
run: |
|
||||
uv run pytest -q --ignore=tests/smoke \
|
||||
uv run pytest -q -rs --ignore=tests/smoke \
|
||||
--cov=app \
|
||||
--cov-report=term-missing:skip-covered \
|
||||
--cov-report=xml:coverage.xml \
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ NB: RBAC-гейт (app/main.py `rbac_guard`) требует заголовок `
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -40,3 +41,70 @@ def _clear_dependency_overrides():
|
|||
app.dependency_overrides.clear()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# ── Бюджет пропусков (#2745) ──────────────────────────────────────────────────
|
||||
#
|
||||
# Три инцидента за одни сутки (#2722, #2729, #2740) — один класс дефекта:
|
||||
# проверка, которая тихо не исполняется, со временем перестаёт быть верной, и
|
||||
# узнают об этом ровно тогда, когда на неё понадобилось опереться. `-rs` в CI
|
||||
# делает пропуск ГРОМКИМ; этот хук делает его УЧТЁННЫМ — пропущено может быть
|
||||
# только то, что записано в `tests/skip_allowlist.txt`. Любой новый пропуск (в
|
||||
# том числе возникший сам: отвалилась зависимость, исчезла БД, кто-то дописал
|
||||
# skipif «пока починю») роняет прогон, пока его не внесут в список осознанно.
|
||||
#
|
||||
# Список — НАДмножество: покрывает и CI (где есть Postgres и native-libs), и
|
||||
# ноутбук разработчика (где их нет). Запись, чей пропуск не случился, — не
|
||||
# ошибка; ошибка — пропуск без записи.
|
||||
#
|
||||
# Сюда же попадает xfail: pytest рапортует его как skipped, поэтому xfail без
|
||||
# `strict=True` (зеленеет и когда тест внезапно ПРОШЁЛ) тоже придётся объявить.
|
||||
#
|
||||
# Чего хук НЕ видит: `--deselect` / `-k` / `-m` — они убирают тест до прогона,
|
||||
# отчёта о пропуске не возникает вовсе. Против них работает правило «никаких
|
||||
# deselect'ов в workflow» (см. .forgejo/workflows/ci-tradein.yml).
|
||||
|
||||
_SKIP_ALLOWLIST_PATH = Path(__file__).parent / "skip_allowlist.txt"
|
||||
_observed_skips: set[str] = set()
|
||||
|
||||
|
||||
def _allowed_skips() -> set[str]:
|
||||
if not _SKIP_ALLOWLIST_PATH.exists():
|
||||
return set()
|
||||
lines = _SKIP_ALLOWLIST_PATH.read_text(encoding="utf-8").splitlines()
|
||||
return {stripped for line in lines if (stripped := line.split("#", 1)[0].strip())}
|
||||
|
||||
|
||||
def _record_skip(report) -> None:
|
||||
if report.skipped:
|
||||
# nodeid без «[параметров]»: список фиксирует, какая ПРОВЕРКА может
|
||||
# отсутствовать, а не каждую её параметризацию.
|
||||
_observed_skips.add(report.nodeid.split("[", 1)[0])
|
||||
|
||||
|
||||
def pytest_runtest_logreport(report) -> None:
|
||||
_record_skip(report)
|
||||
|
||||
|
||||
def pytest_collectreport(report) -> None:
|
||||
# Модульный pytest.skip(allow_module_level=True) приходит сюда, а не в
|
||||
# logreport, и nodeid у него — путь файла.
|
||||
_record_skip(report)
|
||||
|
||||
|
||||
def pytest_sessionfinish(session, exitstatus) -> None:
|
||||
unlisted = sorted(_observed_skips - _allowed_skips())
|
||||
if not unlisted:
|
||||
return
|
||||
print(
|
||||
f"\nНЕУЧТЁННЫЙ ПРОПУСК ({len(unlisted)}): проверка не исполнилась и не "
|
||||
f"объявлена в {_SKIP_ALLOWLIST_PATH.name}:"
|
||||
)
|
||||
for nodeid in unlisted:
|
||||
print(f" - {nodeid}")
|
||||
print(
|
||||
"Почини тест либо внеси его в skip_allowlist.txt с причиной — "
|
||||
"пропуск без записи неотличим от пройденной проверки."
|
||||
)
|
||||
if exitstatus == 0:
|
||||
session.exitstatus = 1
|
||||
|
|
|
|||
89
backend/tests/skip_allowlist.txt
Normal file
89
backend/tests/skip_allowlist.txt
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
# Объявленные пропуски сьюта backend/.
|
||||
#
|
||||
# Пропуск, которого здесь нет, роняет прогон (хук в tests/conftest.py). Смысл: не
|
||||
# запретить пропуски, а запретить НЕЗАМЕТНЫЕ. Проверка, которая тихо перестала
|
||||
# исполняться, со временем расходится с кодом, и узнают об этом ровно тогда,
|
||||
# когда на неё понадобилось опереться (#2722, #2729, #2740).
|
||||
#
|
||||
# Формат: один nodeid в строке, без «[параметров]»; `#` — комментарий.
|
||||
# Прежде чем добавить строку, ответь: почему эту проверку нельзя выполнить ЗДЕСЬ
|
||||
# и где она выполняется вместо этого. Нет ответа — это не пропуск, а дыра.
|
||||
#
|
||||
# Список — НАДмножество сред: на CI (ubuntu + libpango + postgres-сервис) часть
|
||||
# записей не срабатывает, на ноутбуке без них — срабатывает. Запись, чей пропуск
|
||||
# не случился, безвредна; пропуск без записи — нет.
|
||||
|
||||
# ── WeasyPrint: нужны native-либы (Pango/cairo/GObject) ───────────────────────
|
||||
# В CI ЭТИ ТЕСТЫ ИДУТ: ci.yml ставит libcairo2/libpango-1.0-0/libpangoft2-1.0-0.
|
||||
# Записи нужны только для macOS-dev, где libgobject-2.0-0 нет.
|
||||
tests/api/v1/test_parcels_forecast.py::test_export_forecast_pdf_returns_pdf
|
||||
tests/services/exporters/test_report_pdf.py::TestConfidenceSectionParity::test_dict_shaped_factor_produces_valid_pdf
|
||||
tests/services/exporters/test_report_pdf.py::TestFullReportExport::test_accepts_as_dict_input
|
||||
tests/services/exporters/test_report_pdf.py::TestFullReportExport::test_returns_non_trivial_length
|
||||
tests/services/exporters/test_report_pdf.py::TestFullReportExport::test_returns_pdf_magic_bytes
|
||||
tests/services/exporters/test_report_pdf.py::TestGracefulPartialReport::test_empty_report_still_valid_pdf
|
||||
tests/services/exporters/test_report_pdf.py::TestGracefulPartialReport::test_garbage_input_does_not_crash
|
||||
tests/services/exporters/test_report_pdf.py::TestGracefulPartialReport::test_partial_report_some_sections
|
||||
tests/services/generative/test_exporters.py::test_pdf_export_produces_pdf_bytes
|
||||
tests/test_layout_tz_pdf.py
|
||||
|
||||
# ── phantom-column gate: нужна КОПИЯ ПРОДОВОЙ схемы ───────────────────────────
|
||||
# Смысл этих тестов — поймать колонку, которой нет в реальной БД, поэтому пустой
|
||||
# сервис-контейнер их не заменяет: против пустой схемы они дали бы красноту, не
|
||||
# относящуюся к делу. Условие: TEST_DATABASE_URL на SSH-туннель к проду
|
||||
# (`ssh -N gendesign` → localhost:15432), см. tests/integration/conftest.py.
|
||||
# ЗАПУСКАТЬ ВРУЧНУЮ после правок SQL-запросов в app/services/**.
|
||||
tests/integration/test_analyze_parcels_sql.py::TestIrdOverlapSql::test_explain_ird_overlap
|
||||
tests/integration/test_analyze_parcels_sql.py::TestNeighborsSummarySql::test_explain_neighbors_summary
|
||||
tests/integration/test_phantom_columns.py::TestCadGeoTables::test_parcel_centroid_query
|
||||
tests/integration/test_phantom_columns.py::TestDomrfKnFlats::test_avg_price_query
|
||||
tests/integration/test_phantom_columns.py::TestDomrfKnFlats::test_supply_batch_query
|
||||
tests/integration/test_phantom_columns.py::TestDomrfKnObjects::test_competitors_full_cte_query
|
||||
tests/integration/test_phantom_columns.py::TestDomrfKnObjects::test_competitors_radius_query
|
||||
tests/integration/test_phantom_columns.py::TestDomrfKnObjects::test_velocity_competitor_query_columns
|
||||
tests/integration/test_phantom_columns.py::TestEkburgConstructionPermits::test_permits_rns_columns
|
||||
tests/integration/test_phantom_columns.py::TestEkburgConstructionPermits::test_permits_rve_columns
|
||||
tests/integration/test_phantom_columns.py::TestEkburgConstructionPermits::test_recent_permits_query
|
||||
tests/integration/test_phantom_columns.py::TestMvLayoutVelocity::test_velocity_by_room_bucket
|
||||
tests/integration/test_phantom_columns.py::TestObjectiveTables::test_objective_corpus_room_month_columns
|
||||
tests/integration/test_phantom_columns.py::TestObjectiveTables::test_objective_mapping_columns
|
||||
tests/integration/test_phantom_columns.py::TestObjectiveTables::test_sold_count_query
|
||||
|
||||
# ── SQL-логика на живой Postgres ──────────────────────────────────────────────
|
||||
# В CI ЭТИ ТЕСТЫ ИДУТ с #2745 (postgres-сервис в ci.yml); до него не бежали ни
|
||||
# разу с момента написания. Схема не нужна — строят себе временные таблицы.
|
||||
# Локально: docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=... postgres:16
|
||||
tests/sql/test_ddu_price_indicator.py::test_basis_and_previous_index
|
||||
tests/sql/test_ddu_price_indicator.py::test_packaged_deal_bucketed_by_per_unit_area
|
||||
tests/sql/test_ddu_price_indicator.py::test_prev_period_value_skips_filtered_quarter
|
||||
tests/sql/test_mv_layout_velocity_weighted_avg.py::TestWeightedAvgFormula::test_hand_computed_weighted_average
|
||||
tests/sql/test_mv_layout_velocity_weighted_avg.py::TestWeightedAvgFormula::test_no_zero_months_weighted_equals_naive
|
||||
tests/sql/test_mv_layout_velocity_weighted_avg.py::TestWeightedAvgFormula::test_nullif_prevents_division_by_zero
|
||||
tests/sql/test_mv_layout_velocity_weighted_avg.py::TestWeightedAvgFormula::test_weighted_differs_from_naive_sparse_project
|
||||
tests/sql/test_mv_layout_velocity_weighted_avg.py::TestWeightedAvgFormula::test_zero_deal_months_skew_naive_avg
|
||||
tests/sql/test_velocity_alerts.py::test_lookback_anchors_to_latest_data_month
|
||||
tests/sql/test_velocity_alerts.py::test_sharp_drop_is_flagged
|
||||
|
||||
# ── Живая сеть NSPD (nspd.gov.ru) ─────────────────────────────────────────────
|
||||
# Ходят в внешний сервис: в гейте это флейк и зависимость от чужого аптайма.
|
||||
# Запуск вручную: uv run pytest tests/scrapers/ -m slow / -m integration -s
|
||||
tests/scrapers/test_nspd_bulk_client.py::test_list_objects_in_building_real
|
||||
tests/scrapers/test_nspd_bulk_client.py::test_search_by_quarter_empty
|
||||
tests/scrapers/test_nspd_bulk_client.py::test_search_by_quarter_real_ekb
|
||||
tests/scrapers/test_nspd_bulk_client.py::test_wms_feature_info_real
|
||||
tests/scrapers/test_nspd_grid_walk.py::test_live_nspd_grid_walk_skipped
|
||||
|
||||
# ── Образцы, которых нет в git ────────────────────────────────────────────────
|
||||
# ДОЛГ, а не норма: парсеры проверяются на реальных дампах, которые лежат только
|
||||
# в untracked `.playwright-mcp/`. Пока файлы не в репозитории, эти 8 проверок не
|
||||
# исполняются нигде. Правильная починка — закоммитить образцы в tests/fixtures/
|
||||
# (как сделано в tradein-mvp/backend/tests/fixtures/*.html), после чего строки
|
||||
# отсюда убрать.
|
||||
tests/services/scrapers/test_ekb_krt_registry.py::TestMultiSiteCountFromDump::test_no_duplicate_site_names
|
||||
tests/services/scrapers/test_ekb_krt_registry.py::TestMultiSiteCountFromDump::test_site_count_from_dump
|
||||
tests/services/scrapers/test_ekb_ppt_tep_parser.py::TestParsePptTepRealPdf::test_phasing_has_area
|
||||
tests/services/scrapers/test_ekb_ppt_tep_parser.py::TestParsePptTepRealPdf::test_phasing_has_rows
|
||||
tests/services/scrapers/test_ekb_ppt_tep_parser.py::TestParsePptTepRealPdf::test_raw_tables_not_empty
|
||||
tests/services/scrapers/test_ekb_ppt_tep_parser.py::TestParsePptTepRealPdf::test_tep_has_rows
|
||||
tests/services/scrapers/test_ekb_ppt_tep_parser.py::TestParsePptTepRealPdf::test_zone_balance_has_itogo
|
||||
tests/services/scrapers/test_ekb_ppt_tep_parser.py::TestParsePptTepRealPdf::test_zone_balance_has_rows
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -57,3 +58,70 @@ def _no_leaked_password_verify_slots():
|
|||
f"тест оставил {inflight} занятых слотов проверки пароля (по ключам: {by_key}) — "
|
||||
"утечка слота при пуле в 1 поток это вечный 429 всем на входе"
|
||||
)
|
||||
|
||||
|
||||
# ── Бюджет пропусков (#2745) ──────────────────────────────────────────────────
|
||||
#
|
||||
# Три инцидента за одни сутки (#2722, #2729, #2740) — один класс дефекта:
|
||||
# проверка, которая тихо не исполняется, со временем перестаёт быть верной, и
|
||||
# узнают об этом ровно тогда, когда на неё понадобилось опереться. `-rs` в CI
|
||||
# делает пропуск ГРОМКИМ; этот хук делает его УЧТЁННЫМ — пропущено может быть
|
||||
# только то, что записано в `tests/skip_allowlist.txt`. Любой новый пропуск (в
|
||||
# том числе возникший сам: отвалилась зависимость, исчезла БД, кто-то дописал
|
||||
# skipif «пока починю») роняет прогон, пока его не внесут в список осознанно.
|
||||
#
|
||||
# Список — НАДмножество: покрывает и CI (где есть Postgres и native-libs), и
|
||||
# ноутбук разработчика (где их нет). Запись, чей пропуск не случился, — не
|
||||
# ошибка; ошибка — пропуск без записи.
|
||||
#
|
||||
# Сюда же попадает xfail: pytest рапортует его как skipped, поэтому xfail без
|
||||
# `strict=True` (зеленеет и когда тест внезапно ПРОШЁЛ) тоже придётся объявить.
|
||||
#
|
||||
# Чего хук НЕ видит: `--deselect` / `-k` / `-m` — они убирают тест до прогона,
|
||||
# отчёта о пропуске не возникает вовсе. Против них работает правило «никаких
|
||||
# deselect'ов в workflow» (см. .forgejo/workflows/ci-tradein.yml).
|
||||
|
||||
_SKIP_ALLOWLIST_PATH = Path(__file__).parent / "skip_allowlist.txt"
|
||||
_observed_skips: set[str] = set()
|
||||
|
||||
|
||||
def _allowed_skips() -> set[str]:
|
||||
if not _SKIP_ALLOWLIST_PATH.exists():
|
||||
return set()
|
||||
lines = _SKIP_ALLOWLIST_PATH.read_text(encoding="utf-8").splitlines()
|
||||
return {stripped for line in lines if (stripped := line.split("#", 1)[0].strip())}
|
||||
|
||||
|
||||
def _record_skip(report) -> None:
|
||||
if report.skipped:
|
||||
# nodeid без «[параметров]»: список фиксирует, какая ПРОВЕРКА может
|
||||
# отсутствовать, а не каждую её параметризацию.
|
||||
_observed_skips.add(report.nodeid.split("[", 1)[0])
|
||||
|
||||
|
||||
def pytest_runtest_logreport(report) -> None:
|
||||
_record_skip(report)
|
||||
|
||||
|
||||
def pytest_collectreport(report) -> None:
|
||||
# Модульный pytest.skip(allow_module_level=True) приходит сюда, а не в
|
||||
# logreport, и nodeid у него — путь файла.
|
||||
_record_skip(report)
|
||||
|
||||
|
||||
def pytest_sessionfinish(session, exitstatus) -> None:
|
||||
unlisted = sorted(_observed_skips - _allowed_skips())
|
||||
if not unlisted:
|
||||
return
|
||||
print(
|
||||
f"\nНЕУЧТЁННЫЙ ПРОПУСК ({len(unlisted)}): проверка не исполнилась и не "
|
||||
f"объявлена в {_SKIP_ALLOWLIST_PATH.name}:"
|
||||
)
|
||||
for nodeid in unlisted:
|
||||
print(f" - {nodeid}")
|
||||
print(
|
||||
"Почини тест либо внеси его в skip_allowlist.txt с причиной — "
|
||||
"пропуск без записи неотличим от пройденной проверки."
|
||||
)
|
||||
if exitstatus == 0:
|
||||
session.exitstatus = 1
|
||||
|
|
|
|||
39
tradein-mvp/backend/tests/skip_allowlist.txt
Normal file
39
tradein-mvp/backend/tests/skip_allowlist.txt
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# Объявленные пропуски сьюта tradein-mvp/backend.
|
||||
#
|
||||
# Пропуск, которого здесь нет, роняет прогон (хук в tests/conftest.py). Смысл: не
|
||||
# запретить пропуски, а запретить НЕЗАМЕТНЫЕ. Проверка, которая тихо перестала
|
||||
# исполняться, со временем расходится с кодом, и узнают об этом ровно тогда,
|
||||
# когда на неё понадобилось опереться (#2722, #2729, #2740).
|
||||
#
|
||||
# Формат: один nodeid в строке, без «[параметров]»; `#` — комментарий.
|
||||
# Прежде чем добавить строку, ответь: почему эту проверку нельзя выполнить ЗДЕСЬ
|
||||
# и где она выполняется вместо этого. Нет ответа — это не пропуск, а дыра.
|
||||
#
|
||||
# Список — НАДмножество сред: в CI Postgres есть (ci-tradein.yml поднимает
|
||||
# сервис-контейнер и собирает схему из backend/data/sql/), на ноутбуке без БД —
|
||||
# нет. Запись, чей пропуск не случился, безвредна; пропуск без записи — нет.
|
||||
|
||||
# ── Реальный рендер PDF: нужны native-либы WeasyPrint (Pango/cairo/GObject) ────
|
||||
# Где выполняется: внутри prod-образа — runner-stage tradein-mvp/backend/Dockerfile
|
||||
# ставит libcairo2 + libpango-1.0-0 + libpangoft2-1.0-0:
|
||||
# docker exec tradein-backend python -m pytest -q -m pdf_render tests/test_pdf_real_render.py
|
||||
# На голом ubuntu-latest в ci-tradein.yml этих либ нет (apt-шага в job'е нет).
|
||||
tests/test_pdf_real_render.py
|
||||
|
||||
# ── Тесты против живой Postgres ───────────────────────────────────────────────
|
||||
# В CI они ИДУТ (postgres-сервис в ci-tradein.yml, #2745) — записи ниже нужны
|
||||
# только для прогона на машине без БД. Именно эта группа разъехалась со схемой,
|
||||
# пока не бежала нигде: #2740 (houses.url стал NOT NULL) и #2744
|
||||
# (test_gar_flats_loader падал до первого утверждения). Поднять локально:
|
||||
# docker run -d -p 5432:5432 -e POSTGRES_DB=tradein -e POSTGRES_USER=tradein \
|
||||
# -e POSTGRES_PASSWORD=tradein postgis/postgis:16-3.4
|
||||
# psql ... -f backend/data/sql/*.sql # порядок как в deploy-tradein.yml
|
||||
tests/tasks/test_cadastral_geo_match.py::test_real_knn_nearest_within_threshold_picked
|
||||
tests/test_audit_api.py::test_real_accounts_and_analytics_aggregate_inserted_rows
|
||||
tests/test_gar_flats_loader.py::test_upsert_and_canon_match_populates_gar_flat_count
|
||||
tests/test_house_dedup_merge.py::test_real_canon_clusterkey_and_geo_guard_merge_semantics
|
||||
tests/test_house_dedup_merge.py::test_real_fias_pass_cross_guard_and_identity_carryover
|
||||
tests/test_house_dedup_merge.py::test_real_fias_pass_ignores_geo_guard
|
||||
tests/test_house_dedup_merge.py::test_real_merge_is_reversible_via_journal
|
||||
tests/test_house_dedup_merge.py::test_real_merge_repoints_dedups_deletes_and_is_idempotent
|
||||
tests/test_user_events.py::test_real_record_event_inserts_row
|
||||
Loading…
Add table
Reference in a new issue