Files
StarPilot/openpilot/starpilot/ui/tests/test_speed_stack.py
T
2026-10-26 20:38:11 -04:00

96 lines
4.9 KiB
Python

"""Regression checks for qualified values, saved geometry and request feedback."""
from dataclasses import replace
from unittest.mock import Mock
from openpilot.starpilot.ui.onroad_state import ObservationKind, OnroadInput, OnroadState, SlcActionKind, SlcUiRequest, SpeedLimitObservation
from openpilot.starpilot.ui.onroad_customization import default_document, validate_document, rgba
from openpilot.starpilot.ui.slc_action_dispatch import SlcActionDispatcher
from openpilot.starpilot.ui.tests.test_slc_ui_actions import native_state, NOW
from openpilot.starpilot.speed_limits.speed_domain import adjusted_limit, OffsetBand, OffsetSchedule
from openpilot.starpilot.speed_limits.runtime import Runtime
from openpilot.starpilot.speed_limits.runtime_settings import parse
from openpilot.starpilot.ui.tests.test_slc_ui_runtime import START, replay_inputs, publish_request
def road(document=None):
obs = SpeedLimitObservation(ObservationKind.VALID, 'dashboard', 20, accepted_speed_limit_mps=20,
accepted_adjusted_limit_mps=22, session_id='drive-session', presentation_id=9,
decision_id=7, action_enabled=True)
return OnroadState(True, False, 20, 100, obs, longitudinal_active=True, slc_system_long_available=True,
customization=document or default_document())
def test_offset_proposals_use_their_own_band_and_reject_invalid_values():
schedule = OffsetSchedule((OffsetBand(0, 20, 1), OffsetBand(20, None, 3)))
assert adjusted_limit(19, schedule) == 20
assert adjusted_limit(20, schedule) == 23
assert adjusted_limit(float('nan'), schedule) is None
assert adjusted_limit(10, OffsetSchedule((OffsetBand(0, None, -20),))) == 10
def test_numeric_card_and_sources_row_are_not_tap_targets_while_pending():
state = road()
state = replace(state, speed_limit=replace(state.speed_limit, pending_speed_limit_mps=25))
emitted = []
touch = OnroadInput(emitted.append)
touch.press(120, 320, state)
touch.release(120, 320, state)
assert not emitted
# Where the removed "View sources" row used to sit; sources now follow the Galaxy setting only.
touch.press(100, 709, state)
assert not touch.claimed
touch.release(100, 709, state)
assert not emitted
def test_v4_migration_preserves_settings_and_clamps_only_enlarged_widgets():
doc = default_document()
doc['version'] = 4
doc['palette']['cardFill'] = '#12345678'
doc['speedSources'] = True
doc['layouts']['large']['cruise_limits'].update(x=1654, y=75)
doc['layouts']['large']['speed_limit_actions'].update(x=1654, y=500)
old_speed = dict(doc['layouts']['large']['current_speed'])
migrated = validate_document(doc)
assert migrated['version'] == 12
assert migrated['layouts']['large']['cruise_limits']['x'] == 1539
assert migrated['layouts']['large']['speed_limit_actions'] == {'x': 1678, 'y': 267, 'enabled': True}
assert migrated['layouts']['large']['current_speed'] == old_speed
assert migrated['speedSources'] is True
assert rgba(migrated, 'cardFill', 'large', 'cruise_limits') == (18, 52, 86, 120)
assert validate_document(migrated) == migrated
def test_feedback_requires_matching_receipts_and_command_application():
now = [NOW]
dispatcher = SlcActionDispatcher(Mock(), lambda _: native_state(), lambda: now[0])
assert dispatcher.dispatch(SlcUiRequest(SlcActionKind.ACCEPT, 'drive-session', 7, 9, 22))
obs = road().speed_limit
assert dispatcher.feedback(obs) == 'Waiting…'
obs = replace(obs, action_sequence_id=NOW, action_status='accept')
assert dispatcher.feedback(obs) == 'Limit accepted'
obs = replace(obs, command_action_sequence_id=NOW, command_status='issued')
assert dispatcher.feedback(obs) == 'Applying…'
assert dispatcher.feedback(replace(obs, command_status='applied')) == 'Applied'
assert dispatcher.feedback(replace(obs, command_status='expired')) == 'Setpoint not applied'
assert dispatcher.feedback(replace(obs, session_id='next-drive')) == ''
def test_publisher_receipt_survives_next_cycle_and_resets_with_session():
sm, cp = replay_inputs()
runtime = Runtime(parse({'SpeedLimitController': True, 'SLCConfirmation': True, 'SLCConfirmationHigher': True}), session_id='drive')
pending = runtime.step(sm, cp, now_ns=START).message.slcState
request = SlcUiRequest(SlcActionKind.ACCEPT, 'drive', pending.decisionId, pending.presentationId, pending.pendingSpeedLimit)
action = publish_request(pending, request, START)
sm.advance(START + 50_000_000)
accepted = runtime.step(sm, cp, now_ns=START + 50_000_000, request=action).message.slcState
sm.advance(START + 100_000_000)
next_frame = runtime.step(sm, cp, now_ns=START + 100_000_000).message.slcState
assert accepted.actionSequenceId == next_frame.actionSequenceId == action.sequence_id
assert accepted.actionStatus == next_frame.actionStatus == 'accept'
assert accepted.commandActionSequenceId == next_frame.commandActionSequenceId == action.sequence_id
runtime.reset()
assert runtime._ui_receipt is None
assert runtime._command_ui_sequence == 0