mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 05:23:41 +08:00
d8708b65c7
Implement conditional driving modes with explicit scene, button and traffic state ownership. Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com> Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com> Co-authored-by: whoisdomi <dcantu83@gmail.com>
175 lines
8.3 KiB
Python
175 lines
8.3 KiB
Python
"""Strict conditional-mode document and read-only frozen-Params migration tests."""
|
|
|
|
from dataclasses import replace
|
|
import json
|
|
import math
|
|
import unittest
|
|
|
|
from openpilot.starpilot.conditional_mode.policy import ModeChoice, ModeSettings
|
|
from openpilot.starpilot.conditional_mode.preferences import (
|
|
CCMOptions,
|
|
CEMOptions,
|
|
MAX_DOCUMENT_BYTES,
|
|
PreferenceError,
|
|
SavedPreferences,
|
|
decode_preferences,
|
|
encode_preferences,
|
|
manual_for_drive,
|
|
propose_legacy_adoption,
|
|
saved_selection_for_drive,
|
|
selection_for_drive,
|
|
)
|
|
|
|
|
|
class ConditionalModePreferencesTest(unittest.TestCase):
|
|
def test_factory_default_is_cem_and_explicit_stock_round_trip_is_canonical(self):
|
|
default = SavedPreferences()
|
|
self.assertIs(default.mode, ModeChoice.CEM)
|
|
self.assertEqual(default.cem, CEMOptions())
|
|
self.assertEqual(default.ccm, CCMOptions())
|
|
raw = encode_preferences(default)
|
|
self.assertEqual(decode_preferences(raw), default)
|
|
self.assertEqual(encode_preferences(decode_preferences(raw)), raw)
|
|
self.assertTrue(raw.startswith(b'{"ccm":'))
|
|
self.assertNotIn(b'SafeMode', raw)
|
|
self.assertLess(len(raw), MAX_DOCUMENT_BYTES)
|
|
stock = SavedPreferences(mode=ModeChoice.STOCK)
|
|
self.assertIs(decode_preferences(encode_preferences(stock)).mode, ModeChoice.STOCK)
|
|
|
|
def test_all_options_survive_and_policy_projection_is_explicit(self):
|
|
preferences = SavedPreferences(
|
|
mode=ModeChoice.CEM,
|
|
cem=CEMOptions(speed_mps=10.5, speed_with_lead_mps=7.0, signal_speed_mps=5.0,
|
|
open_road=True, curves=True, curves_with_lead=True, lead=True,
|
|
slower_lead=False, stopped_lead=True, stop_lights=False,
|
|
model_stop_s=6.5, signal_lane_detection=False,
|
|
signal_lane_width_m=3.7, persist_manual=True),
|
|
ccm=CCMOptions(speed_mps=20.0, speed_with_lead_mps=15.0,
|
|
set_speed_margin_mps=2.0, lead=False, launch_assist=True,
|
|
persist_manual=True),
|
|
)
|
|
self.assertEqual(decode_preferences(encode_preferences(preferences)), preferences)
|
|
settings = preferences.mode_settings()
|
|
self.assertIsInstance(settings, ModeSettings)
|
|
self.assertEqual((settings.cem_speed_mps, settings.cem_speed_with_lead_mps,
|
|
settings.cem_signal_mps), (10.5, 7.0, 5.0))
|
|
self.assertFalse(settings.cem_stop) # CEStopLights gates CEModelStopTime.
|
|
self.assertEqual((settings.ccm_speed_mps, settings.ccm_speed_with_lead_mps,
|
|
settings.ccm_set_speed_margin_mps), (20.0, 15.0, 2.0))
|
|
self.assertFalse(settings.ccm_lead)
|
|
self.assertTrue(settings.ccm_launch)
|
|
self.assertFalse(replace(preferences, cem=replace(preferences.cem, model_stop_s=0.0)).mode_settings().cem_stop)
|
|
|
|
def test_drive_binding_preserves_existing_typed_api_and_does_not_grant_authority(self):
|
|
preferences = SavedPreferences(mode=ModeChoice.CCM)
|
|
selection = saved_selection_for_drive(preferences, 42)
|
|
self.assertEqual(selection, selection_for_drive(ModeChoice.CCM, preferences.mode_settings(), 42))
|
|
self.assertIsNone(saved_selection_for_drive(preferences, 0))
|
|
self.assertIsNone(saved_selection_for_drive(preferences, True))
|
|
self.assertIsNone(manual_for_drive('force_chill', 42, 100))
|
|
self.assertFalse(hasattr(selection, 'safe_mode'))
|
|
self.assertFalse(hasattr(selection, 'system_long_capable'))
|
|
|
|
def test_malformed_document_never_falls_back_or_silently_rewrites(self):
|
|
good = json.loads(encode_preferences(SavedPreferences()))
|
|
invalid = []
|
|
for key in ('version', 'mode', 'cem', 'ccm'):
|
|
document = dict(good)
|
|
document.pop(key)
|
|
invalid.append(document)
|
|
for key, value in (('extra', 1), ('version', 2), ('version', True),
|
|
('mode', 'conditional_chill_unknown'), ('mode', 1)):
|
|
invalid.append(dict(good, **{key: value}))
|
|
for section, name, value in (
|
|
('cem', 'speed_mps', True), ('cem', 'speed_mps', -1),
|
|
('cem', 'speed_mps', 50.0), ('cem', 'model_stop_s', 9.1),
|
|
('cem', 'signal_lane_width_m', 15.1), ('cem', 'open_road', 1),
|
|
('ccm', 'set_speed_margin_mps', 9.0), ('ccm', 'lead', 'false'),
|
|
):
|
|
document = json.loads(json.dumps(good))
|
|
document[section][name] = value
|
|
invalid.append(document)
|
|
missing = json.loads(json.dumps(good))
|
|
del missing['cem']['slower_lead']
|
|
invalid.append(missing)
|
|
extra = json.loads(json.dumps(good))
|
|
extra['ccm']['unknown'] = False
|
|
invalid.append(extra)
|
|
for document in invalid:
|
|
with self.subTest(document=document):
|
|
with self.assertRaises(PreferenceError):
|
|
decode_preferences(json.dumps(document))
|
|
for raw in (b'\xff', '{"version":1,"version":1}',
|
|
'{"value":NaN}', b'x' * (MAX_DOCUMENT_BYTES + 1),
|
|
'not-json', ''):
|
|
with self.subTest(raw=raw):
|
|
with self.assertRaises(PreferenceError):
|
|
decode_preferences(raw)
|
|
with self.assertRaises(PreferenceError):
|
|
encode_preferences(replace(SavedPreferences(), cem=replace(CEMOptions(), speed_mps=math.nan)))
|
|
with self.assertRaises(PreferenceError):
|
|
encode_preferences(replace(SavedPreferences(), ccm=replace(CCMOptions(), speed_mps=10**350)))
|
|
|
|
def test_legacy_adoption_requires_explicit_mode_bits_and_unit_system(self):
|
|
# Frozen params_keys.h stores numeric values in display units; old
|
|
# starpilot_variables.py multiplied by MPH_TO_MS/KPH_TO_MS at runtime.
|
|
imperial = propose_legacy_adoption({
|
|
'ConditionalExperimental': b'1', 'ConditionalChill': b'0',
|
|
'CESpeed': b'25.0', 'CESpeedLead': b'15.0', 'CESignalSpeed': b'12.0',
|
|
'CCMSpeed': b'45.0', 'CCMSetSpeedMargin': b'3.0',
|
|
'CEModelStopTime': b'7.7', 'CEStopLights': b'1',
|
|
'CESlowerLead': b'0', 'CEStoppedLead': b'1',
|
|
'CESignalLaneDetection': b'1', 'LaneDetectionWidth': b'12.0',
|
|
'PersistExperimentalState': b'1', 'PersistChillState': b'0',
|
|
}, units='imperial')
|
|
self.assertIs(imperial.preferences.mode, ModeChoice.CEM)
|
|
self.assertAlmostEqual(imperial.preferences.cem.speed_mps, 25 * 0.44704)
|
|
self.assertAlmostEqual(imperial.preferences.cem.signal_lane_width_m, 12 * 0.3048)
|
|
self.assertEqual(imperial.preferences.cem.model_stop_s, 7.7)
|
|
self.assertFalse(imperial.preferences.cem.slower_lead)
|
|
self.assertTrue(imperial.preferences.cem.stopped_lead)
|
|
self.assertTrue(imperial.preferences.cem.persist_manual)
|
|
self.assertFalse(imperial.preferences.ccm.persist_manual)
|
|
self.assertIn('CESpeed', imperial.source_keys)
|
|
self.assertEqual(decode_preferences(encode_preferences(imperial.preferences)), imperial.preferences)
|
|
|
|
metric = propose_legacy_adoption({
|
|
'ConditionalExperimental': False, 'ConditionalChill': True,
|
|
'CCMSpeed': '72.0', 'CCMSpeedLead': '54.0',
|
|
'CCMSetSpeedMargin': '10.0', 'LaneDetectionWidth': '3.5',
|
|
}, units='metric')
|
|
self.assertIs(metric.preferences.mode, ModeChoice.CCM)
|
|
self.assertEqual(metric.preferences.ccm.speed_mps, 20.0)
|
|
self.assertEqual(metric.preferences.ccm.speed_with_lead_mps, 15.0)
|
|
self.assertAlmostEqual(metric.preferences.ccm.set_speed_margin_mps, 10 / 3.6)
|
|
self.assertEqual(metric.preferences.cem.signal_lane_width_m, 3.5)
|
|
|
|
stock = propose_legacy_adoption({'ConditionalExperimental': '0',
|
|
'ConditionalChill': '0'}, units='imperial')
|
|
self.assertIs(stock.preferences.mode, ModeChoice.STOCK)
|
|
|
|
def test_legacy_adoption_rejects_ambiguity_corruption_and_safe_mode_coupling(self):
|
|
base = {'ConditionalExperimental': '0', 'ConditionalChill': '1'}
|
|
bad = (
|
|
({'ConditionalExperimental': '1', 'ConditionalChill': '1'}, 'imperial'),
|
|
({'ConditionalExperimental': '1'}, 'imperial'),
|
|
({**base, 'SafeMode': '0'}, 'imperial'),
|
|
({**base, 'CCMSpeed': '1e2'}, 'imperial'),
|
|
({**base, 'CCMSpeed': 'nan'}, 'imperial'),
|
|
({**base, 'CCMSpeed': '100'}, 'imperial'),
|
|
({**base, 'CCMSetSpeedMargin': '16'}, 'imperial'),
|
|
({**base, 'CCMSetSpeedMargin': '31'}, 'metric'),
|
|
({**base, 'CEModelStopTime': '9.1'}, 'metric'),
|
|
({**base, 'CEStopLights': 'true'}, 'metric'),
|
|
({**base, 'LaneDetectionWidth': '15.1'}, 'metric'),
|
|
(base, 'unknown'),
|
|
)
|
|
for values, units in bad:
|
|
with self.subTest(values=values, units=units):
|
|
with self.assertRaises(PreferenceError):
|
|
propose_legacy_adoption(values, units=units)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|