mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 13:33:42 +08:00
152 lines
7.9 KiB
Python
152 lines
7.9 KiB
Python
"""Regression checks for qualified values, saved geometry and request feedback."""
|
|
from dataclasses import replace
|
|
from types import SimpleNamespace as N
|
|
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.large_speed_geometry import source_toggle_bounds, source_panel_bounds
|
|
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),))) is None
|
|
|
|
|
|
def test_numeric_card_is_not_an_accept_target_but_sources_work_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
|
|
x, y, _, _ = source_toggle_bounds(state)
|
|
touch.press(x + 12, y + 12, state)
|
|
touch.release(x + 12, y + 12, state)
|
|
assert emitted[0].kind == 'set_speed_sources'
|
|
|
|
|
|
def test_saved_edge_geometry_keeps_toggle_and_panel_accessible():
|
|
viewport = N(x=30, y=30, width=1800, height=1020)
|
|
for card_x, card_y in ((88, 75), (1350, 528), (88, 528)):
|
|
for action_y in (567, 750, 942):
|
|
doc = default_document()
|
|
doc['layouts']['large']['cruise_limits'].update(x=card_x, y=card_y)
|
|
doc['layouts']['large']['speed_limit_actions'].update(x=card_x, y=action_y)
|
|
state = road(validate_document(doc))
|
|
left, top, right, bottom = source_toggle_bounds(state)
|
|
assert 30 <= left < right <= 1830
|
|
assert 30 <= top < bottom <= 1050
|
|
bounds = source_panel_bounds(state, 200, viewport)
|
|
assert bounds is not None
|
|
x, y, width, height = bounds
|
|
assert 30 <= x and x + width <= 1830
|
|
assert 30 <= y and y + height <= 1050
|
|
|
|
|
|
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'] == 6
|
|
assert migrated['layouts']['large']['cruise_limits']['x'] == 1486
|
|
assert migrated['layouts']['large']['speed_limit_actions'] == {'x': 1486, 'y': 567, '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
|
|
|
|
|
|
def test_disengaging_during_receipt_keeps_sources_separate_from_feedback():
|
|
state = road()
|
|
state = replace(state, longitudinal_active=False, speed_limit=replace(state.speed_limit, action_feedback='Applied'))
|
|
_, top, _, _ = source_toggle_bounds(state)
|
|
assert top >= 447 + 36 + 8
|
|
assert source_panel_bounds(state, 200, N(x=30, y=30, width=1800, height=1020)) is not None
|
|
|
|
|
|
def test_sources_popup_moves_past_gauge_and_keeps_enabled_widget_footprints_clear():
|
|
from openpilot.starpilot.ui.large_speed_geometry import GAP
|
|
from openpilot.starpilot.ui.onroad_customization import CAMERA_WIDGETS, CEM_GAUGE_WIDGET, PROFILES, widget_size
|
|
|
|
from openpilot.starpilot.conditional_mode.policy import ModeChoice
|
|
from openpilot.starpilot.ui.conditional_status import ConditionalDisplay
|
|
|
|
viewport = N(x=30, y=30, width=1800, height=1020)
|
|
doc = default_document()
|
|
doc['layouts']['large']['speed_limit_actions']['y'] = 750
|
|
state = replace(road(validate_document(doc)), conditional_configured=ModeChoice.CEM,
|
|
conditional_perception=ConditionalDisplay(ModeChoice.CEM, False, 'cem_stop', 8, 'planner', 1))
|
|
bounds = source_panel_bounds(state, 200, viewport)
|
|
assert bounds is not None
|
|
x, y, width, height = bounds
|
|
gauge = state.customization['layouts']['large'][CEM_GAUGE_WIDGET]
|
|
gauge_width, _ = widget_size(state.customization, 'large', CEM_GAUGE_WIDGET)
|
|
assert gauge['enabled'] and x >= gauge['x'] + gauge_width + GAP
|
|
assert viewport.x <= x and x + width <= viewport.x + viewport.width
|
|
assert viewport.y <= y and y + height <= viewport.y + viewport.height
|
|
for key in PROFILES['large']['widgets']:
|
|
saved = state.customization['layouts']['large'][key]
|
|
if saved['enabled'] and key not in CAMERA_WIDGETS:
|
|
widget_width, widget_height = widget_size(state.customization, 'large', key)
|
|
assert (x + width <= saved['x'] or x >= saved['x'] + widget_width or
|
|
y + height <= saved['y'] or y >= saved['y'] + widget_height), key
|
|
toggle_left, toggle_top, toggle_right, toggle_bottom = source_toggle_bounds(state)
|
|
assert (x + width <= toggle_left or x >= toggle_right or y + height <= toggle_top or y >= toggle_bottom)
|