Run the core tests from the gate and finish the layout rename

The gate now runs pytest as well, so one command answers whether the code
base is broken: syntax, protocol imports, the 125 core tests, then the lab
functional suites. It fails when a test fails, verified by feeding it a
deliberately broken test.

Two leftovers from moving the labs into experiments/ are fixed: the lab
command still pointed at tests/, and the hook only watched protocol and
tests, so edits under experiments/ and tools/ triggered nothing.

Profiling the 3.6 s gate: 0.24 s syntax, 0.12 s imports, 0.61 s pytest,
2.7 s lab functional suites, of which Lab042's software loopback is 1.14 s
because it pushes a whole image through the chain. The pytest addition is
the small part. prepare_jpeg is now cached, since six checks call it and
each call re-encoded the image at several qualities.

README described the gate without the tests it now runs.
This commit is contained in:
LittleSam129
2026-08-11 13:40:05 +03:00
parent a37ad1f7b3
commit 13f183f7c7
5 changed files with 57 additions and 7 deletions

View File

@@ -12,7 +12,7 @@ argument-hint: <номер лабы> [краткая постановка]
Запусти агента `Explore` с запросом: что в `protocol/` уже относится к этой задаче, Запусти агента `Explore` с запросом: что в `protocol/` уже относится к этой задаче,
какие модули придётся трогать, где есть пересечение или дублирование, как устроены какие модули придётся трогать, где есть пересечение или дублирование, как устроены
две-три ближайшие по смыслу лабы в `tests/`. две-три ближайшие по смыслу лабы в `experiments/`.
Прочитай сам последние записи в `PROJECT_LOG.md` — там указано, чем должен Прочитай сам последние записи в `PROJECT_LOG.md` — там указано, чем должен
заниматься следующий этап. заниматься следующий этап.

View File

@@ -19,7 +19,7 @@ import subprocess
import sys import sys
PROJECT_ROOT = Path(__file__).resolve().parents[2] PROJECT_ROOT = Path(__file__).resolve().parents[2]
WATCHED = re.compile(r"[\\/](?:protocol|tests)[\\/][^\\/]+\.py$") WATCHED = re.compile(r"[\\/](?:protocol|experiments|tests|tools)[\\/][^\\/]+\.py$")
def main() -> int: def main() -> int:

View File

@@ -98,7 +98,7 @@ python -m pip install -r requirements-hardware.txt
python -m pytest tests/ -q python -m pytest tests/ -q
``` ```
**Дешёвый шлюз** — синтаксис, импорт `protocol`, функциональные проверки лабораторных: **Дешёвый шлюз** — синтаксис, импорт `protocol`, быстрые проверки ядра и функциональные проверки лабораторных. Отвечает на один вопрос: не сломана ли кодовая база прямо сейчас:
```bash ```bash
python tools/quick_gate.py python tools/quick_gate.py

View File

@@ -26,6 +26,7 @@ from __future__ import annotations
import argparse import argparse
import csv import csv
import functools
import io import io
import time import time
from dataclasses import asdict, dataclass, field from dataclasses import asdict, dataclass, field
@@ -193,6 +194,7 @@ class FunctionalTestResult:
# ==================================================================== подготовка # ==================================================================== подготовка
@functools.lru_cache(maxsize=4)
def prepare_jpeg( def prepare_jpeg(
source_path: Path = SOURCE_IMAGE, source_path: Path = SOURCE_IMAGE,
width: int = IMAGE_WIDTH, width: int = IMAGE_WIDTH,
@@ -207,6 +209,10 @@ def prepare_jpeg(
заранее принятым значением, и возвращается вместе с байтами. заранее принятым значением, и возвращается вместе с байтами.
Возвращает готовые байты JPEG в градациях серого и применённое качество. Возвращает готовые байты JPEG в градациях серого и применённое качество.
Результат кэшируется: функция вызывается шестью функциональными
проверками подряд, а каждый вызов заново кодирует изображение в
нескольких вариантах качества.
""" """
if not source_path.exists(): if not source_path.exists():

View File

@@ -3,9 +3,10 @@
Проверяет за одну-две секунды то, что ломается чаще всего: Проверяет за одну-две секунды то, что ломается чаще всего:
1. Синтаксис всех файлов в protocol, tests и tools. 1. Синтаксис всех файлов в protocol, tests, experiments и tools.
2. Импорт каждого модуля protocol. 2. Импорт каждого модуля protocol.
3. Функциональные проверки тех лаб, которым не нужны данные эксперимента. 3. Быстрые проверки ядра из tests под pytest.
4. Функциональные проверки тех лаб, которым не нужны данные эксперимента.
Полный прогон лабы шлюз не заменяет. Полный прогон лабы шлюз не заменяет.
Он отвечает на один вопрос: не сломана ли кодовая база прямо сейчас. Он отвечает на один вопрос: не сломана ли кодовая база прямо сейчас.
@@ -19,7 +20,10 @@ from __future__ import annotations
import ast import ast
import importlib import importlib
import inspect import inspect
import os
from pathlib import Path from pathlib import Path
import re
import subprocess
import sys import sys
import traceback import traceback
@@ -62,6 +66,41 @@ def check_protocol_imports() -> list[str]:
return failures 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]: def check_functional_tests() -> tuple[list[str], int, int]:
""" """
Выполнить функциональные проверки лаб, не требующие данных эксперимента. Выполнить функциональные проверки лаб, не требующие данных эксперимента.
@@ -128,8 +167,9 @@ def main() -> int:
return 1 return 1
import_failures = check_protocol_imports() import_failures = check_protocol_imports()
core_failures, passed = check_core_tests()
test_failures, executed, skipped = check_functional_tests() test_failures, executed, skipped = check_functional_tests()
failures = import_failures + test_failures failures = import_failures + core_failures + test_failures
if failures: if failures:
print("Шлюз не пройден") print("Шлюз не пройден")
@@ -137,7 +177,11 @@ def main() -> int:
print(f" {failure}") print(f" {failure}")
return 1 return 1
print(f"Шлюз пройден: синтаксис в порядке, protocol импортируется, функциональных проверок выполнено {executed}, пропущено {skipped}") print(
"Шлюз пройден: синтаксис в порядке, protocol импортируется, "
f"тестов ядра пройдено {passed}, "
f"функциональных проверок лаб выполнено {executed}, пропущено {skipped}"
)
return 0 return 0