Py_sax2sab/verify.py
arda.aydin@ontrol.com.tr ec4019ea96 Convert both directions, and read sedona home from system.properties
Adds the reverse conversion (sab -> sax) and makes the tools usable without
remembering paths on the command line.

- sax.py: decoder ported from OfflineApp.decodeAppBinary + encodeAppXml.
  Rebuilds the tree from the firstChild/nextSibling chain and writes XML the
  way XWriter does. Reproduces java.lang.Float.toString, and rounds manifest
  defaults to f4 before comparing them - otherwise props equal to their
  default (0.1) get written where sedonac omits them.
- sab2sax.py: CLI dispatching on the input extension, like sedonac. sab.py
  still runs and hands off to it.
- config.py: sedona.home lives in system.properties. If it is missing or has
  no manifests/, a folder chooser opens and the answer is written back. File
  choosers for the input, save dialogs for the output; the default output name
  carries a timestamp so a run never overwrites the previous one.
- verify.py: checks both directions plus a sab -> sax -> sab round trip, and
  points at testprogram/ where the test files actually live.

All three checks pass byte-identical against the checked-in sedonac output.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 21:51:04 +03:00

106 lines
3.7 KiB
Python

"""Regression check: our output must be byte-identical to sedonac.exe's, both ways.
sax -> sab check_encode()
sab -> sax check_decode()
sab -> sax -> sab round trip
Usage: python verify.py [sedona_home]
Without an argument the home comes from system.properties (see config.py), which
pops a folder chooser the first time.
"""
import os, sys, subprocess, tempfile, shutil
import sab, sax, config
HERE = os.path.dirname(os.path.abspath(__file__))
HOME = sys.argv[1] if len(sys.argv) > 1 else config.get_sedona_home()
TESTS = os.path.join(HERE, "testprogram")
def sedonac(src, tmp):
"""Run sedonac on a copy of `src`; return the file it produced (needs a JRE)."""
work = os.path.join(tmp, os.path.basename(src))
shutil.copy(src, work)
exe = os.path.join(HOME, "bin", "sedonac.exe")
r = subprocess.run([exe, work], capture_output=True, text=True)
if r.returncode != 0:
print(" sedonac failed:\n" + r.stdout + r.stderr)
return None
other = ".sax" if work.endswith(".sab") else ".sab"
return os.path.splitext(work)[0] + other
def compare(label, reference, mine, binary):
a = open(reference, "rb").read()
b = open(mine, "rb").read()
if a == b:
print(" PASS %-28s %d bytes identical" % (label, len(a)))
return True
diffs = [i for i in range(min(len(a), len(b))) if a[i] != b[i]]
print(" FAIL %-28s sedonac=%d ours=%d, %d byte diffs" % (label, len(a), len(b), len(diffs)))
if diffs:
i = diffs[0]
if binary:
lo, hi = max(0, i - 12), i + 12
print(" first diff at offset %d" % i)
print(" sedonac %s" % a[lo:hi].hex())
print(" ours %s" % b[lo:hi].hex())
else:
line = a[:i].count(b"\n") + 1
print(" first diff at line %d" % line)
print(" sedonac %s" % a.splitlines()[line - 1])
print(" ours %s" % b.splitlines()[line - 1])
return False
def check_encode(sax_file, reference=None):
"""sax -> sab, against `reference` or a fresh sedonac run."""
tmp = tempfile.mkdtemp()
try:
reference = reference or sedonac(sax_file, tmp)
if reference is None:
return False
mine = os.path.join(tmp, "mine.sab")
sab.encode(sax_file, HOME, mine)
return compare(os.path.basename(sax_file) + " -> sab", reference, mine, True)
finally:
shutil.rmtree(tmp, ignore_errors=True)
def check_decode(sab_file, reference=None):
"""sab -> sax, against `reference` or a fresh sedonac run."""
tmp = tempfile.mkdtemp()
try:
reference = reference or sedonac(sab_file, tmp)
if reference is None:
return False
mine = os.path.join(tmp, "mine.sax")
sax.decode(sab_file, HOME, mine)
return compare(os.path.basename(sab_file) + " -> sax", reference, mine, False)
finally:
shutil.rmtree(tmp, ignore_errors=True)
def check_round_trip(sab_file):
"""sab -> sax -> sab must come back to the same bytes."""
tmp = tempfile.mkdtemp()
try:
mid = os.path.join(tmp, "mid.sax")
back = os.path.join(tmp, "back.sab")
sax.decode(sab_file, HOME, mid)
sab.encode(mid, HOME, back)
return compare(os.path.basename(sab_file) + " round trip", sab_file, back, True)
finally:
shutil.rmtree(tmp, ignore_errors=True)
if __name__ == "__main__":
print("sedona home: %s" % HOME)
saxf = os.path.join(TESTS, "test_normal.sax")
sabf = os.path.join(TESTS, "test_normal_sedonac.sab")
ok = check_encode(saxf, sabf)
ok &= check_decode(sabf, saxf)
ok &= check_round_trip(sabf)
sys.exit(0 if ok else 1)