"""그림이 정본에서 나온 것인지 보는 검사기. `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('손그림\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()