Files
StarPilot/openpilot/starpilot/tests/test_legacy_cache_migration.py
T
firestar5683 80fbb4f6db Feature registration
Register vehicle controls, planner modes, UI actions and Galaxy services after their providers exist. Wire the shared runtime entrypoints and cover their combined behavior.

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
Co-authored-by: Daniel Koepping <elkoled@gmail.com>
Co-authored-by: Danny <dannyirwin31@yahoo.com>
Co-authored-by: DimitriChrysafis <Dimitri.chrysafis@gmail.com>
Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com>
Co-authored-by: Kacper Rączy <gfw.kra@gmail.com>
Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu>
Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com>
Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>
Co-authored-by: stef <19478336+stefpi@users.noreply.github.com>
Co-authored-by: whoisdomi <dcantu83@gmail.com>

Co-authored-by: Intelli <contact@intelli.software>
Co-authored-by: haraschax <6804392+haraschax@users.noreply.github.com>
Co-authored-by: prabhaavp <prabhaav@msu.edu>
2026-10-14 20:02:39 -05:00

106 lines
4.8 KiB
Python

import math
import os
import tempfile
import unittest
from pathlib import Path
import capnp
from openpilot.cereal import log
from openpilot.starpilot import schema_cache as cache
from openpilot.starpilot.legacy_cache_migration import LEGACY_CALIBRATION_FIELDS, LegacyCalibrationError, migrate_legacy_cache
LEGACY_SCHEMA = """@0xbcb0a0a1d008d210;
struct LiveCalibrationData {
calStatus @11 :Status;
calCycle @2 :Int32;
calPerc @3 :Int8;
validBlocks @9 :Int32;
extrinsicMatrix @4 :List(Float32);
rpyCalib @7 :List(Float32);
rpyCalibSpread @8 :List(Float32);
wideFromDeviceEuler @10 :List(Float32);
height @12 :List(Float32);
warpMatrixDEPRECATED @0 :List(Float32);
calStatusDEPRECATED @1 :Int8;
warpMatrix2DEPRECATED @5 :List(Float32);
warpMatrixBigDEPRECATED @6 :List(Float32);
enum Status { uncalibrated @0; calibrated @1; invalid @2; recalibrating @3; }
}
"""
def calibration(**changes):
values = {"calStatus": "calibrated", "calCycle": 2, "calPerc": 100, "validBlocks": 37,
"rpyCalib": [0, 0.02, -0.01], "rpyCalibSpread": [0, 0.001, 0.002],
"wideFromDeviceEuler": [0, 0.01, 0], "height": [1.22], "extrinsicMatrix": []}
values.update(changes)
return log.Event.new_message(logMonoTime=987654321, valid=True, extrinsicsCalibration=values)
def active_values(message):
return {name: list(getattr(message, name)) if field_type.startswith("List") else str(getattr(message, name))
if field_type == "Status" else getattr(message, name)
for name, (_, field_type) in LEGACY_CALIBRATION_FIELDS.items()}
class TestLegacyCacheMigration(unittest.TestCase):
def test_original_struct_wire_layout_and_active_values(self):
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "legacy-calibration.capnp"
path.write_text(LEGACY_SCHEMA)
legacy = capnp.load(str(path)).LiveCalibrationData
old = legacy.new_message(calStatus="calibrated", calCycle=2, calPerc=100, validBlocks=37,
rpyCalib=[0, 0.02, -0.01], rpyCalibSpread=[0, 0.001, 0.002],
wideFromDeviceEuler=[0, 0.01, 0], height=[1.22])
with log.ExtrinsicsCalibration.from_bytes(old.to_bytes()) as decoded:
self.assertEqual(decoded.calStatus, "calibrated")
self.assertEqual(decoded.validBlocks, 37)
self.assertEqual(list(decoded.rpyCalib), list(old.rpyCalib))
values = {key: value for key, value in decoded.to_dict().items() if key != "deprecated"}
message = log.Event.new_message(logMonoTime=987654321, valid=True, extrinsicsCalibration=values)
converted = migrate_legacy_cache("CalibrationParams", message.to_bytes())
payload = cache.inspect_cache("CalibrationParams", converted).payload
with log.Event.from_bytes(payload) as result:
self.assertEqual(result.logMonoTime, 987654321)
self.assertEqual(active_values(result.extrinsicsCalibration), active_values(message.extrinsicsCalibration))
def test_real_dom_fixture_when_supplied(self):
fixture = os.environ.get("STARPILOT_DOM_CALIBRATION_FIXTURE")
if fixture is None:
self.skipTest("Private original-device fixture supplied only during qualification")
raw = Path(fixture).read_bytes()
converted = migrate_legacy_cache("CalibrationParams", raw)
payload = cache.inspect_cache("CalibrationParams", converted).payload
with log.Event.from_bytes(raw) as source, log.Event.from_bytes(payload) as result:
self.assertEqual(result.logMonoTime, source.logMonoTime)
self.assertEqual(result.valid, source.valid)
self.assertEqual(active_values(result.extrinsicsCalibration), active_values(source.extrinsicsCalibration))
def test_invalid_calibration_is_never_silently_reset(self):
for changes in ({"rpyCalib": [0, 0]}, {"rpyCalib": [0, math.nan, 0]}, {"height": []},
{"validBlocks": 51}, {"calPerc": -1}, {"rpyCalib": [0, 2, 0]}):
with self.subTest(changes=changes), self.assertRaises(LegacyCalibrationError):
migrate_legacy_cache("CalibrationParams", calibration(**changes).to_bytes())
def test_wrong_service_and_bad_encoding_rejected(self):
for raw in (b"broken", log.Event.new_message(lateralDelay={}).to_bytes()):
with self.subTest(raw=raw), self.assertRaises(LegacyCalibrationError):
migrate_legacy_cache("CalibrationParams", raw)
def test_valid_envelope_preserved_and_other_legacy_caches_retired(self):
class Sink:
def put(self, key, raw, **kwargs):
self.raw = raw
sink = Sink()
cache.put_cache(sink, "CalibrationParams", calibration(), block=True)
self.assertEqual(migrate_legacy_cache("CalibrationParams", sink.raw), sink.raw)
for key in cache.CACHE_KEYS - {"CalibrationParams"}:
with self.subTest(key=key):
self.assertIsNone(migrate_legacy_cache(key, b"legacy cache"))
if __name__ == "__main__":
unittest.main()