#!/usr/bin/env python3 """Generate a single Typst unit-test table for chapter 5.""" from __future__ import annotations import re from pathlib import Path try: import yaml except ImportError: yaml = None # type: ignore def typst_escape(s: str) -> str: return s.replace("\\", "\\\\").replace("`", "\\`") 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") mod = p.parts[p.parts.index("src") - 1] for m in re.finditer(r"@Test[\s\S]*?fun\s+(?:`([^`]+)`|(\w+))\s*\(", text): name = m.group(1) or m.group(2) desc = describe_method(name, overrides) rows.append((mod, name, desc)) 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', ] data_rows = [] for i, (mod, name, desc) in enumerate(rows, start=1): data_rows.append([str(i), mod, name, desc]) 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(") \n\n") out.write_text("".join(lines), encoding="utf-8") print(f"Wrote {out} ({len(rows)} tests)", flush=True) if __name__ == "__main__": main()