""" Быстрый шлюз проверки SDR Rover Link. Проверяет за одну-две секунды то, что ломается чаще всего: 1. Синтаксис всех файлов в protocol, tests, experiments и tools. 2. Импорт каждого модуля protocol. 3. Быстрые проверки ядра из tests под pytest. 4. Функциональные проверки тех лаб, которым не нужны данные эксперимента. Полный прогон лабы шлюз не заменяет. Он отвечает на один вопрос: не сломана ли кодовая база прямо сейчас. Запуск: python tools/quick_gate.py """ from __future__ import annotations import ast import importlib import inspect import os from pathlib import Path import re import subprocess import sys import traceback PROJECT_ROOT = Path(__file__).resolve().parent.parent CHECKED_DIRECTORIES = ("protocol", "tests", "experiments", "tools") def check_syntax() -> list[str]: """Разобрать все файлы Python и вернуть список сообщений об ошибках.""" failures: list[str] = [] for directory in CHECKED_DIRECTORIES: for path in sorted((PROJECT_ROOT / directory).glob("*.py")): try: ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) except SyntaxError as error: relative = path.relative_to(PROJECT_ROOT) failures.append(f"{relative}:{error.lineno}: синтаксис: {error.msg}") return failures def check_protocol_imports() -> list[str]: """Импортировать каждый модуль protocol и вернуть список сообщений об ошибках.""" failures: list[str] = [] for path in sorted((PROJECT_ROOT / "protocol").glob("*.py")): if path.name == "__init__.py": continue module_name = f"protocol.{path.stem}" try: importlib.import_module(module_name) except Exception: failures.append(f"{module_name}: импорт: {traceback.format_exc(limit=3).strip()}") return failures def check_core_tests() -> tuple[list[str], int]: """ Прогнать быстрые проверки ядра из tests под pytest. Возвращает: failures: Список сообщений об ошибках. passed: Количество пройденных проверок, ноль если разобрать вывод не удалось. """ environment = dict(os.environ, PYTHONIOENCODING="utf-8") completed = subprocess.run( [sys.executable, "-m", "pytest", "tests", "-q"], cwd=PROJECT_ROOT, capture_output=True, text=True, encoding="utf-8", errors="replace", env=environment, ) output = (completed.stdout + completed.stderr).strip() if completed.returncode != 0: return [f"pytest: тесты ядра не пройдены:\n{output}"], 0 match = re.search(r"(\d+) passed", output) passed = int(match.group(1)) if match else 0 return [], passed def check_functional_tests() -> tuple[list[str], int, int]: """ Выполнить функциональные проверки лаб, не требующие данных эксперимента. Возвращает: failures: Список сообщений об ошибках. executed: Количество выполненных лаб. skipped: Количество пропущенных лаб. """ failures: list[str] = [] executed = 0 skipped = 0 for path in sorted((PROJECT_ROOT / "experiments").glob("lab*.py")): source = path.read_text(encoding="utf-8") if "def run_functional_tests" not in source: continue module_name = f"experiments.{path.stem}" try: module = importlib.import_module(module_name) except Exception: failures.append(f"{module_name}: импорт: {traceback.format_exc(limit=3).strip()}") continue run_functional_tests = module.run_functional_tests parameters = inspect.signature(run_functional_tests).parameters required = [name for name, parameter in parameters.items() if parameter.default is parameter.empty] if required: skipped += 1 continue try: run_functional_tests() except Exception: failures.append(f"{module_name}: проверки: {traceback.format_exc(limit=3).strip()}") continue executed += 1 return failures, executed, skipped def main() -> int: """Выполнить шлюз и вернуть код возврата процесса.""" sys.path.insert(0, str(PROJECT_ROOT)) syntax_failures = check_syntax() if syntax_failures: print("Шлюз не пройден: ошибки синтаксиса") for failure in syntax_failures: print(f" {failure}") return 1 import_failures = check_protocol_imports() core_failures, passed = check_core_tests() test_failures, executed, skipped = check_functional_tests() failures = import_failures + core_failures + test_failures if failures: print("Шлюз не пройден") for failure in failures: print(f" {failure}") return 1 print( "Шлюз пройден: синтаксис в порядке, protocol импортируется, " f"тестов ядра пройдено {passed}, " f"функциональных проверок лаб выполнено {executed}, пропущено {skipped}" ) return 0 if __name__ == "__main__": raise SystemExit(main())