149 lines
6.2 KiB
Python
149 lines
6.2 KiB
Python
"""런 원장 검사기. 절차를 안 지킨 원장을 실제로 잡는지 본다.
|
|
|
|
이 검사기의 값은 「통과시키는 것」이 아니라 「안 지킨 것을 잡는 것」이라, 시험도 전부
|
|
위반을 넣어 걸리는지 보는 모양이다.
|
|
"""
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
SCRIPT = os.path.join(ROOT, "scripts", "verify-pipeline-run.py")
|
|
LEDGER = os.path.join(ROOT, "runs", "keycloak", "2026-09-07-2215", "run.json")
|
|
|
|
|
|
def run(path, *args):
|
|
p = subprocess.run([sys.executable, SCRIPT, path, *args],
|
|
capture_output=True, text=True, cwd=ROOT)
|
|
return p.returncode, p.stdout + p.stderr
|
|
|
|
|
|
@unittest.skipUnless(os.path.exists(LEDGER), "실증 런 원장이 없다")
|
|
class LedgerRules(unittest.TestCase):
|
|
"""실제로 돈 원장을 밑감으로 삼아 한 군데씩 어긴다."""
|
|
|
|
def setUp(self):
|
|
self.base = json.load(open(LEDGER, encoding="utf-8"))
|
|
self.dir = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.dir.cleanup)
|
|
|
|
def write(self, mutate):
|
|
run_ = json.loads(json.dumps(self.base))
|
|
mutate({s["id"]: s for s in run_["stages"]}, run_)
|
|
path = os.path.join(self.dir.name, "run.json")
|
|
json.dump(run_, open(path, "w", encoding="utf-8"), ensure_ascii=False)
|
|
return path
|
|
|
|
def test_실증한_원장은_통과한다(self):
|
|
code, out = run(LEDGER)
|
|
self.assertEqual(code, 0, out)
|
|
self.assertIn("PASS", out)
|
|
|
|
def test_영수증이_그_스킬의_문장이_아니면_잡는다(self):
|
|
path = self.write(lambda st, _: st["S5"].update(
|
|
skillEcho="이 문장은 그 스킬 어디에도 없다 정말로 없다 한 글자도"))
|
|
code, out = run(path)
|
|
self.assertEqual(code, 1)
|
|
self.assertIn("스킬 영수증이 그 스킬의 문장이 아니다", out)
|
|
|
|
def test_영수증이_비면_잡는다(self):
|
|
path = self.write(lambda st, _: st["S6"].update(skillEcho=""))
|
|
code, out = run(path)
|
|
self.assertEqual(code, 1)
|
|
self.assertIn("스킬 영수증이 없다", out)
|
|
|
|
def test_관문이_실패하면_잡는다(self):
|
|
def m(st, _):
|
|
st["S3"]["gates"][0]["exit"] = 1
|
|
path = self.write(m)
|
|
code, out = run(path)
|
|
self.assertEqual(code, 1)
|
|
self.assertIn("관문이 통과하지 못했다", out)
|
|
|
|
def test_관문이_빠지면_잡는다(self):
|
|
path = self.write(lambda st, _: st["S6"].update(gates=[]))
|
|
code, out = run(path)
|
|
self.assertEqual(code, 1)
|
|
self.assertIn("관문이 빠졌다", out)
|
|
|
|
def test_건너뛸_수_없는_단계를_건너뛰면_잡는다(self):
|
|
path = self.write(lambda st, _: st["S3"].update(
|
|
status="SKIPPED", skipReason="그냥"))
|
|
code, out = run(path)
|
|
self.assertEqual(code, 1)
|
|
self.assertIn("건너뛸 수 없는 단계를 건너뛰었다", out)
|
|
|
|
def test_건너뛴_사유가_없으면_잡는다(self):
|
|
path = self.write(lambda st, _: st["S4"].update(skipReason=""))
|
|
code, out = run(path)
|
|
self.assertEqual(code, 1)
|
|
self.assertIn("건너뛴 사유가 없다", out)
|
|
|
|
def test_없는_산출물을_적으면_잡는다(self):
|
|
path = self.write(lambda st, _: st["S1"].update(
|
|
outputs=["docs/keycloak/없는파일.md"]))
|
|
code, out = run(path)
|
|
self.assertEqual(code, 1)
|
|
self.assertIn("적어 낸 산출물이 디스크에 없다", out)
|
|
|
|
def test_다른_스킬을_쓰면_잡는다(self):
|
|
path = self.write(lambda st, _: st["S7"].update(skill="technical-visualizer"))
|
|
code, out = run(path)
|
|
self.assertEqual(code, 1)
|
|
self.assertIn("단계가 다른 스킬을 썼다", out)
|
|
|
|
def test_끝나지_않은_단계를_잡는다(self):
|
|
path = self.write(lambda st, _: st["S5"].update(status="RUNNING"))
|
|
code, out = run(path)
|
|
self.assertEqual(code, 1)
|
|
self.assertIn("끝나지 않은 단계가 있다", out)
|
|
|
|
def test_단계가_통째로_빠지면_잡는다(self):
|
|
def m(_, run_):
|
|
run_["stages"] = [s for s in run_["stages"] if s["id"] != "S6"]
|
|
path = self.write(m)
|
|
code, out = run(path)
|
|
self.assertEqual(code, 1)
|
|
self.assertIn("단계가 원장에 없다", out)
|
|
|
|
def test_곁증명의_영수증도_대조한다(self):
|
|
"""건너뛴 단계라도 곁증명을 냈으면 같은 잣대로 본다."""
|
|
proof = os.path.join(ROOT, "runs/keycloak/2026-09-07-2215/stage/S4/stage-report.json")
|
|
if not os.path.exists(proof):
|
|
self.skipTest("곁증명이 없다")
|
|
data = json.load(open(proof, encoding="utf-8"))
|
|
data["skillEcho"] = "그 스킬에 없는 문장을 곁증명에 적었다 정말로 없다"
|
|
bad = os.path.join(self.dir.name, "stage-report.json")
|
|
json.dump(data, open(bad, "w", encoding="utf-8"), ensure_ascii=False)
|
|
rel = os.path.relpath(bad, ROOT)
|
|
path = self.write(lambda st, _: st["S4"].update(outputs=[rel]))
|
|
code, out = run(path)
|
|
self.assertEqual(code, 1)
|
|
self.assertIn("곁증명의 스킬 영수증이 그 스킬의 문장이 아니다", out)
|
|
|
|
|
|
class Init(unittest.TestCase):
|
|
def test_틀에서_런을_연다(self):
|
|
with tempfile.TemporaryDirectory() as d:
|
|
path = os.path.join(d, "run.json")
|
|
p = subprocess.run(
|
|
[sys.executable, SCRIPT, "--init", path,
|
|
"--project", "keycloak", "--record", "docs/keycloak/final/document.md"],
|
|
capture_output=True, text=True, cwd=ROOT)
|
|
self.assertEqual(p.returncode, 0, p.stdout + p.stderr)
|
|
run_ = json.load(open(path, encoding="utf-8"))
|
|
self.assertEqual([s["id"] for s in run_["stages"]],
|
|
["S1", "S2", "S3", "S4", "S5", "S6", "S7"])
|
|
self.assertEqual(run_["project"], "keycloak")
|
|
# 갓 연 원장은 아직 아무 단계도 안 끝났으므로 통과하면 안 된다
|
|
code, out = run(path)
|
|
self.assertEqual(code, 1)
|
|
self.assertIn("끝나지 않은 단계가 있다", out)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|