Files
document-haness/scripts/tests/test_review_scope.py
T
DongHyeonkaandClaude Opus 5 14137382fe fix(tests): 새 시험의 픽스처를 TechLog 로 옮겨 PIPELINE CONTRACT 를 되돌린다
첫 제출에서 verify-pipeline 의 FORBIDDEN_LITERAL 이 새 시험을 걸었다. 픽스처가
옛 저장소 이름과 같은 이름의 docs 프로젝트를 가리키고 있었다.

글자를 쪼개 피하지 않았다. verify-pipeline.py:172 자신이 쓰는 수법이지만 그건 검사기가
물으려던 것에 답하는 게 아니라 글자만 피하는 것이다. 재는 것이 「비교를 돌렸나」이지 그
기록의 내용이 아니므로 프로젝트를 TechLog 로 옮겼다.

양성 대조를 함께 넣었다 — available: true 만 재면 픽스처가 조용히 같아졌을 때 시험이
초록인 채로 아무것도 안 재게 된다. warnings 가 비지 않았는지와 그중에 「유보 감소」가
있는지를 잰다.

검사기가 「옛 저장소 이름에 의존한다」와 「그 이름의 docs 프로젝트를 가리킨다」를 못 가르는
것은 고치지 않고 보고서 §7.6 에 발견으로 적었다. 검사기 수정은 C 몫이다.

  PIPELINE CONTRACT: PASS
  python3 -m unittest discover -s scripts/tests — Ran 266 · OK (skipped=13)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-11 09:25:03 +09:00

166 lines
7.8 KiB
Python

"""경고 0 건에는 두 가지가 있다 — 「봤는데 없었다」와 「볼 것이 아직 없었다」.
이 묶음의 **막는** 경고는 전부 편집 전후 보존 비교에서 나온다. `review-package.py` 에서
`_warn` 을 부르는 자리가 둘뿐이고 둘 다 `if preservation.get("available"):` 안이다.
그 비교는 `--before` 를 줘야 돈다. 빼면 `studio-save.py` 의 검토 관문이 **아무 줄도 안
남기고 통과한다.**
종료 코드는 양쪽 다 0 이다. 새 관문이 아니라 **읽는 계약**이라 갈리는 것은 찍는 문구다.
**같은 문구가 나오면 고친 것이 아니다.**
"""
from __future__ import annotations
import json
import os
import shutil
import subprocess
import sys
import tempfile
import unittest
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# 어느 프로젝트를 쓰든 상관없다 — 재는 것은 「비교를 돌렸나」이지 그 기록의 내용이 아니다.
# `verify-pipeline.py:172` 의 `FORBIDDEN_LITERAL` 이 `scripts/` 아래 텍스트에서 옛 저장소
# 이름을 찾는데, **그 이름의 `docs/` 프로젝트를 가리키는 것과 가리지 못한다.** 글자를
# 쪼개 피하지 않고 다른 프로젝트를 쓴다 — 피하는 것은 검사기가 물으려던 것에 답하는 게 아니다.
PROJECT = "TechLog"
RECORD = os.path.join(
"docs", PROJECT, "tech-log-studio", "addresses-frozen-at-publish-time",
"case", "case-a-link-that-pointed-at-itself.md")
# 윤문 전 사본에만 넣을 유보 표현. `HEDGES` 의 「보인다」다 —
# 이것이 사라지는 것이 `hedgesDropped` 이고, 그 자리가 이 묶음의 **막는** 경고다
ASSERTED = "눌러도 아무 일이 없었다."
HEDGED = "눌러도 아무 일이 없었던 것으로 보인다."
def _package(out: str, before: str | None) -> subprocess.CompletedProcess:
cmd = [sys.executable, "scripts/review-package.py", PROJECT,
"--record", RECORD, "-o", out]
if before:
cmd += ["--before", before]
return subprocess.run(cmd, cwd=ROOT, capture_output=True, text=True)
class PreservationScopeTest(unittest.TestCase):
"""묶음이 자기 입력을 적는가. 빠진 칸과 null 은 다르다."""
def setUp(self):
self.tmp = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, self.tmp, ignore_errors=True)
# 윤문 전 사본. 한 문장만 다르게 둔다 — 비교가 실제로 돌아야 한다
self.before = os.path.join(self.tmp, "before.md")
src = open(os.path.join(ROOT, RECORD), encoding="utf-8").read()
self.assertIn(ASSERTED, src, "고정문이 기록에서 사라졌다 — 픽스처를 다시 짠다")
open(self.before, "w", encoding="utf-8").write(src.replace(ASSERTED, HEDGED))
def test_without_before_the_package_says_it_did_not_look(self):
out = os.path.join(self.tmp, "nb.json")
r = _package(out, None)
self.assertEqual(0, r.returncode, r.stderr)
pkg = json.load(open(out, encoding="utf-8"))
pres = pkg["preservation"]
self.assertFalse(pres["available"])
self.assertEqual("NO_BEFORE", pres["reason"])
# 빠진 칸과 null 은 다르다 — 안 줬다는 것을 **명시적으로** 적는다
self.assertIn("before", pres)
self.assertIsNone(pres["before"])
self.assertIsNone(pres["beforeSha256"])
def test_with_before_the_package_records_its_input(self):
out = os.path.join(self.tmp, "wb.json")
r = _package(out, self.before)
self.assertEqual(0, r.returncode, r.stderr)
pkg = json.load(open(out, encoding="utf-8"))
pres = pkg["preservation"]
self.assertTrue(pres["available"])
self.assertNotIn("reason", pres) # 안 돈 이유가 있을 리 없다
self.assertTrue(pres["before"])
self.assertEqual(64, len(pres["beforeSha256"]))
# **`available: true` 만으로는 비교가 무언가를 봤다는 뜻이 아니다.** 픽스처가
# 조용히 같아지면 이 시험은 초록인 채로 아무것도 안 재게 된다 — 양성 대조다
self.assertTrue(pkg["warnings"], "유보가 사라진 것을 못 잡았다 — 픽스처를 다시 짠다")
self.assertTrue(any(w["id"] == "유보 감소" for w in pkg["warnings"]),
[w["id"] for w in pkg["warnings"]])
def test_the_two_packages_print_different_lines(self):
"""**같은 문구가 나오면 고친 것이 아니다.**"""
a = _package(os.path.join(self.tmp, "a.json"), None)
b = _package(os.path.join(self.tmp, "b.json"), self.before)
self.assertEqual(0, a.returncode)
self.assertEqual(0, b.returncode)
self.assertIn("편집 전후 비교를 안 돌렸다", a.stdout)
self.assertIn("사정권 밖", a.stdout)
self.assertNotIn("사정권 밖", b.stdout)
class ReviewGateWordingTest(unittest.TestCase):
"""저장 게이트가 「0 건」을 두 가지로 찍는가. 종료 코드는 양쪽 다 0 이다."""
def setUp(self):
path = os.path.join(ROOT, "scripts", "studio-save.py")
import importlib.util as u
spec = u.spec_from_file_location("studio_save", path)
self.ss = u.module_from_spec(spec)
spec.loader.exec_module(self.ss)
def _gate(self, preservation):
import io
import contextlib
buf = io.StringIO()
with contextlib.redirect_stderr(buf):
out = self.ss._review_gate([], None, __file__, preservation)
return out, buf.getvalue()
def test_it_says_it_looked_when_the_comparison_ran(self):
out, err = self._gate({"available": True})
self.assertEqual([], out)
self.assertIn("편집 전후 비교를 돌렸고", err)
self.assertNotIn("사정권 밖", err)
def test_it_says_it_did_not_look_when_there_was_no_before(self):
out, err = self._gate({"available": False, "reason": "NO_BEFORE"})
self.assertEqual([], out)
self.assertIn("본 것이 없다", err)
self.assertIn("--before", err)
self.assertIn("사정권 밖", err)
def test_it_names_the_other_cause_too(self):
"""`available: false` 의 두 원인을 같은 문구로 찍지 않는다."""
_, a = self._gate({"available": False, "reason": "NO_BEFORE"})
_, b = self._gate({"available": False, "reason": "CHECKER_UNREADABLE"})
self.assertIn("check-preservation.py 를 못 읽었다", b)
self.assertNotEqual(a, b)
def test_a_package_that_says_nothing_is_not_read_as_green(self):
"""옛 묶음에는 `reason` 이 없다. 그래도 초록으로 읽지 않는다."""
_, err = self._gate({"available": False})
self.assertIn("본 것이 없다", err)
self.assertIn("사정권 밖", err)
class PlanSummaryLineTest(unittest.TestCase):
"""사람이 보는 마지막 줄에서도 갈려야 한다. 여기만 그냥 0 이면
사정권 밖이 초록으로 돌아온다."""
def setUp(self):
path = os.path.join(ROOT, "scripts", "studio-save.py")
import importlib.util as u
spec = u.spec_from_file_location("studio_save", path)
self.ss = u.module_from_spec(spec)
spec.loader.exec_module(self.ss)
def test_the_summary_splits_zero_two_ways(self):
looked = self.ss._zero_note({"preservation": {"available": True}})
blind = self.ss._zero_note({"preservation": {"available": False,
"reason": "NO_BEFORE"}})
self.assertNotEqual(looked, blind)
self.assertIn("사정권 밖", blind)
self.assertNotIn("사정권 밖", looked)
def test_an_old_package_without_preservation_is_not_green(self):
self.assertIn("사정권 밖", self.ss._zero_note({}))
if __name__ == "__main__":
unittest.main()