Files
StarPilot/openpilot/starpilot/aol/tests/test_default_enrollment.py
T
2026-10-19 22:00:34 -05:00

177 lines
9.0 KiB
Python

import tempfile
from pathlib import Path
from unittest import TestCase
from types import SimpleNamespace
from opendbc.car.honda.interface import CarInterface
from opendbc.car.honda.values import CAR
from openpilot.common.params import Params
from openpilot.starpilot.aol.defaults import default_enabled, enroll_missing_preference
from openpilot.starpilot.aol.intent import read_settings
from openpilot.starpilot.ui.feature_settings_owner import FeatureSettingsOwner
class TestAolInitialDefault(TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.params = Params(self.temp.name)
self.cp = CarInterface.get_params(CAR.HONDA_CIVIC_BOSCH, {0: {}, 1: {}, 2: {}}, [], True, False, False)
def test_actual_supported_policy_and_canonical_owner_share_default(self):
self.assertFalse(int(self.cp.safetyConfigs[0].safetyParam) & 0x20)
self.assertTrue(default_enabled(self.cp))
owner = FeatureSettingsOwner(self.params, lambda group: True, vehicle_fingerprint=lambda: self.cp.carFingerprint,
vehicle_params=lambda: self.cp)
row = next(row for row in owner.snapshot('aol', parked=True, system_long=True, lateral_context=True, metric=False).rows if row.key == 'AlwaysOnLateral')
self.assertEqual((row.value, row.default_value), ('On', 'On'))
self.assertTrue(enroll_missing_preference(self.params, self.cp))
self.assertTrue(read_settings(self.params).enabled)
self.assertFalse(enroll_missing_preference(self.params, self.cp))
def test_preference_default_is_on_without_vehicle_or_brand_qualification(self):
vehicles = [None, SimpleNamespace(), SimpleNamespace(notCar=True), SimpleNamespace(passive=True),
SimpleNamespace(dashcamOnly=True)]
vehicles += [SimpleNamespace(brand=brand) for brand in ('body', 'chrysler', 'ford', 'gm', 'honda', 'hyundai',
'kia', 'genesis', 'mazda', 'nissan', 'rivian', 'subaru', 'tesla', 'toyota', 'volkswagen', 'volvo', 'unknown')]
for cp in vehicles:
with self.subTest(cp=cp):
self.assertTrue(default_enabled(cp))
def test_missing_preference_can_enroll_before_vehicle_identification(self):
self.assertTrue(enroll_missing_preference(self.params, None))
self.assertEqual(Path(self.params.get_param_path('AlwaysOnLateral')).read_bytes(), b'1')
def test_explicit_off_on_and_invalid_migration_bytes_always_win(self):
for raw in (b'0', b'1', b'invalid'):
with self.subTest(raw=raw):
Path(self.params.get_param_path('AlwaysOnLateral')).write_bytes(raw)
self.assertFalse(enroll_missing_preference(self.params, self.cp))
self.assertEqual(Path(self.params.get_param_path('AlwaysOnLateral')).read_bytes(), raw)
def test_runtime_unavailability_never_rewrites_existing_choice(self):
self.params.put_bool('AlwaysOnLateral', False, block=True)
self.cp.passive = True
self.assertTrue(default_enabled(self.cp))
self.assertFalse(enroll_missing_preference(self.params, self.cp))
self.assertFalse(self.params.get_bool('AlwaysOnLateral'))
def test_irregular_preference_is_preserved_without_startup_failure(self):
target = Path(self.params.get_param_path('AlwaysOnLateral'))
target.mkdir()
self.assertFalse(enroll_missing_preference(self.params, self.cp))
self.assertTrue(target.is_dir())
def test_real_invalid_lock_is_preserved_without_startup_failure(self):
namespace = Path(self.params.get_param_path()).absolute()
lock = namespace.parent / '.lock'
lock.unlink(missing_ok=True)
lock.symlink_to(namespace / 'nonexistent')
self.assertFalse(enroll_missing_preference(self.params, self.cp))
self.assertTrue(lock.is_symlink())
self.assertFalse((namespace / 'AlwaysOnLateral').exists())
def test_real_missing_namespace_write_failure_does_not_abort_startup(self):
namespace = Path(self.params.get_param_path()).absolute()
if namespace.is_symlink():
namespace.unlink()
else:
import shutil
shutil.rmtree(namespace)
self.assertFalse(enroll_missing_preference(self.params, self.cp))
def test_symlink_preference_read_failure_preserves_external_explicit_choice(self):
outside = Path(self.temp.name) / 'saved-off'
outside.write_bytes(b'0')
target = Path(self.params.get_param_path('AlwaysOnLateral'))
target.symlink_to(outside)
self.assertFalse(enroll_missing_preference(self.params, self.cp))
self.assertTrue(target.is_symlink())
self.assertEqual(outside.read_bytes(), b'0')
class TestActualFreshCardStartup(TestCase):
def construct(self, preference=None, *, enabled=True):
from unittest.mock import patch
self.enterContext(patch.dict('os.environ'))
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.selfdrive.car.card import Car
prefix = OpenpilotPrefix()
prefix.__enter__()
self.addCleanup(prefix.__exit__, None, None, None)
params = Params()
recovery = tempfile.TemporaryDirectory()
self.addCleanup(recovery.cleanup)
storage = Path(recovery.name) / 'recovery'
from openpilot.system.manager import manager
from openpilot.starpilot.state_migration import prepare_manager_start
prepare_manager_start(params, storage)
if preference is not None:
Path(params.get_param_path('AlwaysOnLateral')).write_bytes(preference)
metadata = SimpleNamespace(
release_channel=False, tested_channel=False, channel='SecretGoodStarPilot',
openpilot=SimpleNamespace(version='test', git_commit='a' * 40, git_commit_date='test',
git_origin='local', git_normalized_origin='local', is_dirty=True),
)
with patch.object(manager, 'Params', return_value=params), \
patch.object(manager, 'starpilot_storage_root', return_value=storage), \
patch.object(manager.Paths, 'shm_path', return_value=str(Path(recovery.name) / 'shm')), \
patch.object(manager, 'get_build_metadata', return_value=metadata), \
patch.object(manager, 'save_bootlog'), \
patch.object(manager.HARDWARE, 'get_serial', return_value='fixture'), \
patch.object(manager.HARDWARE, 'get_device_type', return_value='pc'), \
patch.object(manager, 'register', return_value='fixture'), \
patch.object(manager.cloudlog, 'bind_global'):
manager.manager_init()
target = Path(params.get_param_path('AlwaysOnLateral'))
if preference is None:
self.assertEqual(target.read_bytes(), b'1', 'Manager must initialize AOL On before vehicle identification')
else:
self.assertEqual(target.read_bytes(), preference, 'Manager must preserve the saved AOL preference')
params.put_bool('OpenpilotEnabledToggle', enabled, block=True)
params.put_bool('IsReleaseBranch', False, block=True)
cp = CarInterface.get_params(CAR.HONDA_CIVIC_BOSCH, {0: {}, 1: {}, 2: {}}, [], True, False, False)
self.assertFalse(int(cp.safetyConfigs[0].safetyParam) & 0x20)
ci = CarInterface(cp)
with patch.dict('os.environ', {'AOL_REPLAY_RUNTIME': '0'}):
host = Car(CI=ci, RI=ci.RadarInterface(cp))
return host, params
def test_missing_preference_bootstraps_actual_same_startup_transport_and_cp(self):
host, params = self.construct()
self.assertTrue(params.get_bool('AlwaysOnLateral'))
self.assertEqual(Path(params.get_param_path('AlwaysOnLateral')).read_bytes(), b'1')
self.assertTrue(host.aol_replay)
self.assertTrue(host.aol_settings.enabled)
self.assertIsNotNone(host.aol_card_intent)
self.assertIn('aolSafetyWire', host.sm.services)
self.assertIn('aolIntentWire', host.pm.sock)
self.assertTrue(int(host.CP.safetyConfigs[0].safetyParam) & 0x20)
from opendbc.car.structs import car
with car.CarParams.from_bytes(params.get('CarParams')) as published:
self.assertTrue(int(published.safetyConfigs[0].safetyParam) & 0x20)
def test_explicit_disabled_preference_keeps_actual_startup_disabled(self):
host, params = self.construct(b'0')
self.assertFalse(params.get_bool('AlwaysOnLateral'))
self.assertEqual(Path(params.get_param_path('AlwaysOnLateral')).read_bytes(), b'0')
self.assertFalse(host.aol_replay)
self.assertIsNone(host.aol_settings)
self.assertFalse(int(host.CP.safetyConfigs[0].safetyParam) & 0x20)
def test_explicit_enabled_preference_survives_manager_and_card_startup(self):
host, params = self.construct(b'1')
self.assertEqual(Path(params.get_param_path('AlwaysOnLateral')).read_bytes(), b'1')
self.assertTrue(host.aol_replay)
self.assertTrue(host.aol_settings.enabled)
def test_global_on_preference_does_not_enable_a_passive_car(self):
host, params = self.construct(enabled=False)
self.assertEqual(Path(params.get_param_path('AlwaysOnLateral')).read_bytes(), b'1')
self.assertTrue(host.CP.passive)
self.assertFalse(host.aol_qualified)
self.assertIsNone(host.aol_card_intent)
from opendbc.car.structs import car
self.assertTrue(all(config.safetyModel == car.CarParams.SafetyModel.noOutput for config in host.CP.safetyConfigs))
self.assertTrue(all(int(config.safetyParam) == 0 for config in host.CP.safetyConfigs))