Files
document-haness/scripts/tests/test_figure_provenance.py
T
DongHyeonkaandClaude Opus 5 ab59130196 chore: 이전 세션이 남긴 변경을 커밋한다
이번 파이프라인 작업과 무관하게 작업 트리에 남아 있던 것을 그대로 올린다.
사용자가 「전부 커밋」으로 정했고, 이번 작업과 섞이지 않게 커밋만 나눴다.

대부분은 clean-architecture-backend-template 의 그림 정본 재배치다 —
final/assets/diagrams/<이름>/ 에 있던 것이 CLAUDE.md 가 적은 배치인
final/assets/<이름>/ 로 옮겨졌고 .techviz/<이름>/ 이 함께 들어왔다.
삽입 줄의 대부분(3.15M)이 그 .techviz context.json 이다.

그 밖에 ca-tmpl·document-haness 의 정리, .claude/agents/ 열한 개,
writing-practitioner-guides 스킬, .playwright-mcp 세션 산출물,
scripts/check-ssot-facts.py 와 그 시험이 들어 있다.

이 커밋의 내용은 내가 만든 것이 아니라 이전 세션이 남긴 것이고 검증하지 않았다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 11:02:02 +09:00

90 lines
3.8 KiB
Python

"""그림이 정본에서 나온 것인지 보는 검사기.
`spec.json` 의 간선과 SVG 의 경로를 직접 견주려면 렌더러가 id 를 어떻게 붙이는지 알아야
한다. **정본을 거쳤는지만 보면 렌더러를 몰라도 된다** — 그리고 손으로 고친 그림이
화살표 뒤집기의 모양이다.
"""
import contextlib
import importlib.util
import os
import shutil
import subprocess
import tempfile
import unittest
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
TOOL = os.path.join(ROOT, "scripts", "check-figure-provenance.py")
_spec = importlib.util.spec_from_file_location("check_figure_provenance", TOOL)
fp = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(fp)
SVG = ("docs/virtualization/final/assets/diagrams/"
"guest-memory-address-translation-path/guest-memory-address-translation-path.svg")
SPEC = ("docs/virtualization/final/.techviz/"
"guest-memory-address-translation-path/spec.json")
def _cli(*args):
return subprocess.run(["python3", TOOL, *args], cwd=ROOT,
capture_output=True, text=True)
@contextlib.contextmanager
def _a_figure_without_a_spec():
"""`.techviz/<이름>/spec.json` 이 없는 그림 한 장짜리 프로젝트를 만들어 준다.
이 상태를 실재 프로젝트 이름으로 가리키면 그 프로젝트의 그림이 전부 정본을 갖는
순간 시험이 깨지고, 프로젝트가 빠지면 `verify()` 가 그림 0장을 보고 「못 본 그림」도
0 이 되어 시험이 엉뚱한 이유로 깨진다. 이름이 아니라 상태가 필요한 시험이다.
"""
docs = os.path.join(ROOT, "docs")
path = tempfile.mkdtemp(prefix="zz-no-spec-", dir=docs)
try:
assets = os.path.join(path, "final", "assets")
os.makedirs(assets)
with open(os.path.join(assets, "handmade.svg"), "w", encoding="utf-8") as fh:
fh.write('<svg xmlns="http://www.w3.org/2000/svg"><title>손그림</title></svg>\n')
yield os.path.basename(path)
finally:
shutil.rmtree(path, ignore_errors=True)
class ProvenanceTest(unittest.TestCase):
def test_every_figure_in_this_repository_passes(self):
"""대조군. 지금 저장소의 그림에 하나라도 걸리면 정책이 과하다."""
p = _cli("--samples", "1")
self.assertEqual(0, p.returncode, p.stdout + p.stderr)
def test_a_hand_edited_svg_is_caught(self):
"""SVG 만 고치고 spec 은 그대로면 정본을 안 거친 그림이다.
고치고 나서 반드시 되돌린다 — 재현용 변경을 남기지 않는 것까지가 검증이다.
"""
svg = os.path.join(ROOT, SVG)
if not os.path.exists(svg):
self.skipTest("견줄 그림이 없다")
original = open(svg, encoding="utf-8").read()
try:
open(svg, "w", encoding="utf-8").write(
original.replace("marker-end", "marker-start", 1))
p = _cli("virtualization")
self.assertEqual(1, p.returncode, p.stdout)
self.assertIn("정본을 거치지 않고 고친 그림", p.stdout)
finally:
open(svg, "w", encoding="utf-8").write(original)
self.assertEqual(0, _cli("virtualization").returncode)
def test_a_figure_without_a_spec_is_counted_not_flagged(self):
"""정본이 없는 그림은 이 검사기가 볼 것이 아니다. 세기만 한다."""
with _a_figure_without_a_spec() as project:
rep = fp.verify(project)
self.assertEqual(0, rep.error_count)
self.assertGreater(rep.facts.get("정본이 없어 못 본 그림", 0), 0)
def test_a_missing_project_is_not_reported_as_clean(self):
self.assertEqual(2, _cli("nosuchxyz").returncode)
if __name__ == "__main__":
unittest.main()