"""구버전 EST Plus(.est): 394 바이트 레코드 쓰기→읽기 왕복, 단가비교 자리, 짝짓기.""" import json import subprocess from pathlib import Path from test_est_rea_parity import run ROOT = Path(__file__).resolve().parents[2] EST = ROOT / "tools/est-test" def node(code): out = subprocess.check_output( ["node", "-e", code], cwd=EST, text=True, encoding="utf-8" ) return json.loads(out) def test_extended_and_round_trip_of_sample(): result = node( """ const L=require('./legacy-estplus.cjs'),s=require('./sample.json'); const b=Buffer.alloc(10),xs=[0,1,-2.5,1234567.891,0.001,1e15].map(v=>(L.writeX(b,0,v),L.readX(b,0))); const buf=L.write(s),p=L.read(buf,'t'); const same=(t,keys)=>s.tables[t].every((r,i)=>keys.every(k=>Math.abs((Number(r[k])||0)-(Number(p.tables[t][i][k])||0))<1e-6)); console.log(JSON.stringify({xs, est:same('EstMainDat',['GN','TU','TC','NC','GC','JC','Level']), head:same('HeadMainDat',['GN','TC','NC','GC','JC']), cost:s.tables.CostMainDat.every((r,i)=>r.Index>0||(r.LineData||'')===(p.tables.CostMainDat[i].LineData||'')), title:p.tables.CostTitleDat[0].Name,labor:p.tables.LaborcDat.length, linked:p.tables.EstMainDat.find(r=>r.Name==='터파기').Code,auto:p.options.AutoCalc, cmp:L.compare(buf,2)[0]})); """) assert result["xs"] == [0, 1, -2.5, 1234567.891, 0.001, 1e15] assert result["est"] and result["head"] and result["cost"] assert result["title"] == "터파기" assert result["labor"] == 9 assert result["linked"] == "D0000000001" assert result["auto"] is False assert result["cmp"] == {"ONU": 66841, "OGU": 1336, "OJU": 0} def test_pair_flags_sum_mismatch_in_order(): result = run( "math", """(()=>{ const p=EstNative.empty();p.tables.HeadMainDat=[{Index:1,Code:'U1',Name:'a',NC:10,GC:5},{Index:0}, {Index:2,Code:'U2',Name:'b',NC:3}]; return EstEstPlus.pair(p,2,[{ONU:10,OGU:5,OJU:0},{ONU:4,OGU:0,OJU:0}]).map(r=>[r.Code,r.UsesCheck]); })()""", ) assert result == [["U1", False], ["U2", True]] def test_percent_quantity_machine_index_and_non_cp949_text(): result = node( """ const L=require('./legacy-estplus.cjs'); const p={name:'t',options:{},tables:{EstMainDat:[],HeadMainDat:[{Index:1,Name:'a'}, {Index:0,LinkIndex:0,Name:'사용고재',Unite:'%',GN:-500},{Index:0,LinkIndex:0,Name:'공구손료',Unite:'%',GN:3}, {Index:0,LinkIndex:0,Name:'x'+String.fromCodePoint(0x1F600)+'y',GN:1}], MachMainDat:[{Index:1,Name:'굴삭기',TC:10},{Index:-3,Selected:1,Name:'손료',GN:2,JC:5},{Index:-1,Selected:11,Name:'운전사',GN:1,NC:7}], CostMainDat:[],LaborcDat:[],StuffcDat:[],SilJukDat:[],Wonga:[]}}; const b=L.read(L.write(p),'t'); console.log(JSON.stringify({gn:b.tables.HeadMainDat.slice(1,3).map(r=>r.GN),name:b.tables.HeadMainDat[3].Name, mach:b.tables.MachMainDat.map(r=>[r.Index,r.Selected,r.TC])})); """) assert result["gn"] == [-500, 3] assert result["name"] == "x?y" assert result["mach"] == [[1, 0, 10], [-3, 1, 5], [-1, 11, 7]] def test_competitor_examples_keep_stored_values_and_round_trip_to_legacy(): folder = ROOT / "resources/knowledge/original/경쟁사 프로그램/EST Plus 2/example" files = sorted(folder.glob("*.estx")) assert len(files) >= 2 for file in files: parity = json.loads( subprocess.check_output( ["node", str(ROOT / "resources/tester/helper_est_archive.cjs"), str(file)], text=True, encoding="utf-8", timeout=300, ) ) assert parity["differences"] == [], file.name result = node( f""" const L=require('./legacy-estplus.cjs'),{{importEstx}}=require('./import-estx.cjs'); const p=importEstx(require('fs').readFileSync({json.dumps(str(file))}),'x'),b=L.read(L.write(p),'t'); const diff=(t,ks)=>p.tables[t].filter((r,i)=>ks.some(k=>Math.abs((Number(r[k])||0)-(Number(b.tables[t][i][k])||0))>1e-6)).length; console.log(JSON.stringify({{est:diff('EstMainDat',['GN','TC','NC','GC','JC']),head:diff('HeadMainDat',['GN','TC','NC','GC','JC']), cmp:L.compare(L.write(p),2).length===p.tables.HeadMainDat.filter(r=>r.Index>0).length}})); """ ) assert result == {"est": 0, "head": 0, "cmp": True}, file.name