Files
StarPilot/openpilot/starpilot/tests/test_dom_handoff_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

143 lines
6.2 KiB
Python

import importlib.util
import json
from pathlib import Path
import tempfile
from types import SimpleNamespace
import unittest
from unittest.mock import patch
spec = importlib.util.spec_from_file_location('handoff_state_migration', Path(__file__).resolve().parents[1] / 'state_migration.py')
assert spec is not None
migration = importlib.util.module_from_spec(spec)
spec.loader.exec_module(migration)
class TestDomHandoff(unittest.TestCase):
def setUp(self):
temporary = tempfile.TemporaryDirectory()
self.addCleanup(temporary.cleanup)
self.root = Path(temporary.name)
self.namespace = self.root / 'params' / 'd'
self.namespace.mkdir(parents=True)
self.storage = self.root / 'recovery'
self.types = {'IsMetric': 'BOOL', 'CalibrationParams': 'BYTES', 'DongleId': 'STRING'}
self.params = SimpleNamespace(get_param_path=lambda: str(self.namespace), all_keys=lambda: self.types,
get_type=lambda key: SimpleNamespace(name=self.types[key]))
cache = SimpleNamespace(CACHE_KEYS={'CalibrationParams'}, inspect_cache=lambda key, raw: SimpleNamespace(status='valid'))
self.patch = patch.dict('sys.modules', {'openpilot.starpilot.schema_cache': cache})
self.patch.start()
self.addCleanup(self.patch.stop)
self.start()
self.profile = next((self.storage / 'profiles').iterdir())
self.handoff = self.namespace.parent / f'.starpilot-dom-handoff-{migration.digest(str(self.namespace).encode())}.json'
def start(self, **kwargs):
return migration.prepare_manager_start(self.params, self.storage, auto_migrate=True, **kwargs)
def write(self, **values):
for key, raw in values.items():
(self.namespace / key).write_bytes(raw)
def mark_dom(self, token='a' * 32, **changes):
record: dict = {'format': 'starpilot-dom-handoff', 'version': 1, 'namespace': str(self.namespace),
'target': str(self.namespace.resolve()), 'token': token}
record.update(changes)
migration._atomic_write(self.handoff, migration.canonical_json(record))
def test_handoff_archives_foreign_settings_and_keeps_identity(self):
self.write(IsMetric=b'1', CalibrationParams=b'Dom calibration', DomSetting=b'old', DongleId=b'identity')
original = migration._read_namespace(self.namespace)
self.mark_dom()
self.start()
self.assertEqual(migration._read_namespace(self.namespace), {'DongleId': b'identity'})
self.assertIn(original, [migration.load_snapshot(path) for path in (self.storage / 'snapshots').iterdir()])
self.assertEqual(json.loads(self.profile.read_bytes())['consumed_dom_token'], 'a' * 32)
self.assertTrue(self.handoff.exists())
def test_same_token_and_marker_removal_preserve_rebase_preferences(self):
self.mark_dom()
self.start()
self.write(IsMetric=b'1', CalibrationParams=b'rebase calibration')
self.start()
self.assertEqual((self.namespace / 'IsMetric').read_bytes(), b'1')
self.handoff.unlink()
self.start()
self.assertEqual((self.namespace / 'CalibrationParams').read_bytes(), b'rebase calibration')
def test_new_dom_token_resets_again(self):
self.mark_dom()
self.start()
self.write(IsMetric=b'1')
self.mark_dom('b' * 32)
self.start()
self.assertFalse((self.namespace / 'IsMetric').exists())
self.assertEqual(json.loads(self.profile.read_bytes())['consumed_dom_token'], 'b' * 32)
def test_malformed_wrong_target_and_bad_permissions_never_reset(self):
self.write(IsMetric=b'1')
for changes in ({'target': str(self.root)}, {'token': 'invalid'}, {'version': True}):
with self.subTest(changes=changes):
self.mark_dom(**changes)
with self.assertRaises(migration.MigrationRequired):
self.start()
self.assertEqual((self.namespace / 'IsMetric').read_bytes(), b'1')
self.mark_dom()
self.handoff.chmod(0o644)
with self.assertRaises(migration.MigrationRequired):
self.start()
self.assertEqual((self.namespace / 'IsMetric').read_bytes(), b'1')
def test_archive_failure_does_not_consume(self):
self.write(IsMetric=b'1')
before = self.profile.read_bytes()
self.mark_dom()
with patch.object(migration, 'load_snapshot', return_value={'wrong': b'value'}):
with self.assertRaises(migration.MigrationRequired):
self.start()
self.assertEqual(self.profile.read_bytes(), before)
self.assertEqual((self.namespace / 'IsMetric').read_bytes(), b'1')
self.start()
self.assertFalse((self.namespace / 'IsMetric').exists())
def test_partial_reset_failure_retries_same_token(self):
self.write(IsMetric=b'1', CalibrationParams=b'old', DomSetting=b'old')
original = migration._read_namespace(self.namespace)
before = self.profile.read_bytes()
self.mark_dom()
unlink = Path.unlink
def fail(path, *args, **kwargs):
if path == self.namespace.resolve() / 'IsMetric':
raise OSError('reset failed')
return unlink(path, *args, **kwargs)
with patch.object(Path, 'unlink', fail):
with self.assertRaises(migration.MigrationRequired):
self.start()
self.assertEqual(self.profile.read_bytes(), before)
self.assertIn(original, [migration.load_snapshot(path) for path in (self.storage / 'snapshots').iterdir()])
self.start()
self.assertEqual(migration._read_namespace(self.namespace), {})
def test_strict_and_dry_run_do_not_consume(self):
self.mark_dom()
before = self.profile.read_bytes()
with self.assertRaises(migration.MigrationRequired):
self.start(dry_run=True)
with self.assertRaises(migration.MigrationRequired):
migration.prepare_manager_start(self.params, self.storage)
self.assertEqual(self.profile.read_bytes(), before)
def test_namespace_markers_are_isolated(self):
other = self.namespace.parent / 'other'
other.mkdir()
self.mark_dom()
other_params = SimpleNamespace(get_param_path=lambda: str(other), all_keys=lambda: self.types,
get_type=self.params.get_type)
migration.prepare_manager_start(other_params, self.storage, auto_migrate=True)
self.assertNotIn('consumed_dom_token', json.loads((self.storage / 'profiles' /
f'{migration.digest(str(other).encode())}.json').read_bytes()))
if __name__ == '__main__':
unittest.main()