Files
Aislo/resources/tester/test_berm_knee_progress.py
T
eomsangdonandClaude Opus 5 0ef32b5279 chore(tester): 시험을 resources/tester/ 로 옮김 — 창끼리 건너가게
⚠ **뿌리** — `tmp/` 는 창 사이에 안 건너감(실측 확인: 상대 창이 놓은 `tmp/_sync_probe.txt`
가 시간을 두고 두 번 봐도 안 보임). 그래서 **정본(등록부 스키마)만 건너가고 그것을 읽는
시험은 안 건너가** 오늘 두 번, 같은 시험이 **연 창은 통과·받은 창은 실패**가 됐음.

- `tmp/tests/*` 를 `resources/tester/` 로 **복사**(127 파일). 내용은 **한 줄도 안 고침**
- `tmp/tests` 는 **남겨 둠** — 되돌릴 자리(사용자 지시)
- 실행: `./venv/Scripts/python.exe -m pytest resources/tester/ -q`
  옮기기 전과 **같은 수**: 617 통과 / 22 건너뜀 / 실패 0

⇒ 이제 시험·예외·까닭이 **정본과 함께** 움직임. 오늘 세운 「예외는 정본 스키마에」와 짝임.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 17:12:30 +09:00

138 lines
5.7 KiB
Python

"""소단 + 다중 무릎이 **끝나는지** — 화면이 멈추던 자리(2026-09-07 실사고).
무슨 일이 있었나 — 사용자 공용 브라우저에서 **소단을 한 건 놓자 화면이 25분 넘게 멈췄다**
(렌더러가 1.4코어를 계속 태움). 서버(파이썬)로 같은 측점을 돌리면 0.01초에 끝나 서버는 멀쩡했다.
원인 — `cut_profile_points` 의 무릎 가지에서 **제자리 뒤집기**. 암 경계선의 기울기가
**암 경사와 토사 경사 사이**면
· 토사로 바꾸면 다음 걸음이 경계 **아래** → 「다시 암」
· 암으로 바꾸면 다음 걸음이 경계 **위** → 「다시 토사」
가 되고, 이때 보간 비율 `share` 가 0 이라 `knee_dist == dist` — **한 걸음도 안 나간다.**
`while dist < limit` 이 끝나지 않는다. `multi_knee` 는 **소단이 있으면 항상 참**이라
소단이 곧 지뢰였다.
고침 — 무릎은 **앞으로 나아갈 때만** 인정한다(직전 무릎과 같은 자리면 뒤집지 않고 한 걸음 간다).
파이썬·TS 짝 둘 다 같은 모양으로 넣었다.
"""
import json
import subprocess
import sys
import threading
from pathlib import Path
import pytest
PROJECT_ROOT = Path(__file__).resolve().parents[2]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from common_util.common_util_cross_berm import BermSpec, cut_profile_points # noqa: E402
TSC = PROJECT_ROOT / "config" / "node_modules" / "typescript" / "bin" / "tsc"
# 경계선 기울기를 두 설계 경사 **사이**에 둔다 — 암 1:0.4 는 수평 1m 에 2.5m 오르고
# 토사 1:1.0 은 1.0m 오른다. 그 사이(1.5m)면 어느 쪽으로 바꿔도 경계를 다시 넘는다.
BOUNDARY_RISE = 1.5
CUT_RATIO, SOIL_CUT_RATIO = 0.4, 1.0
BERM = BermSpec(0.5, 3.0, 0.0)
MAX_REACH_M = 50.0
def _boundary(dist: float) -> float:
return 100.0 + dist * BOUNDARY_RISE
def _run_with_timeout(seconds: float):
"""별도 스레드로 돌려 **끝나는지**를 본다 — 안 끝나면 그 자체가 결함이다."""
box: dict[str, object] = {}
def work() -> None:
box["points"] = cut_profile_points(
0.0, 100.0, CUT_RATIO, SOIL_CUT_RATIO, _boundary, BERM, MAX_REACH_M, True
)
thread = threading.Thread(target=work, daemon=True)
thread.start()
thread.join(seconds)
return None if thread.is_alive() else box.get("points")
def test_경계_기울기가_두_경사_사이여도_끝난다():
points = _run_with_timeout(8.0)
assert points is not None, "8초 안에 안 끝남 — 제자리 무릎이 되풀이되고 있다(화면이 멈춘다)"
assert len(points) > 2
def test_꼭짓점이_뒤로_가지_않는다():
"""무릎을 찍든 소단을 놓든 거리는 **늘 앞으로**만 간다."""
points = _run_with_timeout(8.0)
assert points is not None
for before, after in zip(points, points[1:], strict=False):
assert after[0] >= before[0] - 1e-9, f"거리가 뒤로 감: {before}{after}"
def test_소단이_없으면_종전_그대로():
"""이 고침이 소단 없는 경로(무릎 한 번)를 건드리지 않았는지."""
single = cut_profile_points(
0.0, 100.0, CUT_RATIO, SOIL_CUT_RATIO, _boundary, None, MAX_REACH_M, False
)
assert single[0] == (0.0, 100.0)
assert single[-1][0] > single[0][0]
def test_두_파일이_같은_방식으로_막는다():
"""짝이 갈리면 화면(TS)만 멈추고 서버(파이썬)는 멀쩡한 오늘 같은 일이 또 난다."""
py = (PROJECT_ROOT / "common_util" / "common_util_cross_berm.py").read_text(encoding="utf-8")
ts = (PROJECT_ROOT / "common_util" / "common_util_cross_berm.ts").read_text(encoding="utf-8")
assert "last_knee_dist" in py and "knee_dist > last_knee_dist" in py
assert "lastKneeDist" in ts and "kneeDist > lastKneeDist" in ts
@pytest.mark.skipif(not TSC.is_file(), reason="프론트엔드 의존성 미설치")
def test_TS_짝도_끝난다(tmp_path: Path):
"""실제로 멈춘 것은 **화면(TS)** 이었다 — 그쪽도 끝나는지 직접 돌려 본다."""
out = tmp_path / "js"
subprocess.run( # noqa: S603 — 고정 실행 파일
[
"node",
str(TSC),
str(PROJECT_ROOT / "common_util" / "common_util_cross_berm.ts"),
"--outDir",
str(out),
"--module",
"esnext",
"--target",
"es2022",
"--moduleResolution",
"bundler",
"--ignoreConfig",
],
cwd=str(PROJECT_ROOT),
check=True,
capture_output=True,
)
runner = out / "runner.mjs"
runner.write_text(
"""
import { cutProfilePoints } from "./common_util_cross_berm.js";
const boundary = (d) => 100 + d * %s;
const points = cutProfilePoints(0, 100, %s, %s, boundary, { widthM: 0.5, intervalM: 3, slopeDeg: 0 }, %s, true);
console.log(JSON.stringify({ n: points.length, last: points[points.length - 1] }));
"""
% (BOUNDARY_RISE, CUT_RATIO, SOIL_CUT_RATIO, MAX_REACH_M),
encoding="utf-8",
)
done = subprocess.run( # noqa: S603
["node", str(runner)], cwd=str(PROJECT_ROOT), capture_output=True, timeout=20, text=True
)
assert done.returncode == 0, done.stderr[:400]
ts = json.loads(done.stdout.strip().splitlines()[-1])
# 짝 대조 — 같은 기하에서 **꼭짓점 수와 끝점이 같아야** 한다. 한쪽만 고치면 여기서 걸린다.
py_points = _run_with_timeout(8.0)
assert py_points is not None
assert ts["n"] == len(py_points), f"꼭짓점 수가 갈림 — TS {ts['n']} vs 파이썬 {len(py_points)}"
assert abs(ts["last"][0] - py_points[-1][0]) < 1e-6
assert abs(ts["last"][1] - py_points[-1][1]) < 1e-6