"""Generate golden fixtures capturing PRE-REFACTOR behavior (Phase 0). Run once against the un-refactored sc3_aui_app.py; the outputs are committed under tests/golden/. After the refactor, tests compare the new implementations against these committed files — do NOT regenerate them against refactored code, that would defeat the purpose. Usage: .venv/bin/python tests/gen_goldens.py """ import json import shutil import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) import conftest # noqa: F401 (pyueye stub + offscreen) import sc3_aui_app as app GOLDEN = Path(__file__).parent / "golden" # ── Geometry fixtures ──────────────────────────────────────────────────────── GEOMETRY_CASES = [ # label, XS, YS, XD, YD, num_angles, row_spacing ("single_angle_square", "10.0", "10.0", "10.0", "10.0", "1", "0.25"), ("single_angle_rect", "10.0", "10.0", "80.0", "50.0", "1", "0.25"), ("three_angles", "5.0", "5.0", "20.0", "12.0", "3", "0.1"), ("five_angles", "0.0", "0.0", "30.0", "30.0", "5", "0.5"), ("seven_angles_asym", "12.5", "7.5", "42.0", "17.0", "7", "0.05"), ("two_angles_tiny", "1.0", "1.0", "0.02", "0.02", "2", "0.01"), ("flat_line_ydelta_zero", "10.0", "10.0", "5.0", "0.0", "1", "0.25"), ("fine_spacing", "3.0", "4.0", "2.5", "1.5", "4", "0.025"), ] GEOMETRY_ERROR_CASES = [ ("xd_zero", "10.0", "10.0", "0.0", "10.0", "1", "0.25"), ("xd_negative", "10.0", "10.0", "-5.0", "10.0", "1", "0.25"), ("spacing_zero", "10.0", "10.0", "10.0", "10.0", "1", "0.0"), ("angles_zero", "10.0", "10.0", "10.0", "10.0", "0", "0.25"), ("xs_not_number", "abc", "10.0", "10.0", "10.0", "1", "0.25"), ] class _W: """Stands in for a QLineEdit: _build_scan_params only calls .text().""" def __init__(self, text): self._text = text def text(self): return self._text def _fake_window(xs, ys, xd, yd, na, rs): fs = type("FakeMainWindow", (), {})() fs.x_start_edit = _W(xs) fs.y_start_edit = _W(ys) fs.x_delta_edit = _W(xd) fs.y_delta_edit = _W(yd) fs.num_angles_edit = _W(na) fs.row_spacing_edit = _W(rs) fs.scan_prefix_edit = _W("golden") fs.scan_save_dir_edit = _W("/tmp/golden-scans") return fs def build_geometry_fixtures(): out = {"constants": { "LASER_FREQ_HZ": app.LASER_FREQ_HZ, "SCAN_VELOCITY_MM_S": app.SCAN_VELOCITY_MM_S, "GR_ROTATION_SIGN": app.GR_ROTATION_SIGN, }, "cases": {}, "error_cases": {}} for label, *inputs in GEOMETRY_CASES: params = app.MainWindow._build_scan_params(_fake_window(*inputs)) out["cases"][label] = { "inputs": dict(zip(("XS", "YS", "XD", "YD", "num_angles", "row_spacing"), inputs)), "params": params, } for label, *inputs in GEOMETRY_ERROR_CASES: try: app.MainWindow._build_scan_params(_fake_window(*inputs)) raise AssertionError(f"error case {label} did not raise") except ValueError as e: out["error_cases"][label] = { "inputs": dict(zip(("XS", "YS", "XD", "YD", "num_angles", "row_spacing"), inputs)), "message": str(e), } with open(GOLDEN / "geometry.json", "w") as f: json.dump(out, f, indent=2) print(f"geometry.json: {len(out['cases'])} cases, {len(out['error_cases'])} error cases") # ── SRAS v6 file fixtures ──────────────────────────────────────────────────── SPF = 8 # samples per frame (synthetic, tiny) SAMPLE_RATE = 6.25e9 PREAMBLES = [f"WFMOUTPRE:CH{ch};SYNTHETIC;PT_FMT Y;XINCR 1.6E-10" for ch in app.SCAN_CHANNELS] BACKGROUND = bytes(range(SPF)) def _synthetic_frame(ai, ri, ci, fi): return bytes((ai * 7 + ri * 5 + ci * 3 + fi + s) % 256 for s in range(SPF)) def build_sras_fixtures(): # Geometry via the real pre-refactor code path: 2 angles, 3 rows, 4 frames. params = app.MainWindow._build_scan_params( _fake_window("1.0", "1.0", "0.02", "0.02", "2", "0.01")) per_angle = params["per_angle"] angles = [pa["angle"] for pa in per_angle] complete = GOLDEN / "complete.sras" f = app._open_scan_file( complete, angles, per_angle, params["x_start_nominal"], params["y_start_nominal"], params["x_delta_nominal"], params["y_delta_nominal"], params["row_spacing"], app.SCAN_VELOCITY_MM_S, app.LASER_FREQ_HZ, SPF, SAMPLE_RATE, PREAMBLES, BACKGROUND, ) try: data_start = f.tell() for ai, pa in enumerate(per_angle): for ri in range(pa["n_rows"]): for ci in range(len(app.SCAN_CHANNELS)): for fi in range(pa["n_frames"]): f.write(_synthetic_frame(ai, ri, ci, fi)) finally: f.close() info = app._read_sras_header(complete) assert info["data_start_offset"] == data_start row_bytes = info["n_channels"] * per_angle[0]["n_frames"] * SPF * info["bytes_per_sample"] angle_bytes = [row_bytes * pa["n_rows"] for pa in per_angle] def _truncate(name, size): dst = GOLDEN / name shutil.copyfile(complete, dst) with open(dst, "r+b") as g: g.truncate(data_start + size) return dst variants = { "complete.sras": complete, "trunc_midrow_a1.sras": _truncate( "trunc_midrow_a1.sras", angle_bytes[0] + row_bytes + row_bytes // 2), "trunc_rowboundary_a1.sras": _truncate( "trunc_rowboundary_a1.sras", angle_bytes[0] + 2 * row_bytes), "trunc_angleboundary.sras": _truncate( "trunc_angleboundary.sras", angle_bytes[0]), "trunc_midrow_a0.sras": _truncate( "trunc_midrow_a0.sras", row_bytes + row_bytes // 2), "header_only.sras": _truncate("header_only.sras", 0), } expected = {"header": info, "statuses": {}} for name, path in variants.items(): expected["statuses"][name] = app._compute_angle_status(path, info) with open(GOLDEN / "sras_expected.json", "w") as f_json: json.dump(expected, f_json, indent=2) print(f"sras fixtures: {len(variants)} files, " f"data block {sum(angle_bytes)} bytes, row_bytes={row_bytes}") if __name__ == "__main__": GOLDEN.mkdir(exist_ok=True) build_geometry_fixtures() build_sras_fixtures() print("done")