Files
StarPilot/selfdrive/ui/tests/test_gpu_model_ready_sound.py
T
AngusBell97 46596218ba Add an optional GPU-model-ready chime
(cherry picked from commit 0a212734c1)
2026-09-10 16:41:45 -05:00

71 lines
3.0 KiB
Python

import ast
from pathlib import Path
from types import SimpleNamespace
import wave
import pytest
from openpilot.starpilot.common.gpu_model_ready_sound import GpuModelReadyChime
def update(chime, now=0, **kwargs):
return chime.update(**dict(active=False, loading=False, onroad=True, enabled=True, now=now) | kwargs)
def test_ready_once_after_success_not_during_loading_or_failure():
chime = GpuModelReadyChime()
assert not update(chime, loading=True)
assert not update(chime, 1, active=True, loading=True)
assert update(chime, 2, active=True)
chime.consume()
assert not update(chime, 3, active=True)
assert not update(chime, 4, loading=True)
assert not update(chime, 5)
def test_restart_with_already_ready_model_does_not_chime():
assert not update(GpuModelReadyChime(), active=True)
@pytest.mark.parametrize("cancel", [dict(enabled=False), dict(onroad=False), dict(active=False), dict(loading=True)])
def test_cancel_pending_on_disabled_offroad_fallback_or_reload(cancel):
chime = GpuModelReadyChime()
update(chime)
assert update(chime, 1, active=True)
assert not update(chime, 2, **(dict(active=True) | cancel))
def test_pending_notification_expires_without_late_chime():
chime = GpuModelReadyChime()
update(chime)
assert update(chime, 1, active=True)
assert not update(chime, 6, active=True)
assert not update(chime, 7, active=True)
def test_enabling_option_after_load_does_not_replay():
chime = GpuModelReadyChime()
update(chime, enabled=False)
assert not update(chime, 1, active=True, enabled=False)
assert not update(chime, 2, active=True, enabled=True)
@pytest.mark.parametrize("alert,timeout,valid,alive,expected", [(1,False,True,True,False),(0,True,True,True,False),(0,False,False,True,False),(0,False,True,False,False),(0,False,True,True,True)])
def test_soundd_never_replaces_existing_or_timeout_alerts(alert,timeout,valid,alive,expected):
source=(Path(__file__).parents[1]/'soundd.py').read_text()
cls=next(n for n in ast.parse(source).body if isinstance(n,ast.ClassDef) and n.name=='Soundd')
method=next(n for n in cls.body if isinstance(n,ast.FunctionDef) and n.name=='update_model_ready_sound')
scope={'time':SimpleNamespace(monotonic=lambda:1.1),'AudibleAlert':SimpleNamespace(none=0),'GPU_MODEL_READY_ALERT':2000}
exec(compile(ast.Module(body=[method],type_ignores=[]),'<soundd method>','exec'),scope)
calls=[]
sound=SimpleNamespace(model_ready_last_check=1.0,model_ready_pending=True,model_ready_chime=GpuModelReadyChime(),current_alert=alert,selfdrive_timeout_alert=timeout,update_alert=calls.append)
sm=SimpleNamespace(valid={'selfdriveState':valid},alive={'selfdriveState':alive})
scope['update_model_ready_sound'](sound,sm)
assert calls == ([2000] if expected else [])
def test_notification_asset_matches_soundd_audio_format():
with wave.open(str(Path(__file__).parents[2]/'assets/sounds/model_ready.wav')) as audio:
assert (audio.getnchannels(),audio.getsampwidth(),audio.getframerate()) == (1,2,48000)
assert 0 < audio.getnframes() < 48000