mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-09 14:03:42 +08:00
a82935f326
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>
136 lines
5.9 KiB
Python
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]
|