All checks were successful
Deploy Trade-In / changes (push) Successful in 10s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 3m0s
Deploy Trade-In / build-backend (push) Successful in 1m1s
Deploy Trade-In / deploy (push) Successful in 1m9s
59 lines
3.1 KiB
Python
59 lines
3.1 KiB
Python
"""Repo-wide test config for tradein-mvp/backend.
|
||
|
||
Регистрирует кастомные pytest-маркеры (иначе PytestUnknownMarkWarning:
|
||
`--strict-markers` в pyproject.toml не включён, так что незарегистрированный
|
||
маркер только предупреждал бы) и сторожит глобальное состояние, которое
|
||
переживает отдельный тест, — см. `_no_leaked_password_verify_slots`.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
|
||
import pytest
|
||
|
||
|
||
def pytest_configure(config) -> None:
|
||
config.addinivalue_line(
|
||
"markers",
|
||
"pdf_render: real (non-mocked) WeasyPrint render — needs native "
|
||
"Pango/cairo/GObject libs, self-skips where unavailable (see "
|
||
"tests/test_pdf_real_render.py docstring for how to run it for real).",
|
||
)
|
||
|
||
|
||
@pytest.fixture(autouse=True)
|
||
def _no_leaked_password_verify_slots():
|
||
"""Тест не оставляет за собой занятых слотов проверки пароля (#2665, #2714).
|
||
|
||
Счётчики в `app.core.password` — состояние ПРОЦЕССА, а `pytest-asyncio` даёт
|
||
каждому тесту свой событийный цикл. Слот освобождает колбэк, посланный в
|
||
цикл через `call_soon_threadsafe`; если цикл к тому моменту закрыт,
|
||
`_schedule_verify_slot_release` ловит RuntimeError и слот не возвращается
|
||
никогда. На проде цикл живёт столько же, сколько процесс, и ветка
|
||
недостижима — а в тестах она копится молча и роняет НЕ ТОТ тест, который
|
||
её устроил: при пуле в 1 поток пары утечек хватает, чтобы всё дальнейшее
|
||
получало 429 «на ровном месте».
|
||
|
||
Поэтому проверка тут и общая: считаем слоты после каждого теста.
|
||
|
||
`sys.modules.get`, а не import: тестам, которые password.py не трогают
|
||
(большинство), незачем тянуть `Settings()` с его требованием DATABASE_URL.
|
||
"""
|
||
yield
|
||
|
||
password_mod = sys.modules.get("app.core.password")
|
||
if password_mod is None:
|
||
return
|
||
|
||
inflight = password_mod._verify_inflight
|
||
by_key = dict(password_mod._verify_inflight_by_key)
|
||
# Сброс ДО assert: иначе одна утечка красит все последующие тесты и виновник
|
||
# теряется среди пострадавших.
|
||
password_mod._verify_inflight = 0
|
||
password_mod._verify_inflight_by_key.clear()
|
||
|
||
assert inflight == 0 and not by_key, (
|
||
f"тест оставил {inflight} занятых слотов проверки пароля (по ключам: {by_key}) — "
|
||
"утечка слота при пуле в 1 поток это вечный 429 всем на входе"
|
||
)
|