chore(문서): 문서 사이트 운영 안 함(사용자) — docs/tools · mkdocs.yml · 시험 478 둘 지움 · 생성물 무시 줄 걷음 · docs = guide · raw
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C6BNBFXFndbqYxu6sZuknK
This commit is contained in:
@@ -1,134 +0,0 @@
|
||||
"""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)
|
||||
@@ -1,96 +0,0 @@
|
||||
"""PLAN 478-1 (요구 278) — 문서 추출 스크립트 셋(`docs/tools/extract_all.py`).
|
||||
|
||||
- 알려진 것 있음: computeMassHaul · 계산 등록부 항목 · POST /design/save · 세션 칸 몇 개.
|
||||
- 함수 id 중복 0 · calls · called_by 가 가리키는 id 모두 있음 · 등록부 TS 자리 모두 함수에 있음.
|
||||
- 다시 돌려도 같은 결과(정렬 고정).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
DATA = ROOT / "docs" / "_data"
|
||||
OUTPUTS = ["functions.json", "session_keys.json", "api.json", "api_unmatched.json", "registry.json"]
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
shutil.which("node") is None or not (ROOT / "docs/tools/node_modules/ts-morph").is_dir(),
|
||||
reason="node · ts-morph 없음(npm --prefix docs/tools install)",
|
||||
)
|
||||
|
||||
|
||||
def _run() -> dict[str, str]:
|
||||
subprocess.run( # noqa: S603 — 고정 명령
|
||||
[sys.executable, str(ROOT / "docs/tools/extract_all.py")],
|
||||
cwd=ROOT,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
timeout=300,
|
||||
)
|
||||
return {name: (DATA / name).read_text(encoding="utf-8") for name in OUTPUTS}
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def first() -> dict[str, str]:
|
||||
return _run()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def data(first) -> dict:
|
||||
return {name.removesuffix(".json"): json.loads(text) for name, text in first.items()}
|
||||
|
||||
|
||||
def test_known_function(data):
|
||||
f = {x["id"]: x for x in data["functions"]}
|
||||
mh = f["ts:common_util/common_util_mass_haul.ts#computeMassHaul"]
|
||||
assert mh["kind"] == "function" and mh["exported"] and mh["doc"]
|
||||
assert [p["name"] for p in mh["params"]][:2] == ["crossSections", "conversion"]
|
||||
assert mh["calls"] and mh["called_by"]
|
||||
assert any(x["lang"] == "py" for x in data["functions"])
|
||||
|
||||
|
||||
def test_ids_unique_and_links_exist(data):
|
||||
ids = [x["id"] for x in data["functions"]]
|
||||
assert len(ids) == len(set(ids))
|
||||
known = set(ids)
|
||||
for x in data["functions"]:
|
||||
assert set(x["calls"]) <= known, x["id"]
|
||||
assert set(x["called_by"]) <= known, x["id"]
|
||||
assert x.get("target") is None or x["target"] in known
|
||||
assert ids == sorted(ids)
|
||||
|
||||
|
||||
def test_registry(data):
|
||||
reg = {e["id"]: e for e in data["registry"]}
|
||||
assert {"route.polyline", "facility.follow"} <= set(reg)
|
||||
known = {x["id"] for x in data["functions"]}
|
||||
assert [e["ts"] for e in data["registry"] if e["ts"] and f"ts:{e['ts']}" not in known] == []
|
||||
|
||||
|
||||
def test_design_save_api(data):
|
||||
hit = [
|
||||
a
|
||||
for a in data["api"]
|
||||
if a["method"] == "POST" and a["path"] == "/api/projects/{project_id}/design/save"
|
||||
]
|
||||
assert len(hit) == 1
|
||||
assert hit[0]["handler"] == "py:B06_Section/B06_Section_Router_DesignSave.py#save_design"
|
||||
assert "ts:A00_Common/design/design_save_api.ts#postDesignSave" in hit[0]["callers"]
|
||||
assert set(data["api_unmatched"]) == {"ts_calls", "routes_without_caller"}
|
||||
|
||||
|
||||
def test_session_keys(data):
|
||||
keys = {k["key"]: k for k in data["session_keys"]}
|
||||
for key in ("section-context", "route-draft-hold", "masshaul-open"):
|
||||
assert keys[key]["reads"] or keys[key]["writes"], key
|
||||
assert list(keys) == sorted(keys)
|
||||
|
||||
|
||||
def test_rerun_same(first):
|
||||
assert _run() == first
|
||||
Reference in New Issue
Block a user