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

640 lines
35 KiB
Python

"""Saved display choices in real disposable Params and native UI adapters."""
from unittest.mock import Mock, patch
from dataclasses import replace
import os
from types import SimpleNamespace as NS
from pathlib import Path
import tempfile
import unittest
from openpilot.common.params import Params
from openpilot.starpilot.ui import display_compact
from openpilot.starpilot.ui.display_owner import DisplayOwner
from openpilot.starpilot.ui.display_preferences import MASTER, SCREEN_OFF, read_choice, read_preferences
from openpilot.starpilot.ui.feature_settings_state import (
FEATURE_CONTROL_LEFT, FEATURE_CONTROL_RIGHT, FEATURE_ROW_HEIGHT, FEATURE_VISIBLE_ROWS,
FeatureInput, FeatureSettingsRequest, feature_row_top, row_change,
FeatureSettingsState, FeatureUiAction, feature_action_rects, value_buttons, value_done_rect,
)
from openpilot.starpilot.ui.presentation import Profile
from openpilot.starpilot.ui.settings_state import Destination, SettingsAction, SettingsActionKind, SettingsInput, SettingsState, tile_rects
from openpilot.starpilot.ui.shell import ShellInput, ShellMode, ShellRequest, ShellSnapshot
from openpilot.starpilot.ui.power_owner import PowerOwner, HOURS, VOLTS
from openpilot.system.ui.widgets import DialogResult
from openpilot.starpilot.ui.tests.test_runtime_snapshot import NOW, ui_fake
class DisplaySettingsTests(unittest.TestCase):
def setUp(self):
temporary = tempfile.TemporaryDirectory()
self.addCleanup(temporary.cleanup)
self.params_root = temporary.name
self.params = Params(temporary.name)
self.parked = True
self.owner = DisplayOwner(self.params, lambda: self.parked)
def row(self, key, profile=Profile.LARGE):
return next(row for row in self.owner.snapshot(profile).rows if row.key == key)
def request(self, key, profile=Profile.LARGE):
request = row_change(self.row(key, profile))
assert request is not None
return request
def large_session(self, expanded=True):
from openpilot.starpilot.ui.runtime_app import StarShellSession
session = StarShellSession.__new__(StarShellSession)
session.profile = Profile.LARGE
session._mode, session.selected = ShellMode.SETTINGS, Destination.SYSTEM
session.display_owner = self.owner
session.power_owner = PowerOwner(self.params, lambda: self.parked)
session.map_snapshot = Mock(return_value=FeatureSettingsState())
self.enterContext(patch.object(session, 'drive_state',
NS(snapshot=lambda: {"mode": "auto", "revision": None, "available": False,
"effective": None, "overrideAllowed": False}), create=True))
session.display_scroll, session.display_edit_key = 0, None
session.display_page = "system"
session.sidebar_expanded = expanded
session._snapshot_cache = None
session._on_destination_change = None
session.confirmed_offroad = Mock(return_value=True)
session.favorites, session.view = Mock(), Mock()
session.view.display.reset.return_value = False
session._unavailable = Mock()
session.input = ShellInput(Profile.LARGE, session._emit)
return session
def test_system_pages_keep_every_control_in_a_stable_group(self):
from openpilot.starpilot.ui.maps_state import map_page
session = self.large_session()
first = ("drive_state", "device_power", MASTER, "power:enabled", HOURS)
for enabled in (False, True):
self.params.put_bool(MASTER, enabled, block=True)
for brightness in (50, 101):
self.params.put("ScreenBrightness", brightness, block=True)
for status in (None, "idle", "transferring", "failed"):
maps = map_page(None if status is None else {"schemaVersion": 1, "state": status,
"selectedGeneration": "", "completedGroups": 1, "totalGroups": 2})
session.map_snapshot.return_value = maps
rows = session.system_snapshot().rows
self.assertEqual(tuple(row.key for row in rows[:5]), first)
self.assertEqual(rows[5].key, VOLTS)
self.assertEqual(rows[6:], maps.rows)
session._display_ui(FeatureUiAction("open", rows[2]))
submenu = session.system_snapshot()
self.assertEqual(tuple(row.key for row in submenu.rows), (MASTER, "ScreenBrightness", "ScreenBrightnessOnroad",
"ScreenTimeout", "ScreenTimeoutOnroad", SCREEN_OFF))
self.assertEqual(submenu.rows[1].source, self.row("ScreenBrightness").source)
self.assertEqual(submenu.rows[1].dependencies, self.row("ScreenBrightness").dependencies)
session._display_ui(FeatureUiAction("back"))
def test_system_modes_select_directly_and_cancel_changed_or_dragged_touches(self):
session = self.large_session()
session._drive_change = Mock()
status = {"mode": "auto", "revision": "shown-revision", "available": True,
"effective": "offroad", "overrideAllowed": True}
session.drive_state.snapshot = Mock(return_value=status)
inputs = FeatureInput(session._display_ui)
for expanded in (False, True):
for current in ("auto", "offroad", "onroad"):
status["mode"] = current
state = replace(session.system_snapshot(), sidebar_expanded=expanded)
row = state.rows[0]
rects = feature_action_rects(row, feature_row_top(state))
for index, desired in enumerate(("auto", "offroad", "onroad")):
session._drive_change.reset_mock()
x, y, width, height = rects[index]
point = x + width / 2, y + height / 2
inputs.press(*point, state)
session._drive_change.assert_not_called()
inputs.release(*point, state)
if desired == current:
session._drive_change.assert_not_called()
else:
session._drive_change.assert_called_once_with(desired, "shown-revision")
self.assertIsNone(inputs.target(rects[0][0] + rects[0][2] + 8, point[1], state))
status["mode"] = "auto"
state = session.system_snapshot()
row = state.rows[0]
x, y, width, height = feature_action_rects(row, feature_row_top(state))[2]
point = x + width / 2, y + height / 2
for changed in (replace(state, scroll=5), replace(state, sidebar_expanded=False),
replace(state, rows=(replace(row, source=b"new-revision"),) + state.rows[1:]),
replace(state, rows=(replace(row, available=False),) + state.rows[1:])):
session._drive_change.reset_mock()
inputs.press(*point, state)
inputs.release(*point, changed)
session._drive_change.assert_not_called()
inputs.press(*point, state)
inputs.move(point[0] - 50, point[1], state)
inputs.release(*point, state)
session._drive_change.assert_not_called()
status["overrideAllowed"] = False
fresh = session.system_snapshot()
self.assertIsNotNone(inputs.target(*point, fresh))
session._drive_change.reset_mock()
session._display_ui(FeatureUiAction("action", fresh.rows[0], 2))
session._drive_change.assert_called_once_with("onroad", "shown-revision")
self.assertTrue(fresh.rows[0].actions[1][1]) # Local Offroad remains available while moving.
session._drive_change.reset_mock()
session._display_ui(FeatureUiAction("action", fresh.rows[0], 1))
session._drive_change.assert_called_once_with("offroad", "shown-revision")
def test_display_submenu_opens_while_off_enabling_enters_and_back_keeps_the_hierarchy(self):
with patch("openpilot.starpilot.ui.runtime_app.native_device.invalidate_display_preferences"):
for expanded in (True, False):
session = self.large_session(expanded)
inputs = FeatureInput(session._display_ui)
root = replace(session.system_snapshot(), sidebar_expanded=expanded)
y = feature_row_top(root) + 2.5 * FEATURE_ROW_HEIGHT
inputs.press(800, y, root)
inputs.release(800, y, root)
submenu = session.system_snapshot()
self.assertEqual(submenu.page, "display")
self.assertEqual(len(submenu.rows), 6)
self.assertEqual(submenu.rows[0].key, MASTER)
self.assertTrue(all(not row.available for row in submenu.rows[1:] if row.key != SCREEN_OFF))
self.assertTrue(submenu.rows[-1].available)
self.assertFalse(read_preferences(self.params, large=True).enabled)
session._emit(ShellRequest("settings", SettingsAction(SettingsActionKind.CLOSE)))
self.assertEqual(session.display_page, "system")
root = replace(session.system_snapshot(), sidebar_expanded=expanded)
inputs.press(2000, y, root)
inputs.release(2000, y, root)
self.assertTrue(read_preferences(self.params, large=True).enabled)
submenu = session.system_snapshot()
self.assertEqual(submenu.page, "display")
self.assertTrue(all(row.available for row in submenu.rows))
self.assertEqual(submenu.parent_title, "System")
session._display_ui(FeatureUiAction("open", submenu.rows[1]))
self.assertEqual(session.system_snapshot().parent_title, "Custom Display Settings")
session._emit(ShellRequest("settings", SettingsAction(SettingsActionKind.CLOSE)))
self.assertIsNone(session.display_edit_key)
self.assertEqual(session.system_snapshot().page, "display")
session._display_ui(FeatureUiAction("change", session.system_snapshot().rows[0]))
self.assertEqual(session.system_snapshot().page, "display")
self.assertFalse(read_preferences(self.params, large=True).enabled)
self.assertTrue(all(not row.available for row in session.system_snapshot().rows[1:] if row.key != SCREEN_OFF))
session._display_ui(FeatureUiAction("back"))
self.parked = False
root = session.system_snapshot()
session._display_ui(FeatureInput.target(800, y, root))
self.assertEqual(session.system_snapshot().page, "display")
self.parked = True
def test_display_parent_does_not_enter_after_failed_enable_or_a_changed_page(self):
session = self.large_session()
Path(self.params.get_param_path("ScreenBrightness")).write_bytes(b"bad")
root = session.system_snapshot()
session._display_ui(FeatureUiAction("change", root.rows[2]))
self.assertEqual(session.display_page, "system")
self.assertFalse(read_preferences(self.params, large=True).enabled)
session._unavailable.assert_called_once()
inputs = FeatureInput(session._display_ui)
y = feature_row_top(root) + 2.5 * FEATURE_ROW_HEIGHT
inputs.press(2000, y, root)
session._display_ui(FeatureUiAction("open", root.rows[2]))
inputs.release(2000, y, session.system_snapshot())
self.assertFalse(read_preferences(self.params, large=True).enabled)
self.assertTrue(session.system_snapshot().rows[1].available) # Invalid saved choices remain repairable.
def test_system_power_touches_revalidate_engagement_and_offroad(self):
from openpilot.starpilot.ui import runtime_app
session = self.large_session()
session.system_power_action = Mock()
ui = NS(engaged=False, is_offroad=lambda: self.parked)
with patch.object(runtime_app, "ui_state", ui):
state = session.system_snapshot()
row = state.rows[1]
inputs = FeatureInput(session._display_ui)
for index, rect in enumerate(feature_action_rects(row, feature_row_top(state) + FEATURE_ROW_HEIGHT)):
x, y, width, height = rect
inputs.press(x + width / 2, y + height / 2, state)
session.system_power_action.assert_not_called()
inputs.release(x + width / 2, y + height / 2, state)
session.system_power_action.assert_called_once_with(index)
session.system_power_action.reset_mock()
for index in (-1, 2):
session._display_ui(FeatureUiAction("action", row, index))
ui.engaged = True
session._display_ui(FeatureUiAction("action", row, 0))
ui.engaged = False
self.parked = False
session._display_ui(FeatureUiAction("action", row, 1))
session.system_power_action.assert_not_called()
self.assertEqual(session.system_snapshot().rows[1].actions, (("Reboot", True), ("Power Off", False)))
def touch(self, session, state, bounds):
from openpilot.starpilot.ui import runtime_app
state = replace(state, sidebar_expanded=session.sidebar_expanded)
snapshot = ShellSnapshot(ShellMode.SETTINGS, Mock(), SettingsState(sidebar_expanded=session.sidebar_expanded), Mock(),
selected=Destination.SYSTEM, display=state)
session.snapshot = Mock(return_value=snapshot)
x, y, width, height = bounds
with patch.object(runtime_app, "ui_state", NS(started=False, started_frame=0)):
session.press(ShellMode.SETTINGS, x + width / 2, y + height / 2)
session.move(ShellMode.SETTINGS, x + width / 2, y + height / 2)
return session.release(ShellMode.SETTINGS, x + width / 2, y + height / 2)
def test_system_presets_touch_save_confirm_and_done_from_overview_pages(self):
from openpilot.starpilot.ui.runtime_app import SYSTEM_PRESETS
self.params.put_bool(MASTER, True, block=True)
steps = {"ScreenBrightness": ("25", "26"), "ScreenBrightnessOnroad": ("25", "26"),
"ScreenTimeout": ("15", "20"), "ScreenTimeoutOnroad": ("15", "20"),
HOURS: ("6 h", "7 h"), VOLTS: ("12.0 V", "12.1 V")}
self.assertEqual(set(SYSTEM_PRESETS), set(steps))
def fake_dialog(_question, _button, callback):
return NS(callback=callback)
with patch("openpilot.starpilot.ui.runtime_app.gui_app.push_widget") as pushed, \
patch("openpilot.system.ui.widgets.confirm_dialog.ConfirmDialog", side_effect=fake_dialog), \
patch("openpilot.starpilot.ui.runtime_app.native_device.invalidate_display_preferences"):
for expanded in (True, False):
for key, presets in SYSTEM_PRESETS.items():
session = self.large_session(expanded)
if key not in (HOURS, VOLTS):
session._display_ui(FeatureUiAction("open", session.system_snapshot().rows[2]))
overview = session.system_snapshot()
index = next(i for i, row in enumerate(overview.rows) if row.key == key)
session.display_scroll = index // FEATURE_VISIBLE_ROWS * FEATURE_VISIBLE_ROWS
overview = replace(overview, scroll=session.display_scroll)
self.assertFalse(any(row.key.startswith("display:auto:") for row in overview.rows))
y = feature_row_top(overview) + (index - overview.scroll) * FEATURE_ROW_HEIGHT
self.assertTrue(self.touch(session, overview, (700, y, 200, FEATURE_ROW_HEIGHT)))
self.assertEqual(session.display_edit_key, key)
for value, _label in presets:
editor = replace(session.system_snapshot(), sidebar_expanded=expanded)
row = editor.rows[0]
button = next(button for button in value_buttons(editor, row, feature_row_top(editor)) if button[0] == value)
if not button[2]:
continue
pushed.reset_mock()
self.assertTrue(self.touch(session, editor, button[1]))
if key in (HOURS, VOLTS):
self.assertEqual(session.system_snapshot().rows[0].source, row.source)
dialog = pushed.call_args.args[0]
dialog.callback(DialogResult.CANCEL)
self.assertEqual(session.system_snapshot().rows[0].source, row.source)
pushed.reset_mock()
self.assertTrue(self.touch(session, editor, button[1]))
pushed.call_args.args[0].callback(DialogResult.CONFIRM)
else:
pushed.assert_not_called()
fresh = session.system_snapshot()
self.assertEqual(fresh.rows[0].value, value)
self.assertFalse(next(button for button in value_buttons(fresh, fresh.rows[0]) if button[0] == value)[2])
if value == steps[key][0]:
fresh = replace(fresh, sidebar_expanded=expanded)
plus = value_buttons(fresh, fresh.rows[0], feature_row_top(fresh))[-1]
self.assertTrue(self.touch(session, fresh, plus[1]))
if key in (HOURS, VOLTS):
pushed.call_args.args[0].callback(DialogResult.CONFIRM)
self.assertEqual(session.system_snapshot().rows[0].value, steps[key][1])
editor = replace(session.system_snapshot(), sidebar_expanded=expanded)
before = editor.rows[0].source
self.assertTrue(self.touch(session, editor, value_done_rect(editor)))
self.assertIsNone(session.display_edit_key)
self.assertEqual(session.selected, Destination.SYSTEM)
self.assertEqual(session.display_scroll, overview.scroll)
self.assertEqual(next(row for row in session.system_snapshot().rows if row.key == key).source, before)
session._unavailable.assert_not_called()
def test_system_editor_stale_dependencies_repairs_and_compact_snapshot(self):
from openpilot.starpilot.ui.feature_settings_state import FeatureUiAction
self.params.put_bool(MASTER, True, block=True)
session = self.large_session()
session.display_edit_key = "ScreenTimeout"
editor = session.system_snapshot()
button_index = next(i for i, button in enumerate(value_buttons(editor, editor.rows[0])) if button[0] == "60")
self.params.put_bool(MASTER, False, block=True)
session._display_ui(FeatureUiAction("action", editor.rows[0], button_index))
self.assertIsNone(read_choice(self.params, "ScreenTimeout", large=True).raw)
disabled = session.system_snapshot()
self.assertTrue(all(not enabled for _, _, enabled in value_buttons(disabled, disabled.rows[0])))
self.assertTrue(self.touch(session, disabled, value_done_rect(disabled)))
Path(self.params.get_param_path("ScreenTimeout")).write_bytes(b"bad")
session.display_edit_key = "ScreenTimeout"
editor = session.system_snapshot()
self.assertEqual(value_buttons(editor, editor.rows[0]), ())
repair = (FEATURE_CONTROL_LEFT, feature_row_top(editor) + 36, FEATURE_CONTROL_RIGHT - FEATURE_CONTROL_LEFT, 82)
with patch("openpilot.starpilot.ui.runtime_app.native_device.invalidate_display_preferences"):
self.assertTrue(self.touch(session, editor, repair))
self.assertEqual(read_choice(self.params, "ScreenTimeout", large=True).value, 30)
session.profile = Profile.COMPACT
self.assertEqual(session.display_snapshot(), self.owner.snapshot(Profile.COMPACT))
self.assertTrue(all(not row.presets for row in session.system_snapshot().rows))
def test_system_editor_bounds_and_changed_editor_cancel_touch(self):
from openpilot.starpilot.ui import runtime_app
self.params.put_bool(MASTER, True, block=True)
self.params.put("ScreenBrightness", 100, block=True)
session = self.large_session()
session.display_edit_key = "ScreenBrightness"
editor = session.system_snapshot()
self.assertFalse(value_buttons(editor, editor.rows[0])[-1][2])
self.params.put("ScreenBrightness", 5, block=True)
editor = session.system_snapshot()
self.assertFalse(value_buttons(editor, editor.rows[0])[0][2])
session.display_edit_key = HOURS
editor = session.system_snapshot()
self.assertFalse(value_buttons(editor, editor.rows[0])[-1][2])
session.display_edit_key = VOLTS
editor = session.system_snapshot()
self.assertFalse(value_buttons(editor, editor.rows[0])[0][2])
session.display_edit_key = "ScreenBrightness"
editor = session.system_snapshot()
snapshot = ShellSnapshot(ShellMode.SETTINGS, Mock(), SettingsState(), Mock(), selected=Destination.SYSTEM, display=editor)
session.snapshot = Mock(return_value=snapshot)
with patch.object(runtime_app, "ui_state", NS(started=False, started_frame=0)):
session.press(ShellMode.SETTINGS, 1200, 950)
session.display_edit_key = "ScreenTimeout"
self.assertFalse(session.release(ShellMode.SETTINGS, 1200, 950))
self.assertEqual(session.display_edit_key, "ScreenTimeout")
def test_absent_defaults_and_registered_keys_do_not_write(self):
self.assertEqual(self.row(MASTER).value, "Off")
self.assertEqual(self.row("ScreenBrightness").value, "Auto")
self.assertEqual((self.row("ScreenBrightness").step, self.row("ScreenBrightness").maximum,
self.row("ScreenBrightness").unit), (1.0, 100.0, "%"))
self.assertEqual((self.row("ScreenTimeout").value, self.row("ScreenTimeout").unit), ("30", "s"))
self.assertEqual(self.row("ScreenTimeoutOnroad").value, "10")
self.assertEqual(self.row("ScreenTimeoutOnroad", Profile.COMPACT).value, "5")
self.assertEqual(list(Path(self.params.get_param_path(MASTER)).parent.iterdir()), [])
for key in (MASTER, "ScreenBrightness", "ScreenBrightnessOnroad", "ScreenTimeout", "ScreenTimeoutOnroad", SCREEN_OFF):
self.assertIsNone(read_choice(self.params, key, large=True).raw)
def test_screen_off_is_independent_default_off_persistent_and_source_guarded(self):
for profile in Profile:
row = self.row(SCREEN_OFF, profile)
self.assertEqual((row.value, row.default_value, row.choices, row.step, row.unit), ("Off", "Off", ("Off", "On"), 0, ""))
self.assertTrue(row.available)
self.assertIn("CPU/GPU", row.reason)
self.assertIn("Tap once to wake", row.reason)
request = self.request(SCREEN_OFF)
self.assertTrue(self.owner.apply(request))
self.assertFalse(self.owner.apply(request)) # The displayed source is stale.
saved = read_preferences(self.params, large=True)
self.assertFalse(saved.enabled)
self.assertTrue(saved.turn_off_screen)
self.assertTrue(Params(self.params_root).get_bool(SCREEN_OFF))
self.assertTrue(self.owner.apply(self.request(MASTER)))
self.assertTrue(self.owner.apply(self.request(MASTER)))
self.assertTrue(read_preferences(self.params, large=True).turn_off_screen)
request = self.request(SCREEN_OFF)
self.parked = False
self.assertFalse(self.owner.apply(request))
self.parked = True
self.assertTrue(self.owner.apply(request))
self.assertFalse(read_preferences(self.params, large=True).turn_off_screen)
def test_invalid_screen_off_fails_off_and_can_be_repaired(self):
Path(self.params.get_param_path(SCREEN_OFF)).write_bytes(b"2")
self.assertFalse(read_preferences(self.params, large=True).turn_off_screen)
self.assertEqual(self.row(SCREEN_OFF).repair_value, "Off")
self.assertTrue(self.owner.apply(self.request(SCREEN_OFF)))
self.assertEqual(self.row(SCREEN_OFF).value, "Off")
def test_screen_off_toggle_in_display_submenu_refreshes_device_preferences(self):
session = self.large_session()
session._display_ui(FeatureUiAction("open", session.system_snapshot().rows[2]))
state = replace(session.system_snapshot(), scroll=5)
inputs = FeatureInput(session._display_ui)
with patch("openpilot.starpilot.ui.runtime_app.native_device.invalidate_display_preferences") as refresh:
inputs.press(2000, feature_row_top(state) + FEATURE_ROW_HEIGHT / 2, state)
self.assertFalse(read_preferences(self.params, large=True).turn_off_screen)
inputs.release(2000, feature_row_top(state) + FEATURE_ROW_HEIGHT / 2, state)
self.assertTrue(read_preferences(self.params, large=True).turn_off_screen)
refresh.assert_called_once()
def test_master_enable_requires_valid_values_but_off_remains_available(self):
path = Path(self.params.get_param_path("ScreenBrightnessOnroad"))
path.write_bytes(b"0")
self.assertEqual(self.row("ScreenBrightnessOnroad").value, "Saved Off (unsupported)")
self.assertEqual(self.row("ScreenBrightnessOnroad").repair_value, "Auto")
self.assertFalse(self.owner.apply(self.request(MASTER)))
self.assertEqual(path.read_bytes(), b"0")
self.assertTrue(self.owner.apply(self.request("ScreenBrightnessOnroad")))
self.assertTrue(self.owner.apply(self.request(MASTER)))
self.assertTrue(read_preferences(self.params, large=True).enabled)
path.write_bytes(b"broken")
self.assertTrue(self.owner.apply(self.request(MASTER)))
self.assertFalse(read_preferences(self.params, large=True).enabled)
self.assertEqual(path.read_bytes(), b"broken")
def test_all_display_children_disable_without_losing_saved_values(self):
session = self.large_session()
values = {"ScreenBrightness": 50, "ScreenBrightnessOnroad": 75, "ScreenTimeout": 30, "ScreenTimeoutOnroad": 10}
self.params.put_bool(MASTER, True, block=True)
for key, value in values.items():
self.params.put(key, value, block=True)
requests = {key: self.request(key) for key in values}
self.assertTrue(self.owner.apply(self.request(MASTER)))
session._display_ui(FeatureUiAction("open", session.system_snapshot().rows[2]))
disabled = session.system_snapshot()
self.assertTrue(next(row for row in disabled.rows if row.key == MASTER).available)
for index, row in enumerate(disabled.rows):
if row.key in values:
self.assertFalse(row.available)
self.assertIn("custom display is off", row.reason)
page = replace(disabled, scroll=index // FEATURE_VISIBLE_ROWS * FEATURE_VISIBLE_ROWS)
target = FeatureInput.target(800, feature_row_top(page) + (index % FEATURE_VISIBLE_ROWS + .5) * FEATURE_ROW_HEIGHT, page)
self.assertEqual(target.kind, "details")
self.assertFalse(self.owner.apply(requests[row.key]))
self.assertEqual(read_choice(self.params, row.key, large=True).value, values[row.key])
self.assertTrue(self.owner.apply(self.request(MASTER)))
self.assertTrue(all(row.available for row in session.system_snapshot().rows if row.key in values))
def test_source_dependency_and_parked_guards(self):
self.assertFalse(self.row("ScreenTimeout").available)
self.params.put_bool(MASTER, True, block=True)
request = row_change(self.row("ScreenTimeout"))
assert request is not None
self.params.put_bool(MASTER, False, block=True)
self.assertFalse(self.owner.apply(request))
self.params.put_bool(MASTER, True, block=True)
request = row_change(self.row("ScreenTimeout"))
assert request is not None
self.parked = False
self.assertFalse(self.owner.apply(request))
self.parked = True
checks = 0
def parked_once():
nonlocal checks
checks += 1
return checks == 1
self.assertFalse(DisplayOwner(self.params, parked_once).apply(request))
self.assertIsNone(read_choice(self.params, "ScreenTimeout", large=True).raw)
self.params.put_bool(MASTER, False, block=True)
self.assertFalse(self.owner.apply(request))
def test_numeric_metadata_and_master_off_cannot_be_bypassed(self):
row = self.row("ScreenTimeout")
self.assertEqual((row.value, row.step, row.minimum, row.maximum, row.unit, row.choices), ("30", 5.0, 5.0, 60.0, "s", ()))
self.assertFalse(self.owner.apply(FeatureSettingsRequest(row.key, row.source, "35", dependencies=row.dependencies)))
self.params.put_bool(MASTER, True, block=True)
self.params.put("ScreenBrightness", 50, block=True)
row = self.row("ScreenBrightness")
self.assertEqual((row.value, row.step, row.minimum, row.maximum, row.unit, row.choices), ("50", 1.0, 5.0, 100.0, "%", ("Auto",)))
self.assertTrue(self.owner.apply(self.request("ScreenBrightness")))
self.assertEqual(read_choice(self.params, "ScreenBrightness", large=True).value, 51)
self.assertTrue(self.owner.apply(self.request("display:auto:ScreenBrightness")))
self.assertEqual(self.row("ScreenBrightness").value, "Auto")
def test_final_source_and_master_freshness_prevent_write(self):
self.params.put_bool(MASTER, True, block=True)
request = self.request("ScreenTimeout")
original_path = self.params.get_param_path
key_reads = 0
def unreadable_on_final(key):
nonlocal key_reads
if key == "ScreenTimeout":
key_reads += 1
if key_reads == 2:
raise OSError("file became unreadable")
return original_path(key)
with patch.object(self.params, "get_param_path", side_effect=unreadable_on_final):
self.assertFalse(self.owner.apply(request))
self.assertIsNone(read_choice(self.params, "ScreenTimeout", large=True).raw)
master_reads = 0
def changed_master_on_final(key):
nonlocal master_reads
if key == MASTER:
master_reads += 1
if master_reads == 2:
Path(original_path(MASTER)).write_bytes(b"0")
return original_path(key)
with patch.object(self.params, "get_param_path", side_effect=changed_master_on_final):
self.assertFalse(self.owner.apply(request))
self.assertIsNone(read_choice(self.params, "ScreenTimeout", large=True).raw)
def test_corrupt_oversized_and_nonregular_are_preserved(self):
path = Path(self.params.get_param_path("ScreenBrightness"))
path.write_bytes(b"\xff")
self.assertEqual(self.row("ScreenBrightness").value, "Invalid saved choice")
self.assertTrue(self.owner.apply(self.request("ScreenBrightness")))
self.assertEqual(path.read_bytes(), b"101")
oversized = b"1" * 100_000
path.write_bytes(oversized)
row = self.row("ScreenBrightness")
self.assertFalse(row.available)
self.assertFalse(self.owner.apply(FeatureSettingsRequest(row.key, row.source, "Auto", dependencies=row.dependencies)))
self.assertEqual(path.read_bytes(), oversized)
path.unlink()
target = path.parent / "target"
target.write_bytes(b"101")
path.symlink_to(target)
self.assertFalse(self.row("ScreenBrightness").available)
path.unlink()
os.mkfifo(path)
self.assertFalse(self.row("ScreenBrightness").available)
def test_native_press_and_compact_refresh(self):
changed = []
input_owner = FeatureInput(changed.append)
state = self.owner.snapshot(Profile.LARGE)
visible_row = next(index for index, row in enumerate(state.rows) if row.key == MASTER) - state.scroll
self.assertTrue(0 <= visible_row < FEATURE_VISIBLE_ROWS)
x = (FEATURE_CONTROL_LEFT + FEATURE_CONTROL_RIGHT) / 2
y = feature_row_top(state) + (visible_row + .5) * FEATURE_ROW_HEIGHT
input_owner.press(x, y, state)
input_owner.release(x, y, state)
self.assertEqual(changed[0].row.key, MASTER)
input_owner.press(x, y, state)
input_owner.cancel()
input_owner.release(x, y, state)
self.assertEqual(len(changed), 1)
class Button:
def __init__(self, label, value):
self.label, self.value, self.click = label, value, None
def set_click_callback(self, callback):
self.click = callback
def set_enabled(self, enabled):
self.enabled = enabled
class Scroller:
def __init__(self):
self._scroller = self
self.items = []
def add_widgets(self, cards):
self.items.extend(cards)
class Session:
def display_snapshot(inner):
return self.owner.snapshot(Profile.COMPACT)
def display_request(inner, request):
return self.owner.apply(request)
shown = []
with patch.object(display_compact, "BigButton", Button), patch.object(display_compact, "GreyBigButton", Button), \
patch.object(display_compact, "NavScroller", Scroller), patch.object(display_compact.gui_app, "push_widget", shown.append):
display_compact.DisplayCompact(Session()).entry_button().click()
page = shown[0]
self.assertEqual(page.items[0].label, "display")
screen_off = next(item for item in page.items if item.label == "turn off comma screen")
self.assertIn("cpu/gpu", screen_off.value)
screen_off.click()
self.assertTrue(read_preferences(self.params, large=False).turn_off_screen)
self.assertFalse(read_preferences(self.params, large=False).enabled)
page.items[1].click()
self.assertEqual(page.items[1].value, "on")
self.assertTrue(read_preferences(self.params, large=False).enabled)
for key, label, maximum, step in (("ScreenTimeout", "parked screen timeout", 60, 5),
("ScreenTimeoutOnroad", "driving interaction timeout", 60, 5),
("ScreenBrightness", "parked brightness", 100, 1),
("ScreenBrightnessOnroad", "driving brightness", 100, 1)):
with self.subTest(key=key):
self.params.put(key, maximum, block=True)
display_compact.DisplayCompact(Session())._populate(page)
minus = next(item for item in page.items if item.label == label + " -")
plus = next(item for item in page.items if item.label == label + " +")
self.assertTrue(minus.enabled)
self.assertFalse(plus.enabled)
minus.click()
self.assertEqual(self.params.get(key), maximum - step)
next(item for item in page.items if item.label == label + " +").click()
self.assertEqual(self.params.get(key), maximum)
self.params.put(key, 5, block=True)
display_compact.DisplayCompact(Session())._populate(page)
self.assertFalse(next(item for item in page.items if item.label == label + " -").enabled)
next(item for item in page.items if item.label == "use auto driving brightness set auto").click()
self.assertEqual([item.label for item in page.items if item.label.startswith("driving brightness")], ["driving brightness"])
next(item for item in page.items if item.label == "driving brightness").click()
self.assertEqual(self.params.get("ScreenBrightnessOnroad"), 5)
Path(self.params.get_param_path("ScreenTimeout")).write_bytes(b"invalid")
display_compact.DisplayCompact(Session())._populate(page)
repair = [item for item in page.items if item.label.startswith("parked screen timeout")]
self.assertEqual([item.label for item in repair], ["parked screen timeout set 30 s"])
repair[0].click()
self.assertEqual(self.params.get("ScreenTimeout"), 30)
def test_large_system_tile_routes_to_display_and_back(self):
from openpilot.starpilot.ui.runtime_app import StarShellSession
from openpilot.starpilot.ui.runtime_snapshot import RuntimeSnapshotAdapter
from openpilot.starpilot.ui.feature_settings_state import FeatureUiAction
ui = ui_fake()
ui.params = self.params
ui.started = False
ui.sm.messages["deviceState"].started = False
ui.sm.messages["pandaStates"][0].ignitionLine = False
adapter = RuntimeSnapshotAdapter(ui)
snapshot = adapter.build(ShellMode.SETTINGS, now_ns=NOW)
self.assertTrue(snapshot.settings.destination(Destination.SYSTEM).available)
emitted = []
settings_input = SettingsInput(Profile.LARGE, emitted.append)
x, y, width, height = tile_rects(snapshot.settings)[3]
settings_input.press(x + width / 2, y + height / 2, snapshot.settings)
settings_input.release(x + width / 2, y + height / 2, snapshot.settings)
self.assertEqual(emitted[0].destination.destination, Destination.SYSTEM)
session = StarShellSession.__new__(StarShellSession)
self.enterContext(patch.object(session, 'drive_state', NS(snapshot=lambda: {"mode": "auto", "revision": None, "available": False,
"effective": None, "overrideAllowed": False}), create=True))
session._mode = ShellMode.SETTINGS
session.selected = Destination.SYSTEM
session.display_owner = self.owner
from openpilot.starpilot.ui.power_owner import PowerOwner
session.power_owner = PowerOwner(self.params, lambda: self.parked)
session.display_scroll = 0
session._snapshot_cache = None
session.input = ShellInput(Profile.LARGE, lambda _: None)
session.profile = Profile.LARGE
session._display_ui(FeatureUiAction("back"))
self.assertEqual(session.selected, Destination.STAR)