Phase 2: extract headless core modules (sras_format, scan_geometry, config)
- core/sras_format.py: THE v6 implementation — create_scan_file (writer, byte-identical to the old one, enforced against the Phase-0 goldens), SrasFile parser with frontier/truncation walk, and zero-copy mmap load_angle/load_row views for multi-GB files - core/scan_geometry.py: ScanPlan/AngleGeometry dataclasses, build_plan (rotated-bbox trig from MainWindow._build_scan_params), travel-limit validate_plan (limits now a StageLimits dataclass, not literals buried in the worker), format_eta + EtaEstimator (bounded deque) - core/config.py: ScanDefaults dataclass replaces the module-import-time dict globals. FIXES: editing any main-window port used to rewrite aui_defaults.json without helios_port, silently reverting the Helios port every time (test_helios_port_survives_partial_update covers it). Also drops the inert laser_freq_hz plumbing — scans always used the LASER_FREQ_HZ constant. - hardware/serial_util.py: shared 8N1 open + scored port enumeration (promoted from t3r_control_panel); helios_laser and the panel use it - sc3_aui_app.py and sras_scan_manager.py migrated onto core (three format implementations down to one); ScanWorker now takes a ScanPlan - tests: byte-identical writer vs golden, frontier over every truncation variant, mmap==eager, geometry vs golden fixtures + invariants, config round-trip. 28 passing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
"""core.config: round-trip, tolerance, and the helios_port regression.
|
||||
|
||||
The old dict-based writer rebuilt the JSON from only the main window's
|
||||
fields, silently discarding helios_port every time a port was edited.
|
||||
ScanDefaults.save() always writes every field.
|
||||
"""
|
||||
import json
|
||||
|
||||
from core.config import ScanDefaults
|
||||
|
||||
|
||||
def test_roundtrip(tmp_path):
|
||||
p = tmp_path / "defaults.json"
|
||||
d = ScanDefaults(t3r_port="/dev/ttyACM3", helios_port="/dev/ttyUSB9")
|
||||
d.save(p)
|
||||
loaded = ScanDefaults.load(p)
|
||||
assert loaded == d
|
||||
|
||||
|
||||
def test_missing_file_creates_defaults(tmp_path):
|
||||
p = tmp_path / "defaults.json"
|
||||
d = ScanDefaults.load(p)
|
||||
assert d == ScanDefaults()
|
||||
assert p.exists()
|
||||
|
||||
|
||||
def test_corrupt_file_falls_back(tmp_path):
|
||||
p = tmp_path / "defaults.json"
|
||||
p.write_text("{not json")
|
||||
assert ScanDefaults.load(p) == ScanDefaults()
|
||||
|
||||
|
||||
def test_unknown_keys_ignored(tmp_path):
|
||||
p = tmp_path / "defaults.json"
|
||||
p.write_text(json.dumps({"t3r_port": "/dev/ttyACM7", "laser_freq_hz": 20000.0}))
|
||||
d = ScanDefaults.load(p)
|
||||
assert d.t3r_port == "/dev/ttyACM7"
|
||||
assert d.helios_port == ScanDefaults().helios_port
|
||||
|
||||
|
||||
def test_helios_port_survives_partial_update(tmp_path):
|
||||
"""Regression: editing main-window ports must not clobber helios_port."""
|
||||
p = tmp_path / "defaults.json"
|
||||
ScanDefaults(helios_port="/dev/ttyUSB7").save(p)
|
||||
|
||||
d = ScanDefaults.load(p)
|
||||
d.t3r_port = "/dev/ttyACM1" # what _persist_defaults does
|
||||
d.save(p)
|
||||
|
||||
assert ScanDefaults.load(p).helios_port == "/dev/ttyUSB7"
|
||||
Reference in New Issue
Block a user