"""경고 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__)))) RECORD = os.path.join( "docs", "document-haness", "tech-log-studio", "pipeline-gate-exit-codes", "case", "case-exit-code-read-behind-a-pipe.md") def _package(out: str, before: str | None) -> subprocess.CompletedProcess: cmd = [sys.executable, "scripts/review-package.py", "document-haness", "--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() open(self.before, "w", encoding="utf-8").write( src.replace("열넷이 전부 `exit=0` 이었다.", "열넷이 전부 `exit=0` 이었을 수도 있다.")) 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) pres = json.load(open(out, encoding="utf-8"))["preservation"] self.assertTrue(pres["available"]) self.assertNotIn("reason", pres) # 안 돈 이유가 있을 리 없다 self.assertTrue(pres["before"]) self.assertEqual(64, len(pres["beforeSha256"])) 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()