arm-ka-fixtures/synthgen/tests/test_generate.py
2026-11-13 01:04:15 +04:00

169 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Сборка набора (спека §7): структура каталога, детерминированность, README прогона, граничные настройки."""
import hashlib
import time
from dataclasses import replace
from pathlib import Path
import pytest
from synthgen import generate as gen
from synthgen.__main__ import main
from synthgen.config import Config, load_config
SMALL = Config(group_size=3, periods=2, osv_counterparties_mean=5, no_inn_companies=1)
def _digest(root: Path) -> dict[str, str]:
return {str(p.relative_to(root)): hashlib.sha256(p.read_bytes()).hexdigest() for p in sorted(root.rglob("*")) if p.is_file()}
@pytest.fixture(scope="module")
def default_run(tmp_path_factory):
out = tmp_path_factory.mktemp("default")
manifest = gen.generate(Config(), out)
return out, manifest
def test_layout(default_run):
out, manifest = default_run
assert (out / "README.md").stat().st_size > 0
assert (out / "config.yaml").is_file()
assert (out / "directories" / "ecp-SYN.xlsx").is_file()
codes = [f"C{i:02d}" for i in range(1, 6)]
assert sorted(p.name for p in (out / "osv").iterdir()) == codes
assert sorted(p.name for p in (out / "statements").iterdir()) == codes
assert len(list(out.glob("osv/*/*/*.xlsx"))) == 5 * 4 * 9
assert len(list(out.glob("statements/*/*/ББ_*.xlsx"))) == 5 * 4
assert len(list(out.glob("statements/*/*/ОФР_*.xlsx"))) == 5 * 4
assert sorted(p.name for p in (out / "osv" / "C01").iterdir()) == ["2025-FY", "2025-Q1", "2025-Q2", "2025-Q3"]
# манифест перечисляет все файлы набора
on_disk = {p for p in out.rglob("*") if p.is_file()}
assert set(manifest.files) == on_disk
def test_determinism(tmp_path):
a, b, c = tmp_path / "a", tmp_path / "b", tmp_path / "c"
gen.generate(SMALL, a)
gen.generate(SMALL, b)
gen.generate(replace(SMALL, seed=2), c)
assert _digest(a) == _digest(b)
assert str(a) not in (a / "config.yaml").read_text(encoding="utf-8")
d = tmp_path / "d"
gen.generate(replace(SMALL, out=d), d)
assert _digest(a) == _digest(d) # путь результата не попадает в файлы
assert _digest(a) != _digest(c)
def test_out_not_empty(tmp_path, capsys):
out = tmp_path / "res"
gen.generate(SMALL, out)
with pytest.raises(FileExistsError):
gen.generate(SMALL, out)
junk = out / "old.txt"
junk.write_text("старое", encoding="utf-8")
gen.generate(SMALL, out, force=True)
assert not junk.exists()
assert (out / "README.md").is_file()
args = ["--set", "group_size=3", "--set", "periods=1", "--set", "osv_counterparties_mean=3", "--out", str(out)]
assert main(args) == 3
assert "не пуст" in capsys.readouterr().err
assert main([*args, "--force"]) == 0
assert str(out) in capsys.readouterr().out
def test_force_refuses_foreign_dir(tmp_path, capsys):
foreign = tmp_path / "foreign"
foreign.mkdir()
(foreign / "data.txt").write_text("чужое", encoding="utf-8")
with pytest.raises(FileExistsError):
gen.generate(SMALL, foreign, force=True)
assert (foreign / "data.txt").exists()
args = ["--set", "group_size=2", "--set", "periods=1", "--set", "osv_counterparties_mean=2", "--force"]
assert main([*args, "--out", str(foreign)]) == 3
assert "не похож на набор" in capsys.readouterr().err
repo = tmp_path / "repo"
gen.generate(SMALL, repo)
(repo / ".git").mkdir()
with pytest.raises(FileExistsError):
gen.generate(SMALL, repo, force=True)
assert (repo / ".git").exists()
not_dir = tmp_path / "file.txt"
not_dir.write_text("x", encoding="utf-8")
assert main([*args, "--out", str(not_dir)]) == 3
def test_default_out_is_prefix_dir(tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
assert main(["--set", "group_size=2", "--set", "periods=1", "--set", "osv_counterparties_mean=2", "--set", "prefix=ZZ"]) == 0
assert (tmp_path / "out" / "ZZ" / "README.md").is_file()
@pytest.mark.parametrize(
"overrides",
[
{"group_size": 2},
{"osv_counterparties_mean": 0},
{"group_size": 40, "periods": 1, "osv_counterparties_mean": 10},
{"no_inn_companies": 5},
{"vgo_mismatch_share": 1},
{"start": "2025-Q4", "periods": 2},
],
ids=["size2", "mean0", "size40", "all-no-inn", "all-mismatch", "from-q4"],
)
def test_edge_configs(tmp_path, overrides):
base = {"osv_counterparties_mean": 4, "periods": 1, **overrides}
cfg = load_config(None, [f"{k}={v}" for k, v in base.items()])
manifest = gen.generate(cfg, tmp_path / "x")
readme = (tmp_path / "x" / "README.md").read_text(encoding="utf-8")
assert readme.strip()
if cfg.group_size <= 5: # каждый файл набора открывается
import openpyxl
for path in manifest.files:
if path.suffix == ".xlsx":
openpyxl.load_workbook(path).close()
if not manifest.pairs:
assert "Пар ВГО нет" in readme
def _dec(v):
from decimal import Decimal
return Decimal(str(v))
def test_readme_lists_mismatches(tmp_path):
cfg = replace(SMALL, group_size=5, vgo_mismatch_share=_dec(1))
manifest = gen.generate(cfg, tmp_path / "x")
readme = (tmp_path / "x" / "README.md").read_text(encoding="utf-8")
mismatches = [(p, m) for p in manifest.pairs for m in p.mismatches]
assert mismatches
for p, m in mismatches:
line = next((ln for ln in readme.splitlines() if p.a in ln and "→" in ln and m.period_key in ln and f"{m.pct}" in ln), None)
assert line is not None, (p.a, p.b, m)
assert "выше порога" in line or "ниже порога" in line
def test_readme_notes(default_run):
out, _ = default_run
readme = (out / "README.md").read_text(encoding="utf-8")
for needle in ("90.01", "MULTIPLE_RULES", "правил", "80", "58", "За 3 месяца", "вариантн", "ручн", "платформ"):
assert needle in readme
for marker in ("M-4", "M-5", "T4.", "O-2", "O-3", "O-5", "O-6", "OPEN-QUESTION"):
assert marker not in readme
assert "не оракул" in readme
@pytest.mark.slow
def test_large_run_time(tmp_path):
cfg = Config(group_size=15, periods=6, osv_counterparties_mean=150)
t0 = time.monotonic()
manifest = gen.generate(cfg, tmp_path / "big")
elapsed = time.monotonic() - t0
print(f"large run: {elapsed:.1f} s, files={len(manifest.files)}")
assert elapsed < 300