Files
Aislo/resources/tester/test_m01_cache.py
T
eomsangdonandClaude Opus 5 3c6e68231c perf(M01): 로직·조합 목록이 마스터를 한 번만 읽고 막힘은 미리 세어 둠 (일감 32)
- `_Store_Cache` 신설 — 자국(시각·크기)이 바뀐 파일만 다시 읽음 · 목록은 검사를 돌리지 않고 세어 둔 막힘을 씀
- 요소·표가 바뀌면 모든 로직을, 로직 파일이 바뀌면 그 파일과 그 로직을 부르던 줄만 다시 셈
- 저장 뒤에는 고친 파일을 바로 다시 읽게 알려 줌 — 저장 직후 목록 2.5초 → 0.06초
- `/logics` 둘째 번부터 2.5초 → 0.02초 · `/combos` 0.3초 → 0.01초 · 켠 뒤 첫 번만 3.4초
- 시험 `test_m01_cache.py` 다섯 — 세어 둔 막힘이 줄마다 검사한 값과 같음 · 거름 그대로 · 저장 직후 비침

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgUhN55Z3BCYhUJTjwTNfj
2026-09-22 02:17:49 +09:00

113 lines
4.7 KiB
Python

"""M01 목록이 마스터를 한 번만 읽는지 — `M01_MasterData_Store_Cache`.
목록(`/logics` · `/combos`)은 요청마다 마스터를 새로 읽지 않고 막힘도 미리 세어 둔 값을 씀.
여기서 보는 것 = ① 세어 둔 값이 줄마다 검사한 것과 같음 ② 파일이 바뀌면 바로 다시 읽음
③ 저장 직후 목록에 바로 비침 ④ 다른 파일의 로직이 바뀌면 그것을 부르던 줄도 다시 셈.
"""
from __future__ import annotations
import json
import shutil
from pathlib import Path
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from M01_MasterData import M01_MasterData_Router as router_module
from M01_MasterData import M01_MasterData_Store as store
REAL = store.FOLDER
CALLER = "GC000994" # 로직_건설품셈_01장_공통.json — 아래 로직을 부름
CALLEE = "GC000276" # 로직_건설품셈_08장_건설기계.json — 다른 파일에 있음
@pytest.fixture
def client(tmp_path: Path, monkeypatch) -> TestClient:
for path in REAL.glob("*.json"):
if not path.name.startswith("_") or path.name == store.mk.BOOK.name:
shutil.copy(path, tmp_path / path.name)
monkeypatch.setattr(store, "FOLDER", tmp_path)
app = FastAPI()
app.include_router(router_module.router)
return TestClient(app)
def _logics(client: TestClient, **params) -> list[dict]:
res = client.get("/api/m01/logics", params=params)
assert res.status_code == 200, res.text
return res.json()["logics"]
def _write(file: str, change) -> None:
"""정본 파일을 손으로 고침(저장 길을 거치지 않음) — 바뀐 것을 알아채는지 보려고."""
path = store.FOLDER / file
data = json.loads(path.read_text(encoding="utf-8"))
change(data)
path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8", newline="\n")
def test_세어_둔_막힘이_줄마다_검사한_것과_같다(client: TestClient) -> None:
listed = _logics(client)
files, whole = store.loaded()
rows = {
str(r.get("키")): r for _, d in files.items() if d.get("그룹") == "로직" for r in d["줄"]
}
assert len(listed) == len(rows)
for one in listed:
reasons = store.cm.mf.check_logic(whole, rows[one["키"]])[0]
assert one["reasons"] == reasons and one["blocked"] is bool(reasons)
def test_거름이_그대로다(client: TestClient) -> None:
whole = _logics(client)
sub, detail = whole[0]["구분"], whole[0]["상세구분"]
by_sub = _logics(client, sub=sub, detail=detail)
assert by_sub == [x for x in whole if (x["구분"], x["상세구분"]) == (sub, detail)]
assert _logics(client, blocked=1) == [x for x in whole if x["blocked"]]
assert _logics(client, blocked=0) == [x for x in whole if not x["blocked"]]
assert _logics(client, q=CALLER) == [x for x in whole if x["키"] == CALLER]
def test_안_바뀌면_다시_읽지_않고_바뀌면_바로_읽는다(client: TestClient) -> None:
files, whole = store.loaded()
assert store.loaded() == (files, whole) # 같은 것을 그대로 씀
_write("로직_건설품셈_01장_공통.json", lambda d: d["줄"][0].update(비고="자국 바뀜"))
fresh, _ = store.loaded()
assert fresh is not files
assert fresh["로직_건설품셈_01장_공통.json"]["줄"][0]["비고"] == "자국 바뀜"
def test_로직을_저장하면_목록에_바로_비친다(client: TestClient) -> None:
one = client.get("/api/m01/logic", params={"key": CALLER}).json()
assert [x for x in _logics(client, q=CALLER)][0]["이름"] == one["logic"]["이름"]
row = {**one["logic"], "이름": "저장 뒤 이름"}
res = client.post(
"/api/m01/save",
json={
"files": [
{
"file": one["file"],
"version": one["version"],
"changes": [{"op": "edit", "key": CALLER, "row": row}],
}
]
},
)
assert res.status_code == 200, res.text
listed = [x for x in _logics(client) if x["키"] == CALLER]
assert listed and listed[0]["이름"] == "저장 뒤 이름"
def test_부르는_로직이_바뀌면_다른_파일의_줄도_다시_센다(client: TestClient) -> None:
assert [x for x in _logics(client) if x["키"] == CALLER][0]["blocked"] is False
def add_input(data: dict) -> None:
row = next(r for r in data["줄"] if r["키"] == CALLEE)
row["입력"] = [*row.get("입력", []), {"이름": "새입력", "종류": "수"}]
_write("로직_건설품셈_08장_건설기계.json", add_input) # 부르는 쪽 파일은 그대로
caller = [x for x in _logics(client) if x["키"] == CALLER][0]
assert caller["blocked"] is True and any("새입력" in x for x in caller["reasons"])