Files
StarPilot/openpilot/starpilot/ui/tests/test_c4_toggles.py
T

109 lines
4.9 KiB
Python

"""C4 native toggle behavior, including audio saves during Params contention."""
import fcntl
from pathlib import Path
from types import SimpleNamespace as NS
import tempfile
import threading
import time
import unittest
from unittest.mock import Mock, call, patch
import pyray as rl
from openpilot.common.params import Params
from openpilot.selfdrive.ui.layouts.settings import common
from openpilot.selfdrive.ui.mici.layouts.settings import toggles
from openpilot.selfdrive.ui.mici.widgets import button
from openpilot.system.ui.lib.application import gui_app, MousePos
class TestC4SafeMode(unittest.TestCase):
def test_safe_mode_clears_experimental_choice_and_relaxes_personality(self):
with tempfile.TemporaryDirectory() as directory:
params = Params(directory)
params.put_bool("SafeMode", True, block=True)
params.put_bool("ExperimentalMode", True, block=True)
params.put("LongitudinalPersonality", 0, block=True)
panel = toggles.TogglesLayoutMici.__new__(toggles.TogglesLayoutMici)
panel._safe_mode_btn = Mock()
panel._experimental_btn = Mock()
panel._personality_toggle = Mock()
panel._metric_toggle = Mock()
panel._initial_cruise_speed = Mock(spec=toggles.InitialCruiseSpeedButton)
self.enterContext(patch.object(panel, "_slc_offsets", NS(_raw=lambda _key: b"0"), create=True))
toggles_to_refresh = (("SafeMode", panel._safe_mode_btn), ("ExperimentalMode", panel._experimental_btn))
self.enterContext(patch.object(panel, "_refresh_toggles", toggles_to_refresh, create=True))
fake_ui = NS(params=params, CP=None, update_params=Mock())
with patch.object(toggles, "ui_state", fake_ui):
panel._update_toggles()
panel._initial_cruise_speed.refresh.assert_called_once_with()
self.assertFalse(params.get_bool("ExperimentalMode"))
self.assertEqual(params.get("LongitudinalPersonality"), 2)
panel._experimental_btn.set_enabled.assert_called_once_with(False)
panel._personality_toggle.set_enabled.assert_called_once_with(False)
panel._personality_toggle.set_value.assert_called_once_with("relaxed")
class TestC4AudioToggle(unittest.TestCase):
def setUp(self):
directory = tempfile.TemporaryDirectory()
self.addCleanup(directory.cleanup)
self.directory = directory.name
self.params = Params(directory.name)
self.params.put_bool("RecordAudio", False, block=True)
self.ui = NS(params=self.params, CP=None, engaged=False, started=False,
add_engaged_transition_callback=Mock(), update_params=Mock())
self.enterContext(patch.object(toggles, "ui_state", self.ui))
self.enterContext(patch.object(common, "ui_state", self.ui))
self.enterContext(patch.object(button, "Params", return_value=self.params))
self.enterContext(patch.object(gui_app, "font", return_value=rl.Font()))
self.enterContext(patch.object(gui_app, "texture", return_value=NS(width=64, height=64)))
self.panel = toggles.TogglesLayoutMici()
self.audio = next(item for key, item in self.panel._refresh_toggles if key == "RecordAudio")
def test_release_queues_audio_before_restart_without_blocking_writes(self):
with patch.object(self.params, "put_bool") as save:
self.audio._handle_mouse_release(MousePos(0, 0))
self.audio._handle_mouse_release(MousePos(0, 0))
self.assertEqual(save.call_args_list, [call("RecordAudio", True), call("OnroadCycleRequested", True),
call("RecordAudio", False), call("OnroadCycleRequested", True)])
def test_release_returns_while_params_lock_is_held_then_persists_in_order(self):
finished = threading.Event()
errors = []
def release():
try:
self.audio._handle_mouse_release(MousePos(0, 0))
except Exception as error:
errors.append(error)
finally:
finished.set()
with (Path(self.directory) / ".lock").open("a") as lock:
fcntl.flock(lock, fcntl.LOCK_EX)
worker = threading.Thread(target=release, daemon=True)
worker.start()
try:
self.assertTrue(finished.wait(1), "Audio touch handler waited for the Params filesystem lock")
self.assertFalse(self.params.get_bool("RecordAudio"))
self.assertFalse(self.params.get_bool("OnroadCycleRequested"))
finally:
fcntl.flock(lock, fcntl.LOCK_UN)
worker.join(timeout=2)
self.assertEqual(errors, [])
deadline = time.monotonic() + 2
while not self.params.get_bool("OnroadCycleRequested") and time.monotonic() < deadline:
time.sleep(0.01)
self.assertTrue(self.params.get_bool("OnroadCycleRequested"))
self.assertTrue(self.params.get_bool("RecordAudio"))
def test_audio_refresh_and_engaged_guard_remain_effective(self):
self.params.put_bool("RecordAudio", True, block=True)
self.panel._update_toggles()
self.assertTrue(self.audio._checked)
self.assertTrue(self.audio.enabled)
self.ui.engaged = True
self.assertFalse(self.audio.enabled)