Files
StarPilot/selfdrive/controls/tests/test_unified_longitudinal_intent.py
2026-07-24 02:01:57 -05:00

160 lines
4.4 KiB
Python

from types import SimpleNamespace
import pytest
from openpilot.starpilot.common.experimental_state import CEStatus
from openpilot.starpilot.controls.lib.unified_longitudinal_intent import (
MODEL_STOP_ENTER,
UnifiedLongitudinalIntent,
)
class FakeParamsMemory:
def __init__(self):
self.values = {}
def put_int(self, key, value):
self.values[key] = value
def make_controller():
planner = SimpleNamespace(
params_memory=FakeParamsMemory(),
driving_in_curve=False,
road_curvature_detected=False,
starpilot_vcruise=SimpleNamespace(
forcing_stop=False,
stop_sign_confirmed=False,
slc=SimpleNamespace(experimental_mode=False),
),
)
return planner, UnifiedLongitudinalIntent(planner)
def make_sm(*, v_ego=10.0, should_stop=False, model_distance=100.0,
end_speed=10.0, traffic=False, lead_one=None, lead_two=None):
model = SimpleNamespace(
action=SimpleNamespace(shouldStop=should_stop),
position=SimpleNamespace(x=[0.0, model_distance]),
velocity=SimpleNamespace(x=[v_ego, end_speed]),
)
empty_lead = SimpleNamespace(status=False)
return {
"modelV2": model,
"carState": SimpleNamespace(
standstill=v_ego == 0.0,
leftBlinker=False,
rightBlinker=False,
steeringAngleDeg=0.0,
),
"radarState": SimpleNamespace(
leadOne=lead_one or empty_lead,
leadTwo=lead_two or empty_lead,
),
"starpilotCarState": SimpleNamespace(trafficModeEnabled=traffic),
}
def toggles(model_first=False):
return SimpleNamespace(longitudinal_model_preference=model_first)
def settle(controller, sm, *, v_ego=10.0, frames=20):
for _ in range(frames):
controller.update(v_ego, sm, toggles())
def test_open_road_has_no_stop_intent():
_, controller = make_controller()
sm = make_sm()
settle(controller, sm)
assert not controller.stop_detected
assert controller.status_value == CEStatus["OFF"]
def test_model_stop_is_filtered_then_latched():
_, controller = make_controller()
sm = make_sm(model_distance=5.0, end_speed=0.0)
while controller.stop_filter.x < MODEL_STOP_ENTER:
controller.update(10.0, sm, toggles())
assert controller.stop_detected
assert controller.status_value == CEStatus["STOP_LIGHT"]
def test_stop_intent_releases_with_hysteresis():
_, controller = make_controller()
stop_scene = make_sm(should_stop=True)
settle(controller, stop_scene)
assert controller.stop_detected
clear_scene = make_sm()
controller.update(10.0, clear_scene, toggles())
assert controller.stop_detected
settle(controller, clear_scene)
assert not controller.stop_detected
@pytest.mark.parametrize("hard_stop", ["forcing_stop", "stop_sign_confirmed"])
def test_explicit_stop_sources_pin_intent(hard_stop):
planner, controller = make_controller()
setattr(planner.starpilot_vcruise, hard_stop, True)
controller.update(5.0, make_sm(v_ego=5.0), toggles())
assert controller.stop_detected
assert controller.status_value == CEStatus["STOP_LIGHT"]
def test_committed_turn_vetoes_model_stop_false_positive():
planner, controller = make_controller()
planner.driving_in_curve = True
sm = make_sm(v_ego=4.0, should_stop=True)
sm["carState"].leftBlinker = True
sm["carState"].steeringAngleDeg = 60.0
settle(controller, sm, v_ego=4.0)
assert not controller.stop_detected
def test_traffic_mode_vetoes_model_stop_but_not_explicit_force_stop():
planner, controller = make_controller()
sm = make_sm(should_stop=True, traffic=True)
settle(controller, sm)
assert not controller.stop_detected
planner.starpilot_vcruise.forcing_stop = True
controller.update(10.0, sm, toggles())
assert controller.stop_detected
def test_either_credible_slow_lead_slot_sets_diagnostic_status():
_, controller = make_controller()
slow_lead = SimpleNamespace(
status=True,
dRel=25.0,
vLead=4.0,
modelProb=0.95,
radar=False,
)
sm = make_sm(v_ego=10.0, lead_two=slow_lead)
settle(controller, sm)
assert not controller.stop_detected
assert controller.status_value == CEStatus["LEAD"]
def test_model_first_preference_is_status_only_not_a_separate_controller():
planner, controller = make_controller()
controller.update(10.0, make_sm(), toggles(model_first=True))
assert not controller.stop_detected
assert controller.status_value == CEStatus["USER_OVERRIDDEN"]
assert planner.params_memory.values["CEStatus"] == CEStatus["USER_OVERRIDDEN"]