from pathlib import Path APP = Path(r"C:\Users\User\Desktop\바이브코딩\pier_T\Code\교각모델링앱") def patch(rel, reps): p = APP / rel s = p.read_text(encoding="utf-8") for a, b in reps: assert s.count(a) == 1, (rel, a[:60]) s = s.replace(a, b) p.write_text(s, encoding="utf-8") patch("web/app.py", [ ("""import json import sys import threading import traceback""", """import json import os import sys import threading import traceback import uuid"""), ("""RHINO_LOCK = threading.Lock() # one Rhino job at a time""", """RHINO_LOCK = threading.Lock() # one Rhino job at a time ACTIVE = {} # job id -> thread of this server process"""), ("""def _gate_job(p, gate): jp = p.run_dir / f"{gate}_job.json" inputs = p.inputs_sha() log = [] def note(m): log.append(m) store.wjson(jp, {"state": "실행 중", "at": store.now(), "log": log}) with RHINO_LOCK: try: note("Rhino 작업 시작") rep = run_gate(gate, p.dir / "params.json", p.dir / "expected.json", p.run_dir, log=note) rep["inputs_sha"] = inputs store.wjson(p.run_dir / f"{gate}_gate.json", rep) store.wjson(jp, {"state": "완료", "at": store.now(), "log": log}) except Exception: store.wjson(jp, {"state": "오류", "at": store.now(), "log": log, "error": traceback.format_exc()})""", """def job_state(job): \"\"\"A queued/running job counts only while its thread lives in this server process; a job left behind by a stopped server (or test process) is shown as 중단됨 and may be run again.\"\"\" if job and job["state"] in ("대기", "실행 중"): t = ACTIVE.get(job.get("id")) if job.get("pid") != os.getpid() or not (t and t.is_alive()): return "중단됨" return job and job["state"] def _gate_job(p, gate, jid): jp = p.run_dir / f"{gate}_job.json" inputs = p.inputs_sha() log = [] def put(state, **kw): store.wjson(jp, dict({"id": jid, "pid": os.getpid(), "state": state, "at": store.now(), "log": log}, **kw)) def note(m): log.append(m) put("실행 중") with RHINO_LOCK: try: note("Rhino 작업 시작") rep = run_gate(gate, p.dir / "params.json", p.dir / "expected.json", p.run_dir, log=note) rep["inputs_sha"], rep["job_id"] = inputs, jid store.wjson(p.run_dir / f"{gate}_gate.json", rep) put("완료") except Exception: put("오류", error=traceback.format_exc())"""), (""" job = p.gate_status(gate)["job"] if job and job["state"] in ("대기", "실행 중"): flash(f"{gate}는 이미 실행 중입니다.", "fail") return redirect(url_for("gates", name=name)) # Mark the job as queued before the thread starts, so no request sees the previous run's "완료" state. store.wjson(p.run_dir / f"{gate}_job.json", {"state": "대기", "at": store.now(), "log": []}) threading.Thread(target=_gate_job, args=(p, gate), daemon=True).start()""", """ if job_state(p.gate_status(gate)["job"]) in ("대기", "실행 중"): flash(f"{gate}는 이미 실행 중입니다.", "fail") return redirect(url_for("gates", name=name)) # Mark the job as queued (with its id) before the thread starts, so no request sees the previous run's state. jid = uuid.uuid4().hex[:12] store.wjson(p.run_dir / f"{gate}_job.json", {"id": jid, "pid": os.getpid(), "state": "대기", "at": store.now(), "log": []}) t = threading.Thread(target=_gate_job, args=(p, gate, jid), daemon=True) ACTIVE[jid] = t t.start()"""), (""" rows = [dict(id=g, name=GATE_NAMES[g], blockers=gate_blockers(p, g), req=p.gate_requirements(g), **p.gate_status(g)) for g in gate_ids()] running = any(r["job"] and r["job"]["state"] in ("대기", "실행 중") for r in rows)""", """ rows = [dict(id=g, name=GATE_NAMES[g], blockers=gate_blockers(p, g), req=p.gate_requirements(g), **p.gate_status(g)) for g in gate_ids()] for r in rows: r["state"] = job_state(r["job"]) running = any(r["state"] in ("대기", "실행 중") for r in rows)"""), ]) patch("web/templates/gates.html", [ ("""{% if g.job and g.job.state in ('대기', '실행 중') %}
{{ g.job.state }}: {{ g.job.log[-1] if g.job.log else '' }}
{% elif g.job and g.job.state == '오류' %}오류
{{ g.job.error }}{% endif %}""",
"""{% if g.state in ('대기', '실행 중') %}{{ g.state }}: {{ g.job.log[-1] if g.job.log else '' }}
{% elif g.state == '중단됨' %}중단됨: 이전 실행이 끝나지 않은 채 서버가 멈췄습니다. 다시 실행하세요.
{% elif g.state == '오류' %}오류
{{ g.job.error }}{% endif %}"""),
])
patch("tools/test_web.py", [
("""def html(r):
return r.get_data(as_text=True)""",
"""def html(r):
return r.get_data(as_text=True)
def run_and_wait(c, P, g, limit=900):
\"\"\"POST run, then wait until the job with the id written at start-up is finished; returns (job, trace).\"\"\"
c.post(f"/pier/{P}/gate/{g}/run")
jid = (store.Pier(P).gate_status(g)["job"] or {}).get("id")
t0, trace = time.time(), []
while time.time() - t0 < limit:
job = store.Pier(P).gate_status(g)["job"] or {}
trace.append((round(time.time() - t0), job.get("id") == jid, job.get("state")))
if jid and job.get("id") == jid and job.get("state") in ("완료", "오류"):
return job, trace
time.sleep(3)
return None, trace"""),
(""" r = c.post(f"/pier/{P}/gate/{g}/run", follow_redirects=True)
t0 = time.time()
while True:
job = store.Pier(P).gate_status(g)["job"]
if job and job["state"] in ("완료", "오류"):
break
if time.time() - t0 > 900:
break
time.sleep(3)
st = store.Pier(P).gate_status(g)
rep = st["report"] or {}
check(f"{g} run via app -> PASS", job and job["state"] == "완료" and rep.get("verdict") == "PASS",
f"{rep.get('summary')} {time.time() - t0:.0f}s {job.get('error', '')[-200:] if job else ''}")""",
""" job, trace = run_and_wait(c, P, g)
rep = store.Pier(P).gate_status(g)["report"] or {}
check(f"{g} run via app -> PASS", bool(job) and job["state"] == "완료" and rep.get("verdict") == "PASS"
and rep.get("job_id") == job["id"], f"{rep.get('summary')} {trace[-1][0]}s {(job or {}).get('error', '')[-200:]}")"""),
(""" c.post(f"/pier/{P}/gate/G1/run")
t0 = time.time()
while time.time() - t0 < 600:
job = store.Pier(P).gate_status("G1")["job"]
if job and job["state"] in ("완료", "오류"):
break
time.sleep(3)
rep = store.Pier(P).gate_status("G1")["report"]
check("pile length +5 mm -> G1 FAIL (pile length)", rep["verdict"] == "FAIL" and "pile length" in rep["unplanned_fails"],
rep["unplanned_fails"])""",
""" job, trace = run_and_wait(c, P, "G1")
rep = store.Pier(P).gate_status("G1")["report"]
check("pile length +5 mm -> G1 FAIL (pile length)", bool(job) and rep.get("job_id") == job["id"]
and rep["verdict"] == "FAIL" and "pile length" in rep["unplanned_fails"],
f"{rep['unplanned_fails']} trace {trace[:2]}..{trace[-2:]}")"""),
(""" check("report: other modelled marks match", md.count("| 일치 |") == 35, md.count("| 일치 |"))""",
""" sec6 = md.split("## 6.")[1].split("## 7.")[0]
check("report: other modelled marks match (35)", sec6.count("| 일치 |") == 35, sec6.count("| 일치 |"))"""),
(""" # 8 a wrong input gives a FAIL gate""",
""" # 7b a job left "실행 중" by a stopped process must not block the gate forever
jp = TD / P / "runs" / "current" / "G1_job.json"
store.wjson(jp, {"id": "orphan", "pid": -1, "state": "실행 중", "at": store.now(), "log": ["x"]})
r = c.get(f"/pier/{P}/gates")
check("orphaned running job shown as 중단됨", "중단됨: 이전 실행" in html(r))
# 8 a wrong input gives a FAIL gate"""),
])
print("patched")