Files
StarPilot/openpilot/starpilot/audio/tests/test_stream_diagnostics.py
T
firestar5683 a82935f326 User interfaces and Android Auto
Integrate onroad views, configurable widgets, local settings, steering-profile contracts, Bluetooth and Android Auto with their device and runtime services.

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
Co-authored-by: Daniel Koepping <elkoled@gmail.com>
Co-authored-by: Danny <dannyirwin31@yahoo.com>
Co-authored-by: DimitriChrysafis <Dimitri.chrysafis@gmail.com>
Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com>
Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu>
Co-authored-by: Shane Smiskol <shane@smiskol.com>
Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com>
Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>
Co-authored-by: stef <19478336+stefpi@users.noreply.github.com>
Co-authored-by: whoisdomi <dcantu83@gmail.com>

Co-authored-by: Intelli <contact@intelli.software>
Co-authored-by: pharmacomaniac <blittle65@gmail.com>

Co-authored-by: prabhaavp <prabhaav@msu.edu>
2026-10-14 20:02:39 -05:00

136 lines
5.9 KiB
Python

"""Capture transport fault frequency without doing logging in PortAudio's callback."""
from types import SimpleNamespace
from unittest.mock import Mock, patch
import numpy as np
from openpilot.selfdrive.ui import soundd
def make_daemon():
daemon = soundd.Soundd.__new__(soundd.Soundd)
daemon.pending_stream_status = None
daemon.stream_status_count = 0
daemon.output_underflow_count = 0
daemon.reported_output_underflows = 0
daemon.stream_report_at = None
samples = np.array([0.5, -0.5, 0., 0.25], dtype=np.float32)
daemon.get_sound_data = Mock(side_effect=lambda frames: samples[:frames])
return daemon, samples
def test_underflow_counters_preserve_signal_and_log_on_service_thread():
daemon, samples = make_daemon()
output = np.empty((4, 1), dtype=np.float32)
underflow = SimpleNamespace(output_underflow=True)
other = SimpleNamespace(output_underflow=False)
with patch.object(soundd.cloudlog, "warning") as warning, patch.object(soundd.cloudlog, "error") as error, \
patch.object(soundd.time, "monotonic", return_value=2.):
for status in (underflow, underflow, other, None):
daemon.callback(output, 4, None, status)
np.testing.assert_array_equal(output[:, 0], samples)
warning.assert_not_called()
error.assert_not_called()
assert daemon.stream_status_count == 3
assert daemon.output_underflow_count == 2
daemon.log_pending_stream_status(SimpleNamespace(latency=0.2, cpu_load=0.01))
warning.assert_not_called()
error.assert_called_once_with(
f"soundd stream diagnostics: status={other} status_callbacks=3 output_underflows=2")
daemon.log_pending_stream_status(SimpleNamespace(latency=0.2, cpu_load=0.01))
assert error.call_count == 1
def test_underflow_reports_coalesce_without_losing_the_last_dropout():
daemon, _ = make_daemon()
output = np.empty((4, 1), dtype=np.float32)
underflow = SimpleNamespace(output_underflow=True)
with patch.object(soundd.cloudlog, "error") as error, patch.object(soundd.time, "monotonic") as clock:
clock.return_value = 0.
daemon.callback(output, 4, None, underflow)
daemon.log_pending_stream_status()
for now in (0.1, 1., 4.9):
clock.return_value = now
daemon.callback(output, 4, None, underflow)
daemon.log_pending_stream_status()
assert error.call_count == 1
clock.return_value = 5.
daemon.log_pending_stream_status()
assert error.call_count == 2
assert "output_underflows=4" in error.call_args.args[0]
clock.return_value = 10.
daemon.log_pending_stream_status()
assert error.call_count == 2
def test_other_status_stays_a_warning_and_underflow_counter_survives_status_clear():
daemon, _ = make_daemon()
output = np.empty((4, 1), dtype=np.float32)
with patch.object(soundd.cloudlog, "error") as error, patch.object(soundd.cloudlog, "warning") as warning, \
patch.object(soundd.time, "monotonic", return_value=0.) as clock:
daemon.callback(output, 4, None, SimpleNamespace(output_underflow=False))
daemon.log_pending_stream_status()
warning.assert_called_once()
error.assert_not_called()
daemon.callback(output, 4, None, SimpleNamespace(output_underflow=True))
daemon.pending_stream_status = None
clock.return_value = 5.
daemon.log_pending_stream_status()
error.assert_called_once()
assert "output_underflows=1" in error.call_args.args[0]
def test_status_callbacks_preserve_samples_and_defer_health_reports():
daemon, short_samples = make_daemon()
samples = np.tile(short_samples, 1024)
daemon.get_sound_data = Mock(side_effect=lambda frames: samples[:frames])
output = np.empty((4096, 1), dtype=np.float32)
underflow = SimpleNamespace(output_underflow=True)
with patch.object(soundd.time, "monotonic", side_effect=[0., 5.]), \
patch("pathlib.Path.open", side_effect=AssertionError("callback file IO")), \
patch.object(soundd.cloudlog, "error") as error, patch.object(soundd.cloudlog, "warning") as warning:
for status in (None, underflow):
daemon.callback(output, 4096, None, status)
assert output[:, 0].tobytes() == samples.tobytes()
error.assert_not_called()
warning.assert_not_called()
daemon.log_pending_stream_status()
assert "output_underflows=1" in error.call_args.args[0]
for status in (underflow, None):
daemon.callback(output, 4096, None, status)
assert output[:, 0].tobytes() == samples.tobytes()
assert error.call_count == 1
warning.assert_not_called()
daemon.log_pending_stream_status()
assert error.call_count == 2
assert "status_callbacks=2 output_underflows=2" in error.call_args.args[0]
def test_native_clock_metadata_does_not_change_output_or_health_counts():
daemon, samples = make_daemon()
output = np.empty((4, 1), dtype=np.float32)
underflow = SimpleNamespace(output_underflow=True)
with patch.object(soundd.time, "monotonic", side_effect=[0., 5., 10., 15., 20.]), \
patch.object(soundd.cloudlog, "error") as error:
for count, native in enumerate((10., float("nan"), 9., 8., -1.), 1):
daemon.callback(output, 4, SimpleNamespace(currentTime=native, outputBufferDacTime=float("inf")), underflow)
assert output[:, 0].tobytes() == samples.tobytes()
assert daemon.output_underflow_count == count
daemon.log_pending_stream_status()
assert f"output_underflows={count}" in error.call_args.args[0]
assert error.call_count == 5
def test_proc_failure_does_not_affect_underflow_report_or_samples():
daemon, samples = make_daemon()
output = np.empty((4, 1), dtype=np.float32)
with patch("pathlib.Path.open", side_effect=FileNotFoundError) as file_open, \
patch.object(soundd.cloudlog, "error") as error:
daemon.callback(output, 4, None, SimpleNamespace(output_underflow=True))
assert output[:, 0].tobytes() == samples.tobytes()
error.assert_not_called()
daemon.log_pending_stream_status()
file_open.assert_not_called()
assert "output_underflows=1" in error.call_args.args[0]