Merge remote-tracking branch 'origin/sub_laptop_8' into sub_laptop_4
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
"""쉬운 단가산출 2단계 — 표 보고 고르기(`M01_EasyCalc_Table.ts`)의 식 읽기 · 새 식 짜기.
|
||||
|
||||
화면 TS 를 그 자리에서 트랜스파일해 node 로 돌림 — 화면 부품 · 서버는 빈 흉내(순수 함수만 부름).
|
||||
견본 식은 이 시험 안에 둠(진짜 마스터 내용에 기대지 않음).
|
||||
"""
|
||||
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
SOURCE = ROOT / "M01_MasterData" / "EasyCalc" / "M01_EasyCalc_Table.ts"
|
||||
|
||||
RUNNER = r"""
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const [root, file] = process.argv.slice(1);
|
||||
const ts = require(path.join(root, "config", "node_modules", "typescript"));
|
||||
const js = ts.transpileModule(fs.readFileSync(file, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const stub = new Proxy({}, { get: () => () => undefined });
|
||||
const box = { exports: {} };
|
||||
new Function("exports", "module", "require", js)(box.exports, box, () => stub);
|
||||
const m = box.exports;
|
||||
const jobs = JSON.parse(fs.readFileSync(0, "utf8"));
|
||||
const out = jobs.map((job) => {
|
||||
if (job.op === "read") return m.readFinds(job.formula);
|
||||
if (job.op === "swap") {
|
||||
const calls = m.readFinds(job.formula);
|
||||
const swaps = calls.map((c, i) => {
|
||||
const s = job.swaps[i];
|
||||
return s ? m.writeFind(c.table, s.args ?? c.args, s.col ?? c.col) : null;
|
||||
});
|
||||
return m.replaceFinds(job.formula, calls, swaps);
|
||||
}
|
||||
if (job.op === "literal") return m.literal(job.value, job.rule ?? "");
|
||||
return null;
|
||||
});
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
"""
|
||||
|
||||
|
||||
def run(jobs: list[dict]) -> list:
|
||||
if shutil.which("node") is None:
|
||||
pytest.skip("node 가 없음")
|
||||
done = subprocess.run(
|
||||
["node", "-e", RUNNER, str(ROOT), str(SOURCE)],
|
||||
input=json.dumps(jobs, ensure_ascii=False),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
check=True,
|
||||
)
|
||||
return json.loads(done.stdout)
|
||||
|
||||
|
||||
MESSANGI = "찾기(QF000421, 뒷길이=뒷길이, 돌=돌, 쌓기=쌓기).석공 * (1 + 증가율 / 100)"
|
||||
|
||||
|
||||
def test_식_읽기_흔한_꼴():
|
||||
[calls] = run([{"op": "read", "formula": MESSANGI}])
|
||||
assert len(calls) == 1
|
||||
c = calls[0]
|
||||
assert c["table"] == "QF000421" and c["col"] == "석공"
|
||||
assert [(a["key"], a["rhs"]) for a in c["args"]] == [
|
||||
("뒷길이", "뒷길이"),
|
||||
("돌", "돌"),
|
||||
("쌓기", "쌓기"),
|
||||
]
|
||||
assert MESSANGI[c["start"] : c["end"]] == "찾기(QF000421, 뒷길이=뒷길이, 돌=돌, 쌓기=쌓기).석공"
|
||||
|
||||
|
||||
def test_식_읽기_따옴표_쉼표_괄호_겹침():
|
||||
f = (
|
||||
"(찾기(QC000524, 형식=형식).특별인부 - 만약(정밀절단 == '아님', "
|
||||
"찾기(QC000583, 구분='가, 나 (다)').값, 0)) / 시공량"
|
||||
)
|
||||
[calls] = run([{"op": "read", "formula": f}])
|
||||
assert [c["table"] for c in calls] == ["QC000524", "QC000583"]
|
||||
assert calls[1]["args"] == [{"key": "구분", "rhs": "'가, 나 (다)'"}]
|
||||
assert calls[1]["col"] == "값"
|
||||
|
||||
|
||||
def test_식_읽기_못_읽는_꼴과_없는_꼴():
|
||||
out = run(
|
||||
[
|
||||
{"op": "read", "formula": "찾기(QF:{지역}, 구분='a').값"}, # {자리} 낀 키
|
||||
{"op": "read", "formula": "찾기(QF000001, 구분='a'"}, # 안 닫힘
|
||||
{"op": "read", "formula": "품 * 1.2"},
|
||||
{"op": "read", "formula": "'찾기(QF000001, 구분=1).값'"}, # 따옴표 글 안
|
||||
]
|
||||
)
|
||||
assert out[0] is None and out[1] is None
|
||||
assert out[2] == [] and out[3] == []
|
||||
|
||||
|
||||
def test_새_식_짜기_줄_고정과_값칸_바꾸기():
|
||||
[new] = run(
|
||||
[
|
||||
{
|
||||
"op": "swap",
|
||||
"formula": MESSANGI,
|
||||
"swaps": [
|
||||
{
|
||||
"args": [
|
||||
{"key": "뒷길이", "rhs": "35"},
|
||||
{"key": "돌", "rhs": "'깬잡석'"},
|
||||
{"key": "쌓기", "rhs": "쌓기"},
|
||||
],
|
||||
"col": "보통인부",
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
)
|
||||
assert new == (
|
||||
"찾기(QF000421, 뒷길이=35, 돌='깬잡석', 쌓기=쌓기).보통인부 * (1 + 증가율 / 100)"
|
||||
)
|
||||
|
||||
|
||||
def test_새_식_짜기_둘째_찾기만_바꿈_나머지_글_그대로():
|
||||
f = "찾기(QC1, 구분='가새').수량 * 찾기(QC2, 공기=공기).손율 / 100"
|
||||
[new] = run(
|
||||
[
|
||||
{
|
||||
"op": "swap",
|
||||
"formula": f,
|
||||
"swaps": [None, {"args": [{"key": "공기", "rhs": "'1개월'"}]}],
|
||||
}
|
||||
]
|
||||
)
|
||||
assert new == "찾기(QC1, 구분='가새').수량 * 찾기(QC2, 공기='1개월').손율 / 100"
|
||||
# 다시 읽어도 같은 모양(왕복)
|
||||
[calls] = run([{"op": "read", "formula": new}])
|
||||
assert calls[1]["args"] == [{"key": "공기", "rhs": "'1개월'"}]
|
||||
|
||||
|
||||
def test_칸_값을_식_글로():
|
||||
out = run(
|
||||
[
|
||||
{"op": "literal", "value": 25},
|
||||
{"op": "literal", "value": "깬잡석"},
|
||||
{"op": "literal", "value": "it's"}, # 따옴표 든 글은 못 씀
|
||||
{"op": "literal", "value": [20, 40], "rule": "초과·이하"},
|
||||
{"op": "literal", "value": [None, 60], "rule": "이하"},
|
||||
{"op": "literal", "value": [None, 60], "rule": "이상·미만"},
|
||||
{"op": "literal", "value": [80, None], "rule": "초과·이하"},
|
||||
{"op": "literal", "value": [80, None], "rule": "이상·미만"},
|
||||
]
|
||||
)
|
||||
assert out == ["25", "'깬잡석'", None, "30", "60", "59", "81", "80"]
|
||||
Reference in New Issue
Block a user