Отличное форматирование

This commit is contained in:
2026-05-26 14:59:25 +03:00
parent 2b139a18b3
commit e3a615cb50
49 changed files with 1294 additions and 501 deletions

View File

@@ -1,95 +1,114 @@
#!/usr/bin/env python3
"""Generate Typst tables of unit tests for chapter 5."""
"""Generate a single Typst unit-test table for chapter 5."""
from __future__ import annotations
import re
from collections import defaultdict
from pathlib import Path
try:
import yaml
except ImportError:
yaml = None # type: ignore
def typst_escape(s: str) -> str:
return s.replace("\\", "\\\\").replace("`", "\\`")
def emit_table(
lines: list[str],
caption: str,
ncol: int,
headers: list[str],
data_rows: list[list[str]],
label: str,
) -> None:
lines.append("#pz-test-table(\n")
lines.append(f" [{caption}],\n")
lines.append(f" {ncol},\n")
lines.append(" table.header(\n")
for h in headers:
lines.append(f" [{typst_escape(h)}],\n")
lines.append(" ),\n")
for row in data_rows:
cells = ", ".join(f"[{typst_escape(c)}]" for c in row)
lines.append(f" {cells},\n")
lines.append(f") <{label}>\n\n")
def load_overrides(script_dir: Path) -> dict[str, str]:
path = script_dir / "test_descriptions.yaml"
if not path.is_file() or yaml is None:
return {}
data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
return {str(k): str(v) for k, v in data.items()}
def _split_camel(name: str) -> list[str]:
parts: list[str] = []
buf: list[str] = []
for ch in name:
if ch.isupper() and buf and buf[-1].islower():
parts.append("".join(buf))
buf = [ch]
else:
buf.append(ch)
if buf:
parts.append("".join(buf))
return parts
def describe_method(name: str, overrides: dict[str, str]) -> str:
if name in overrides:
return overrides[name]
low = name.lower()
if "encryption" in low and "wrong" in low:
return "дешифрование с неверным ключом завершается ошибкой"
if "encryption" in low and "same" in low:
return "симметрия шифрования и дешифрования при верном ключе"
if "correct key" in low:
return "верный ключ проходит проверку checkKey"
if "incorrect key" in low:
return "неверный ключ не проходит проверку checkKey"
if name.startswith("maps"):
return "исключение преобразуется в типизированную ошибку Wallenc"
if name.startswith("syncGroup"):
rest = _split_camel(name[9:])
return "синхронизация группы: " + " ".join(w.lower() for w in rest)
if name.startswith("sync"):
return "сценарий синхронизации: " + " ".join(w.lower() for w in _split_camel(name[4:]))
if "Totp" in name or "totp" in name or "Otp" in name or "otp" in name:
return "корректность TOTP/OTP: " + " ".join(w.lower() for w in _split_camel(name))
if name.endswith("Works") or "Crud" in name:
return "CRUD-операции и сохранение данных"
if "parses" in low or "rejects" in low:
return "разбор и валидация входных данных"
if "Route" in name or "Intent" in name or "mapsTo" in name:
return "маршрутизация, deep link или подписи UI"
if "enqueue" in low or "cancel" in low or "fail" in low or "progress" in low:
return "жизненный цикл фоновой задачи"
words = _split_camel(name)
return " ".join(w.lower() for w in words)
def main() -> None:
root = Path(__file__).resolve().parents[2]
script_dir = Path(__file__).resolve().parent
out = Path(__file__).resolve().parents[1] / "includes" / "ch05-tests-generated.typ"
overrides = load_overrides(script_dir)
rows: list[tuple[str, str, str, str]] = []
for p in sorted(root.rglob("*.kt")):
if "/src/test/" not in p.as_posix():
continue
text = p.read_text(encoding="utf-8", errors="replace")
cls_m = re.search(r"class\s+(\w+)", text)
if not cls_m:
continue
cls = cls_m.group(1)
mod = p.parts[p.parts.index("src") - 1]
rel = p.relative_to(root).as_posix()
for m in re.finditer(r"@Test[\s\S]*?fun\s+(?:`([^`]+)`|(\w+))\s*\(", text):
name = m.group(1) or m.group(2)
rows.append((mod, cls, name, rel))
desc = describe_method(name, overrides)
rows.append((mod, name, desc))
by_mod: dict[str, list] = defaultdict(list)
for mod, cls, name, rel in rows:
by_mod[mod].append((cls, name, rel))
rows.sort(key=lambda r: (r[0], r[1]))
lines = [
"// AUTO-GENERATED by gen_test_tables.py — include from ch05.typ\n",
'#import "common.typ": pz-test-table\n\n',
]
summary_rows = []
for mod in sorted(by_mod):
for cls, name, rel in by_mod[mod]:
short = rel.split("/")[-1]
summary_rows.append([mod, cls, name, short])
data_rows = []
for i, (mod, name, desc) in enumerate(rows, start=1):
data_rows.append([str(i), mod, name, desc])
emit_table(
lines,
"Сводка модульных unit-тестов (src/test)",
4,
["Модуль", "Класс", "Метод", "Файл"],
summary_rows,
"tbl-unit-all",
)
for mod in sorted(by_mod):
safe = mod.replace("-", "_")
lines.append(f"=== Реестр тестов модуля :{mod}\n\n")
mod_rows = [
[cls, name, rel.split("/")[-1]]
for cls, name, rel in by_mod[mod]
]
emit_table(
lines,
f"Unit-тесты модуля :{mod}",
3,
["Класс", "Метод", "Файл"],
mod_rows,
f"tbl-unit-{safe}",
)
lines.append("#pz-test-table(\n")
lines.append(" [Реестр модульных unit-тестов],\n")
lines.append(" 4,\n")
lines.append(" table.header(\n")
for h in ["", "Модуль", "Метод", "Проверяемое поведение"]:
lines.append(f" [{typst_escape(h)}],\n")
lines.append(" ),\n")
for row in data_rows:
cells = ", ".join(f"[{typst_escape(c)}]" for c in row)
lines.append(f" {cells},\n")
lines.append(") <tbl-unit-all>\n\n")
out.write_text("".join(lines), encoding="utf-8")
print(f"Wrote {out} ({len(rows)} tests)", flush=True)