mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 13:33:42 +08:00
7bce4121e5
Use one saved schedule with editable zero defaults. Preserve current schedules and round displayed offsets and edits to whole units without conversion drift.
147 lines
7.9 KiB
Python
147 lines
7.9 KiB
Python
"""Persisted SLC corruption must not silently select a less restrictive default."""
|
|
|
|
from pathlib import Path
|
|
import tempfile
|
|
import unittest
|
|
|
|
from openpilot.common.params import Params, UnknownKeyName
|
|
from openpilot.starpilot.speed_limits import acceptance as acc
|
|
from openpilot.starpilot.speed_limits import offset_document as od
|
|
from openpilot.starpilot.speed_limits.runtime_settings import MPH_TO_MPS, parse, read_params
|
|
|
|
|
|
class RuntimeSettingsTests(unittest.TestCase):
|
|
def test_confirmation_master_controls_both_directions_regardless_of_legacy_flags(self):
|
|
for enabled in (False, True):
|
|
for legacy in ({}, {'SLCConfirmationLower': False, 'SLCConfirmationHigher': False},
|
|
{'SLCConfirmationLower': True, 'SLCConfirmationHigher': False},
|
|
{'SLCConfirmationLower': False, 'SLCConfirmationHigher': True}):
|
|
with self.subTest(enabled=enabled, legacy=legacy):
|
|
settings = parse({'SpeedLimitController': True, 'SLCConfirmation': enabled, **legacy})
|
|
self.assertFalse(settings.errors)
|
|
self.assertEqual((settings.acceptance.confirm_lower, settings.acceptance.confirm_higher), (enabled, enabled))
|
|
|
|
def test_confirmation_on_never_autoaccepts_first_changed_or_disengaged_limits(self):
|
|
active = acc.Authority(acc.Mode.COMBINED, acc.LongitudinalOwner.SYSTEM, True, True, False, False)
|
|
stopped = acc.Authority(acc.Mode.COMBINED, acc.LongitudinalOwner.SYSTEM, False, False, False, True)
|
|
policy = parse({'SpeedLimitController': True, 'SLCConfirmation': True}).acceptance
|
|
for source in ('dashboard', 'map', 'vision', 'online'):
|
|
previous = acc.Candidate(source, acc.ObservationIdentity(acc.IdentityKind.GEOGRAPHIC, value='accepted'), 20.)
|
|
history = acc.AcceptedLimit(previous, 'drive', 0)
|
|
for authority in (active, stopped):
|
|
for accepted in (None, history):
|
|
for speed in (15., 25.):
|
|
with self.subTest(source=source, authority=authority, accepted=accepted, speed=speed):
|
|
candidate = acc.Candidate(source, acc.ObservationIdentity(acc.IdentityKind.GEOGRAPHIC, value='new'), speed)
|
|
result = acc.step(acc.new_session('drive', accepted), acc.Observation(acc.ObservationKind.VALID, candidate),
|
|
authority, policy, now_ns=1)
|
|
self.assertEqual(result.state.accepted, accepted)
|
|
self.assertIsNone(result.history_write)
|
|
self.assertEqual(result.state.pending is not None, authority.can_control)
|
|
self.assertEqual(result.control_target_mps, 20. if accepted is not None and authority.can_control else None)
|
|
|
|
def test_fresh_vision_display_default_preserves_explicit_saved_choices(self):
|
|
from openpilot.starpilot.speed_limits.selection import Source
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
params = Params(directory)
|
|
settings = read_params(params)
|
|
self.assertTrue(settings.display)
|
|
self.assertFalse(settings.enabled)
|
|
self.assertEqual(settings.selection.slots, (Source.VISION, Source.DASHBOARD))
|
|
self.assertTrue(settings.acceptance.display_only)
|
|
params.put_bool('ShowSpeedLimits', False, block=True)
|
|
params.put('SLCPriority1', 'Dashboard', block=True)
|
|
params.put('SLCPriority2', 'Map Data', block=True)
|
|
saved = read_params(params)
|
|
self.assertFalse(saved.display)
|
|
self.assertFalse(saved.enabled)
|
|
self.assertEqual(saved.selection.slots, (Source.DASHBOARD, Source.MAP))
|
|
self.assertEqual(Path(params.get_param_path('ShowSpeedLimits')).read_bytes(), b'0')
|
|
self.assertEqual(Path(params.get_param_path('SLCPriority1')).read_bytes(), b'Dashboard')
|
|
self.assertEqual(Path(params.get_param_path('SLCPriority2')).read_bytes(), b'Map Data')
|
|
|
|
def test_corrupt_saved_values_disable_control_without_rewriting(self):
|
|
cases = {'SLCConfirmation': b'invalid', 'IsMetric': b'2', 'SLCPriority1': b'\xff'}
|
|
for key, raw in cases.items():
|
|
with self.subTest(key=key), tempfile.TemporaryDirectory() as directory:
|
|
params = Params(directory)
|
|
params.put_bool('SpeedLimitController', True, block=True)
|
|
path = Path(params.get_param_path(key))
|
|
path.write_bytes(raw)
|
|
settings = read_params(params)
|
|
self.assertFalse(settings.enabled)
|
|
self.assertTrue(settings.errors)
|
|
self.assertEqual(path.read_bytes(), raw)
|
|
self.assertTrue(params.get_bool('SpeedLimitController'))
|
|
|
|
def test_absent_optional_values_keep_existing_defaults(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
params = Params(directory)
|
|
params.put_bool('SpeedLimitController', True, block=True)
|
|
settings = read_params(params)
|
|
self.assertTrue(settings.enabled)
|
|
self.assertFalse(settings.errors)
|
|
self.assertTrue(all(band.offset_mps == 0 for band in settings.offsets.bands))
|
|
|
|
def test_valid_native_values_preserve_explicit_zero_and_units(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
params = Params(directory)
|
|
params.put_bool('SpeedLimitController', True, block=True)
|
|
params.put_bool('SLCConfirmation', True, block=True)
|
|
params.put_bool('SLCConfirmationLower', True, block=True)
|
|
params.put('SLCFallback', 0, block=True)
|
|
document = od.OffsetDocument(od.IMPERIAL_BOUNDS, (0, 0, -5 * MPH_TO_MPS, 0, 0, 0, 0))
|
|
params.put('SLCOffsetSchedule', od.to_value(document), block=True)
|
|
settings = read_params(params)
|
|
self.assertTrue(settings.enabled)
|
|
self.assertFalse(settings.errors)
|
|
self.assertTrue(settings.acceptance.confirm_lower)
|
|
self.assertAlmostEqual(settings.offsets.bands[2].offset_mps, -5 * MPH_TO_MPS)
|
|
|
|
def test_retired_fallback_bytes_are_ignored_without_creating_or_rewriting_them(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
params = Params(directory)
|
|
params.put_bool('SpeedLimitController', True, block=True)
|
|
path = Path(params.get_param_path('SLCFallback'))
|
|
baseline = read_params(params)
|
|
self.assertTrue(baseline.enabled)
|
|
self.assertFalse(path.exists())
|
|
self.assertIsNone(params.get_default_value('SLCFallback'))
|
|
for raw in (b'0', b'1', b'2', b'', b'broken', b'999', b'\xff'):
|
|
with self.subTest(raw=raw):
|
|
path.write_bytes(raw)
|
|
self.assertEqual(read_params(params), baseline)
|
|
self.assertEqual(path.read_bytes(), raw)
|
|
for old in (0, 1, 2, None, 'broken'):
|
|
self.assertEqual(parse({'SpeedLimitController': True, 'SLCFallback': old}), baseline)
|
|
|
|
def test_nonfinite_or_unrepresentable_current_offset_disables_control(self):
|
|
for value in (float('nan'), float('inf'), 10 ** 1000):
|
|
with self.subTest(kind=type(value).__name__):
|
|
document = od.OffsetDocument(od.IMPERIAL_BOUNDS, (0, 0, value, 0, 0, 0, 0))
|
|
settings = parse({'SpeedLimitController': True, 'SLCOffsetSchedule': document})
|
|
self.assertFalse(settings.enabled)
|
|
self.assertTrue(settings.errors)
|
|
|
|
def test_retired_offsets_are_unregistered_and_ignored_with_zero_defaults(self):
|
|
for metric in (False, True):
|
|
with self.subTest(metric=metric), tempfile.TemporaryDirectory() as directory:
|
|
params = Params(directory)
|
|
params.put_bool('SpeedLimitController', True, block=True)
|
|
params.put_bool('IsMetric', metric, block=True)
|
|
for index in range(1, 8):
|
|
key = f'Offset{index}'
|
|
with self.assertRaises(UnknownKeyName):
|
|
params.check_key(key)
|
|
Path(params.get_param_path(key)).write_bytes(b'3' if index % 2 else b'broken')
|
|
settings = read_params(params)
|
|
self.assertTrue(settings.enabled)
|
|
self.assertEqual(settings.offsets, od.default_document(metric).schedule())
|
|
self.assertFalse(Path(params.get_param_path('SLCOffsetSchedule')).exists())
|
|
for retired in (3, float('nan'), 'broken', 10 ** 1000):
|
|
self.assertEqual(parse({'SpeedLimitController': True, 'IsMetric': metric, 'Offset1': retired}), settings)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|