mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-29 01:23:58 +08:00
694b460f80
Provide laptop and device build entry points, cache compatibility checks and developer tooling. Keep distribution assets and model packaging available before runtime consumers. Co-authored-by: Danny <dannyirwin31@yahoo.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: prabhaavp <prabhaav@msu.edu>
122 lines
6.1 KiB
Python
122 lines
6.1 KiB
Python
import tempfile
|
|
import json
|
|
import struct
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from opendbc.car import structs
|
|
from openpilot.common.params import Params
|
|
from openpilot.starpilot import schema_cache as cache
|
|
from openpilot.starpilot.state_migration import MigrationRequired, load_snapshot, prepare_manager_start
|
|
from openpilot.cereal import messaging
|
|
from tools.check_startup import check_startup
|
|
|
|
|
|
class TestCheckStartup(unittest.TestCase):
|
|
def setUp(self):
|
|
temporary = tempfile.TemporaryDirectory()
|
|
self.addCleanup(temporary.cleanup)
|
|
self.root = Path(temporary.name)
|
|
self.params = Params(str(self.root / "params"))
|
|
self.storage = self.root / "recovery"
|
|
self.storage.mkdir(mode=0o700)
|
|
|
|
def tree(self):
|
|
return {str(path.relative_to(self.root)): path.read_bytes() if path.is_file() else None
|
|
for path in self.root.rglob("*")}
|
|
|
|
def test_valid_initialized_cache_accepted_without_writes(self):
|
|
prepare_manager_start(self.params, self.storage)
|
|
cache.put_cache(self.params, "LiveDelay", messaging.new_message("lateralDelay"), block=True)
|
|
before = self.tree()
|
|
check_startup(Path(self.params.get_param_path()), self.storage)
|
|
self.assertEqual(self.tree(), before)
|
|
|
|
def test_incompatible_retained_cache_rejected_without_writes(self):
|
|
prepare_manager_start(self.params, self.storage)
|
|
payload = messaging.new_message("lateralDelay").to_bytes()
|
|
header = cache._header(cache.CONTRACTS["LiveDelay"], "LiveDelay", payload, version=1)
|
|
header["schema_sha256"] = "0" * 64
|
|
encoded = json.dumps(header, separators=(",", ":")).encode()
|
|
raw = cache.MAGIC + struct.pack(">I", len(encoded)) + encoded + payload
|
|
self.assertEqual(cache.inspect_cache("LiveDelay", raw).status, "incompatible")
|
|
(Path(self.params.get_param_path()) / "LiveDelay").write_bytes(raw)
|
|
before = self.tree()
|
|
with self.assertRaisesRegex(MigrationRequired, "incompatible retained cache"):
|
|
check_startup(Path(self.params.get_param_path()), self.storage)
|
|
self.assertEqual(self.tree(), before)
|
|
|
|
def stale_cache(self, key):
|
|
contract = cache.CONTRACTS[key]
|
|
message = contract.root.new_message() if contract.service is None else messaging.new_message(contract.service)
|
|
payload = message.to_bytes()
|
|
header = cache._header(contract, key, payload)
|
|
header.update(producer_contract="prior-runtime-contract", schema_sha256="0" * 64)
|
|
encoded = json.dumps(header, separators=(",", ":")).encode()
|
|
raw = cache.MAGIC + struct.pack(">I", len(encoded)) + encoded + payload
|
|
self.assertEqual(cache.inspect_cache(key, raw).status, "incompatible")
|
|
self.params.put(key, raw, block=True)
|
|
return raw
|
|
|
|
def test_initialized_actual_registry_archives_all_vehicle_contracts_only(self):
|
|
prepare_manager_start(self.params, self.storage, auto_migrate=True)
|
|
vehicle_keys = {key for key, contract in cache.CONTRACTS.items() if contract.root is structs.CarParams}
|
|
self.assertTrue(vehicle_keys)
|
|
stale = {key: self.stale_cache(key) for key in vehicle_keys}
|
|
cache.put_cache(self.params, "CalibrationParams", messaging.new_message("extrinsicsCalibration"), block=True)
|
|
cache.put_cache(self.params, "LiveDelay", messaging.new_message("lateralDelay"), block=True)
|
|
self.params.put_bool("IsMetric", True, block=True)
|
|
self.params.put_bool("AlwaysOnLateral", True, block=True)
|
|
self.params.put("DongleId", "preserved-operational-identity", block=True)
|
|
namespace = Path(self.params.get_param_path())
|
|
original = {path.name: path.read_bytes() for path in namespace.iterdir()}
|
|
before = self.tree()
|
|
with self.assertRaisesRegex(MigrationRequired, "incompatible retained cache"):
|
|
check_startup(namespace, self.storage)
|
|
self.assertEqual(self.tree(), before)
|
|
prepare_manager_start(self.params, self.storage, auto_migrate=True)
|
|
expected = {key: raw for key, raw in original.items() if key not in vehicle_keys}
|
|
self.assertEqual({path.name: path.read_bytes() for path in namespace.iterdir()}, expected)
|
|
snapshot, = (self.storage / "snapshots").iterdir()
|
|
self.assertEqual(load_snapshot(snapshot), original)
|
|
receipt = json.loads((snapshot / "migration.json").read_bytes())
|
|
self.assertEqual(set(receipt["actions"]), set(stale))
|
|
self.assertEqual(receipt["status"], "migrated")
|
|
after = self.tree()
|
|
prepare_manager_start(self.params, self.storage, auto_migrate=True)
|
|
check_startup(namespace, self.storage)
|
|
self.assertEqual(self.tree(), after)
|
|
|
|
def test_initialized_vehicle_upgrade_does_not_admit_invalid_other_cache(self):
|
|
prepare_manager_start(self.params, self.storage, auto_migrate=True)
|
|
vehicle_keys = {key for key, contract in cache.CONTRACTS.items() if contract.root is structs.CarParams}
|
|
for key in vehicle_keys:
|
|
self.stale_cache(key)
|
|
self.stale_cache("LiveDelay")
|
|
namespace = Path(self.params.get_param_path())
|
|
original = {path.name: path.read_bytes() for path in namespace.iterdir()}
|
|
with self.assertRaises(MigrationRequired):
|
|
prepare_manager_start(self.params, self.storage, auto_migrate=True)
|
|
self.assertEqual({path.name: path.read_bytes() for path in namespace.iterdir()}, original)
|
|
snapshot, = (self.storage / "snapshots").iterdir()
|
|
self.assertEqual(load_snapshot(snapshot), original)
|
|
|
|
def test_missing_input_rejected_without_creating_namespace(self):
|
|
missing = self.root / "missing"
|
|
with self.assertRaisesRegex(ValueError, "Existing Params namespace required"):
|
|
check_startup(missing, self.storage)
|
|
self.assertFalse(missing.exists())
|
|
|
|
def test_root_with_named_link_or_empty_directory_is_rejected(self):
|
|
named = self.root / "named"
|
|
named.mkdir()
|
|
(named / "other").symlink_to(Path(self.params.get_param_path()), target_is_directory=True)
|
|
before = self.tree()
|
|
with self.assertRaisesRegex(ValueError, "named-link roots are ambiguous"):
|
|
check_startup(named, self.storage)
|
|
empty = self.root / "empty"
|
|
empty.mkdir()
|
|
with self.assertRaisesRegex(ValueError, "empty or named-link roots are ambiguous"):
|
|
check_startup(empty, self.storage)
|
|
self.assertEqual({key: value for key, value in self.tree().items() if key != "empty"}, before)
|