Files
Aislo/resources/tester/test_478_docs.py
T
eomsangdonandClaude Opus 5.5 47a63d3fa4 feat(478-2 문서 사이트): 진짜 추출로 맞춤 — TypeDoc kind-dir(대소문자 겹침 막음) · 도구 대 추출 비교 규칙 · 공용 도구 묶음 · 손글 지문 · TS 메뉴 쪽
- TypeDoc router kind-dir — structure 는 같은 이름 인터페이스 · 함수가 Windows 에서 한 파일로 덮임 · 겹침 링크 안 함
- 비교: TS = export 함수 · 메서드 · 생성자 · 접근자(상속 · 숨은 생성자 뺌 · export * 는 원본으로) · 파이썬 = 안쪽 함수 뺀 전부 ↔ griffe(속성 · 자동 생성 __init__ 반영)
- groups.yml 공용 도구(common_util_* 끝 규칙) · 묶음 없음 0 · 손글 18 묶음 지문
- 결과: 함수 5,746 · 묶음 18 · 세션 칸 45 · 서버 주소 41 · strict 경고 0 · test_478_docs 9 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 19:22:10 +09:00

135 lines
4.6 KiB
Python

"""478-2 문서 사이트 검사 — 묶음 배정 · 손글 함수 · 함수 화면 도구 대 추출 · mkdocs --strict.
가짜 추출(fixtures/478_docs)은 늘 돎 · 진짜 추출(docs/_data)은 있을 때만.
"""
import re
import sys
import warnings
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "docs" / "tools"))
import build # noqa: E402
import group # noqa: E402
FIXTURE = Path(__file__).parent / "fixtures" / "478_docs"
REAL = ROOT / "docs" / "_data"
def make_site(data_dir: Path, out: Path) -> tuple[build.Site, dict]:
site = build.Site(build.load_data(data_dir), group.load_rules(), out)
return site, site.build()
def shown_anchors(site: build.Site) -> set[str]:
text = "".join(p.read_text(encoding="utf-8") for p in (site.src).glob("*/*.md"))
return set(re.findall(r"\{ #(f-[0-9a-f]{10}) \}", text))
def ts_mismatch(site: build.Site) -> tuple[list, list]:
"""추출 export TS ↔ TypeDoc — (추출만, TypeDoc 함수만)."""
rows = build.typedoc_entries(site.out)
every = {(f, n) for f, n, _, _ in rows}
ts = {(f["file"], f["name"]) for f in site.fns if f["lang"] == "ts" and f["exported"]}
names = {n for _, n in ts}
# 화면 없는 줄 = 다시 내보내기 — `export *` 는 추출이 원본 파일에서만 셈
funcs = {(f, n) for f, n, k, rel in rows if k in build.FN_KINDS and (rel or n not in names)}
return sorted(ts - every), sorted(funcs - ts)
def py_mismatch(site: build.Site) -> tuple[list, list]:
"""추출 파이썬(안쪽 함수 뺌) ↔ griffe — (추출만, griffe 만)."""
py = {
(f["file"], f["name"])
for f in site.fns
if f["lang"] == "py" and (f["kind"] == "method" or "." not in f["name"])
}
got = {(file, n) for file in {f for f, _ in py} for n in build.griffe_names(file)}
return sorted(py - got), sorted(got - py)
@pytest.fixture(scope="module")
def fake(tmp_path_factory):
return make_site(FIXTURE, tmp_path_factory.mktemp("fake"))
@pytest.fixture(scope="module")
def real(tmp_path_factory):
if not (REAL / "functions.json").exists():
pytest.skip("추출 없음 — docs/_data")
return make_site(REAL, tmp_path_factory.mktemp("real"))
def test_assign_rules():
data = build.load_data(FIXTURE)
page = group.load_rules()["pages"][0]
gid = {
f["name"]: g
for g, fns in group.assign(data["functions"], data["registry"], page["groups"]).items()
for f in fns
}
assert gid["stationKey"] == "stations" # 등록부 stations.key
assert gid["station_key"] == "stations" # 등록부 pair
assert gid["seatBornNodes"] == "profile" # 등록부 profile.seat
assert gid["median"] == "profile" # 파일 B05_Profile_Grade_*
assert gid["node_edits"] == "route_edit" # 파일 B05_Profile_Route_NodeEdits
assert gid["atomic_write_bytes"] == "common" # 파일 common_util_* 끝 규칙
assert gid["get_project_storage_path"] == group.NONE
def test_fake_site(fake):
site, rep = fake
assert shown_anchors(site) == {build.anchor(f["id"]) for f in site.fns}
assert rep["ungrouped"] == 1
assert rep["session_keys"] == 1 and rep["api"] == 1
assert "stations" in rep["stale_groups"] # 손글 지문 ≠ 가짜 목록
build.build_site(site.out, strict=True)
def test_fake_tools_match_extract(fake):
site, _ = fake
assert ts_mismatch(site) == ([], [])
assert py_mismatch(site) == ([], [])
def test_real_hand_refs(real):
_, rep = real
assert rep["missing_refs"] == [], "손글이 가리키는 함수가 추출에 없음"
def test_real_every_function_shown(real):
site, _ = real
assert shown_anchors(site) == {build.anchor(f["id"]) for f in site.fns}, "조용히 빠진 함수"
def test_real_typedoc_matches_extract(real):
only_extract, only_tool = ts_mismatch(real[0])
assert not only_extract and not only_tool, (
f"추출만 {only_extract[:10]} · TypeDoc 만 {only_tool[:10]}"
)
def test_real_griffe_matches_extract(real):
only_extract, only_tool = py_mismatch(real[0])
assert not only_extract and not only_tool, (
f"추출만 {only_extract[:10]} · griffe 만 {only_tool[:10]}"
)
def test_real_counts(real):
_, rep = real
if rep["undocumented_exports"]:
warnings.warn(f"설명 없는 export 함수 {len(rep['undocumented_exports'])}")
if rep["ungrouped"]:
warnings.warn(f"묶음 없는 함수 {rep['ungrouped']}")
assert rep["ungrouped"] < rep["functions"]
def test_real_strict_build(real):
site, _ = real
build.build_site(site.out, strict=True)