mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 05:23:41 +08:00
524 lines
27 KiB
Python
524 lines
27 KiB
Python
"""SLC native offset actions and both unit paths against real temporary Params."""
|
||
|
||
from pathlib import Path
|
||
from dataclasses import replace
|
||
from types import SimpleNamespace as NS
|
||
from typing import Any, cast
|
||
import tempfile
|
||
import unittest
|
||
from unittest.mock import patch
|
||
|
||
from openpilot.common.params import Params
|
||
from openpilot.starpilot.speed_limits import offset_document as od
|
||
from openpilot.starpilot.speed_limits.runtime_settings import read_params
|
||
from openpilot.starpilot.ui.feature_settings_owner import FeatureSettingsOwner
|
||
from openpilot.starpilot.ui.feature_settings_state import FeatureSettingsRequest, row_change
|
||
from openpilot.starpilot.ui.slc_offset_feature import DOCUMENT_KEY, SlcOffsetOwner, native_parked
|
||
|
||
|
||
class SlcOffsetFeatureTests(unittest.TestCase):
|
||
def setUp(self):
|
||
temporary = tempfile.TemporaryDirectory()
|
||
self.addCleanup(temporary.cleanup)
|
||
self.params = Params(temporary.name)
|
||
self.parked = True
|
||
self.cp: NS | None = NS(carFingerprint="TOYOTA_COROLLA_TSS2", openpilotLongitudinalControl=True, pcmCruise=False,
|
||
notCar=False, dashcamOnly=False, passive=False)
|
||
self.owner = FeatureSettingsOwner(self.params, lambda _group: self.parked,
|
||
vehicle_fingerprint=lambda: self.cp.carFingerprint if self.cp else None,
|
||
vehicle_params=lambda: self.cp)
|
||
self.units = SlcOffsetOwner(self.params, lambda: self.parked, lambda: self.cp)
|
||
|
||
def raw(self, key):
|
||
path = Path(self.params.get_param_path(key))
|
||
return path.read_bytes() if path.exists() else None
|
||
|
||
def row(self, key):
|
||
state = self.owner.snapshot("slc", parked=self.parked, system_long=True, lateral_context=True, metric=False)
|
||
return next(item for item in state.rows if item.key == key)
|
||
|
||
@staticmethod
|
||
def confirm(row):
|
||
return FeatureSettingsRequest(row.key, row.source, "confirm", confirmation=True,
|
||
related_source=row.related_source, vehicle_fingerprint=row.vehicle_fingerprint,
|
||
capability=row.capability, dependencies=row.dependencies)
|
||
|
||
def test_native_offroad_uses_retained_fresh_authority_adapter(self):
|
||
ui = NS()
|
||
with patch("openpilot.starpilot.ui.runtime_snapshot.RuntimeSnapshotAdapter") as factory:
|
||
factory.return_value.confirmed_offroad.side_effect = [True, False]
|
||
self.assertTrue(native_parked(ui))
|
||
self.assertFalse(native_parked(ui))
|
||
factory.assert_called_once_with(ui)
|
||
|
||
def test_pcm_cruise_longitudinal_can_adopt_and_enable_slc(self):
|
||
assert self.cp is not None
|
||
self.cp.carFingerprint = "TOYOTA_HIGHLANDER_TSS2"
|
||
self.cp.pcmCruise = True
|
||
state = self.owner.snapshot("slc", parked=True, system_long=False, lateral_context=True, metric=False)
|
||
adopt = next(row for row in state.rows if row.key == "slc_adopt")
|
||
self.assertTrue(adopt.available)
|
||
self.assertTrue(self.owner.apply(self.confirm(adopt)))
|
||
row = self.row("SpeedLimitController")
|
||
self.assertTrue(row.available)
|
||
request = row_change(row)
|
||
assert request is not None
|
||
self.assertTrue(self.owner.apply(request))
|
||
self.cp.openpilotLongitudinalControl = False
|
||
self.assertFalse(self.row("SpeedLimitController").available)
|
||
|
||
def test_valid_legacy_enable_preserves_sources_and_invalid_offsets_deny(self):
|
||
for legacy in (None, b"5"):
|
||
with self.subTest(legacy=legacy):
|
||
self.params.put_bool("SpeedLimitController", False, block=True)
|
||
if legacy is not None:
|
||
Path(self.params.get_param_path("Offset1")).write_bytes(legacy)
|
||
original = tuple(self.raw(key) for key in ("IsMetric", *(f"Offset{i}" for i in range(1, 8))))
|
||
row = self.row("SpeedLimitController")
|
||
self.assertTrue(row.available)
|
||
request = row_change(row)
|
||
assert request is not None
|
||
self.assertTrue(self.owner.apply(request))
|
||
self.assertTrue(read_params(self.params).enabled)
|
||
self.assertIsNone(self.raw(DOCUMENT_KEY))
|
||
self.assertEqual(tuple(self.raw(key) for key in ("IsMetric", *(f"Offset{i}" for i in range(1, 8)))), original)
|
||
for key, raw in (("Offset1", b"bad"), (DOCUMENT_KEY, b"{bad"),
|
||
(DOCUMENT_KEY, od.encode(od.NEEDS_REVIEW)), ("IsMetric", b"bad")):
|
||
with self.subTest(key=key, raw=raw):
|
||
for saved in ("Offset1", DOCUMENT_KEY, "IsMetric"):
|
||
path = Path(self.params.get_param_path(saved))
|
||
path.unlink(missing_ok=True)
|
||
self.params.put_bool("SpeedLimitController", False, block=True)
|
||
Path(self.params.get_param_path(key)).write_bytes(raw)
|
||
row = self.row("SpeedLimitController")
|
||
self.assertFalse(row.available)
|
||
self.assertIn("Repair invalid", row.reason)
|
||
self.assertFalse(self.owner.slc_offsets.ready_to_enable())
|
||
|
||
def test_unavailable_control_explains_vehicle_and_context_gate(self):
|
||
assert self.cp is not None
|
||
cp = self.cp
|
||
self.cp = None
|
||
self.assertIn("Select or connect", self.row("SpeedLimitController").reason)
|
||
self.cp = cp
|
||
cp.openpilotLongitudinalControl = False
|
||
self.assertIn("StarPilot speed control is unavailable", self.row("SpeedLimitController").reason)
|
||
cp.openpilotLongitudinalControl = True
|
||
self.parked = False
|
||
self.assertIn("settings context is unavailable", self.row("SpeedLimitController").reason)
|
||
|
||
def test_explicit_adopt_edit_and_global_units_preserve_exact_si(self):
|
||
self.params.put_bool("IsMetric", False, block=True)
|
||
self.params.put("Offset1", 5.0, block=True)
|
||
self.params.put("Offset7", -2.0, block=True)
|
||
old = (self.raw("Offset1"), self.raw("Offset7"))
|
||
before = read_params(self.params).offsets
|
||
self.assertTrue(self.row("SpeedLimitController").available)
|
||
adopt = self.row("slc_adopt")
|
||
self.assertFalse(self.owner.apply(FeatureSettingsRequest(adopt.key, adopt.source, "confirm",
|
||
vehicle_fingerprint=adopt.vehicle_fingerprint,
|
||
capability=adopt.capability, dependencies=adopt.dependencies)))
|
||
self.assertTrue(self.owner.apply(self.confirm(adopt)))
|
||
self.assertTrue(self.row("SpeedLimitController").available)
|
||
self.assertEqual(read_params(self.params).offsets, before)
|
||
self.assertEqual((self.raw("Offset1"), self.raw("Offset7")), old)
|
||
|
||
first = self.row("Offset1")
|
||
self.assertTrue(first.available)
|
||
self.assertEqual(row_change(first).direction, 1)
|
||
first_edit = row_change(first)
|
||
assert first_edit is not None
|
||
self.assertTrue(self.owner.apply(first_edit))
|
||
document = od.decode(self.raw(DOCUMENT_KEY))
|
||
self.assertAlmostEqual(document.offsets_mps[0], 6 * od.MPH_TO_MPS)
|
||
self.assertEqual(document.schedule().bands[-1].offset_mps, 0.0)
|
||
self.assertTrue(self.units.change_units(True, b"0"))
|
||
self.assertEqual(od.decode(self.raw(DOCUMENT_KEY)), document)
|
||
metric_row = self.row("Offset1")
|
||
self.assertIn("km/h", metric_row.label)
|
||
metric_edit = row_change(metric_row)
|
||
assert metric_edit is not None
|
||
self.assertTrue(self.owner.apply(metric_edit))
|
||
adjusted = od.decode(self.raw(DOCUMENT_KEY))
|
||
self.assertAlmostEqual(adjusted.offsets_mps[0] - document.offsets_mps[0], od.KPH_TO_MPS)
|
||
self.assertTrue(self.units.change_units(False, b"1"))
|
||
self.assertEqual(od.decode(self.raw(DOCUMENT_KEY)), adjusted)
|
||
self.assertEqual((self.raw("Offset1"), self.raw("Offset7")), old)
|
||
|
||
def test_range_labels_follow_saved_si_bounds_without_rewriting_them(self):
|
||
document = od.OffsetDocument(od.IMPERIAL_BOUNDS, (0.0,) * 7)
|
||
self.params.put(DOCUMENT_KEY, od.to_value(document), block=True)
|
||
original = self.raw(DOCUMENT_KEY)
|
||
rows = [self.row(f"Offset{index}").label for index in range(1, 8)]
|
||
self.assertEqual(rows, [f"{low}–<{high} mph offset" for low, high in
|
||
(("0", "25"), ("25", "34"), ("34", "44"), ("44", "54"),
|
||
("54", "64"), ("64", "74"), ("74", "99"))])
|
||
state = self.owner.snapshot("slc", parked=True, system_long=True, lateral_context=True, metric=False)
|
||
self.assertTrue(any(row.label == "99 mph and above" and row.value == "Zero offset" for row in state.rows))
|
||
self.assertEqual(self.raw(DOCUMENT_KEY), original)
|
||
self.assertTrue(self.units.change_units(True, None))
|
||
self.assertEqual(od.decode(self.raw(DOCUMENT_KEY)), document)
|
||
self.assertEqual(self.row("Offset4").label, "71–<87 km/h offset")
|
||
|
||
def test_editor_rejects_offsets_that_cannot_produce_a_positive_target_in_the_band(self):
|
||
for metric, offset in ((False, -30), (True, -45)):
|
||
with self.subTest(metric=metric):
|
||
self.params.put_bool("IsMetric", metric, block=True)
|
||
document = od.OffsetDocument(od.IMPERIAL_BOUNDS, (0.0,) * 7)
|
||
self.params.put(DOCUMENT_KEY, od.to_value(document), block=True)
|
||
original = self.raw(DOCUMENT_KEY)
|
||
row = self.row("Offset1")
|
||
request = row_change(row)
|
||
assert request is not None
|
||
self.assertGreater(row.minimum, offset)
|
||
self.assertFalse(self.owner.apply(replace(request, value=str(offset), direction=0)))
|
||
self.assertEqual(self.raw(DOCUMENT_KEY), original)
|
||
# Useful reductions remain supported, including in the first band.
|
||
self.assertTrue(self.owner.apply(replace(request, value="-1", direction=0)))
|
||
|
||
def test_stepper_can_repair_an_unsafe_saved_offset_and_clamps_at_the_band_minimum(self):
|
||
document = od.OffsetDocument(od.IMPERIAL_BOUNDS, (-30 * od.MPH_TO_MPS, 0, 0, 0, 0, 0, 0))
|
||
self.params.put(DOCUMENT_KEY, od.to_value(document), block=True)
|
||
row = self.row('Offset1')
|
||
request = row_change(row)
|
||
assert request is not None
|
||
self.assertTrue(self.owner.apply(request))
|
||
self.assertEqual(od.decode(self.raw(DOCUMENT_KEY)).offsets_mps[0], row.minimum * od.MPH_TO_MPS)
|
||
# Unit conversion can leave a fractional value less than one step above the minimum.
|
||
document = od.OffsetDocument(od.IMPERIAL_BOUNDS, ((row.minimum + .5) * od.MPH_TO_MPS, 0, 0, 0, 0, 0, 0))
|
||
self.params.put(DOCUMENT_KEY, od.to_value(document), block=True)
|
||
request = row_change(self.row('Offset1'), -1)
|
||
assert request is not None
|
||
self.assertTrue(self.owner.apply(request))
|
||
self.assertEqual(od.decode(self.raw(DOCUMENT_KEY)).offsets_mps[0], row.minimum * od.MPH_TO_MPS)
|
||
|
||
def test_onroad_adopted_offsets_edit_without_repair_authority(self):
|
||
self.params.put_bool("IsMetric", False, block=True)
|
||
document = od.OffsetDocument(od.IMPERIAL_BOUNDS, (0.0,) * 7)
|
||
self.params.put(DOCUMENT_KEY, od.to_value(document), block=True)
|
||
owner = FeatureSettingsOwner(self.params, lambda group: group != "parked_preferences",
|
||
vehicle_fingerprint=lambda: self.cp.carFingerprint if self.cp else None,
|
||
vehicle_params=lambda: self.cp)
|
||
state = owner.snapshot("slc", parked=False, system_long=True, lateral_context=True, metric=False,
|
||
configure_while_driving=True)
|
||
offset = next(row for row in state.rows if row.key == "Offset2")
|
||
reset = next(row for row in state.rows if row.key == "slc_reset")
|
||
self.assertTrue(offset.available)
|
||
self.assertFalse(reset.available)
|
||
request = row_change(offset)
|
||
assert request is not None
|
||
self.assertTrue(owner.apply(request))
|
||
self.assertFalse(owner.apply(self.confirm(reset)))
|
||
self.assertEqual(od.decode(self.raw(DOCUMENT_KEY)).offsets_mps[1], od.MPH_TO_MPS)
|
||
self.params.put_bool("IsMetric", True, block=True)
|
||
self.assertFalse(owner.apply(request))
|
||
|
||
def test_global_units_do_not_require_openpilot_longitudinal_or_a_fingerprint(self):
|
||
for cp in (None, NS(carFingerprint="STOCK_CRUISE", openpilotLongitudinalControl=False,
|
||
notCar=False, dashcamOnly=False, passive=False)):
|
||
with self.subTest(vehicle=cp):
|
||
self.cp = cp
|
||
self.params.remove(DOCUMENT_KEY)
|
||
self.params.put_bool("IsMetric", False, block=True)
|
||
self.params.put("Offset1", 5.0, block=True)
|
||
self.assertTrue(self.units.change_units(True, b"0"))
|
||
document = od.decode(self.raw(DOCUMENT_KEY))
|
||
self.assertEqual(document, od.adopt_legacy(False, (5.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)))
|
||
self.assertEqual(self.raw("IsMetric"), b"1")
|
||
self.assertEqual(self.raw("Offset1"), b"5.0")
|
||
|
||
def test_off_and_display_only_corrupt_legacy_allow_unit_change(self):
|
||
Path(self.params.get_param_path("Offset1")).write_bytes(b"nan")
|
||
Path(self.params.get_param_path("IsMetric")).write_bytes(b"bad")
|
||
self.params.put_bool("ShowSpeedLimits", True, block=True)
|
||
self.cp = None # global display setting cannot require a supported car when SLC is off
|
||
self.parked = False
|
||
self.assertTrue(self.units.change_units(True, b"bad"))
|
||
self.assertIsNone(self.raw(DOCUMENT_KEY))
|
||
self.assertEqual(self.raw("Offset1"), b"nan")
|
||
self.assertEqual(self.raw("IsMetric"), b"1")
|
||
|
||
def test_valid_legacy_unit_migration_waits_for_park_even_with_slc_off(self):
|
||
self.params.put_bool("IsMetric", False, block=True)
|
||
self.params.put("Offset1", 5.0, block=True)
|
||
self.params.put_bool("SpeedLimitController", False, block=True)
|
||
self.parked = False
|
||
self.assertFalse(self.units.change_units(True, b"0"))
|
||
self.assertIsNone(self.raw(DOCUMENT_KEY))
|
||
self.assertEqual(self.raw("IsMetric"), b"0")
|
||
|
||
def test_saved_on_corrupt_legacy_writes_marker_before_unit(self):
|
||
self.params.put_bool("SpeedLimitController", True, block=True)
|
||
Path(self.params.get_param_path("Offset3")).write_bytes(b"bad")
|
||
self.assertTrue(self.units.change_units(True, None))
|
||
self.assertIs(od.decode(self.raw(DOCUMENT_KEY)), od.NEEDS_REVIEW)
|
||
self.assertEqual(self.raw("IsMetric"), b"1")
|
||
self.assertFalse(read_params(self.params).enabled)
|
||
reset = self.row("slc_reset")
|
||
self.assertTrue(self.owner.apply(self.confirm(reset)))
|
||
self.assertEqual(od.decode(self.raw(DOCUMENT_KEY)).offsets_mps, (0.0,) * 7)
|
||
self.assertTrue(read_params(self.params).enabled)
|
||
self.assertEqual(self.raw("Offset3"), b"bad")
|
||
|
||
def test_marker_failure_veto_and_stale_source_recheck(self):
|
||
self.params.put_bool("SpeedLimitController", True, block=True)
|
||
Path(self.params.get_param_path("Offset1")).write_bytes(b"bad")
|
||
original = self.params.put
|
||
|
||
def fails_marker(key, value, block=False):
|
||
if key == DOCUMENT_KEY:
|
||
raise OSError("write failed")
|
||
return original(key, value, block=block)
|
||
|
||
with patch.object(self.params, "put", side_effect=fails_marker):
|
||
self.assertFalse(self.units.change_units(True, None))
|
||
self.assertIsNone(self.raw("IsMetric"))
|
||
self.assertIsNone(self.raw(DOCUMENT_KEY))
|
||
Path(self.params.get_param_path("Offset1")).write_bytes(b"1")
|
||
adopt = self.row("slc_adopt")
|
||
Path(self.params.get_param_path("Offset1")).write_bytes(b"2")
|
||
self.assertFalse(self.owner.apply(self.confirm(adopt)))
|
||
self.assertIsNone(self.raw(DOCUMENT_KEY))
|
||
|
||
def test_absent_corrupt_legacy_reset_recovers_without_changing_saved_switch(self):
|
||
for saved_on in (False, True):
|
||
with self.subTest(saved_on=saved_on):
|
||
if saved_on:
|
||
self.params.put_bool("SpeedLimitController", True, block=True)
|
||
Path(self.params.get_param_path("Offset1")).write_bytes(b"bad")
|
||
self.assertFalse(read_params(self.params).enabled)
|
||
reset = self.row("slc_reset")
|
||
self.assertTrue(reset.available)
|
||
self.assertIsNone(reset.source)
|
||
self.assertFalse(self.owner.apply(FeatureSettingsRequest("slc_reset", None, "confirm",
|
||
confirmation=False, related_source=reset.related_source,
|
||
capability=reset.capability, dependencies=reset.dependencies)))
|
||
self.assertTrue(self.owner.apply(self.confirm(reset)))
|
||
document = od.decode(self.raw(DOCUMENT_KEY))
|
||
self.assertEqual(document, od.adopt_legacy(False, (0.0,) * 7))
|
||
self.assertEqual(self.raw("Offset1"), b"bad")
|
||
self.assertEqual(self.raw("SpeedLimitController"), b"1" if saved_on else None)
|
||
self.assertEqual(read_params(self.params).enabled, saved_on)
|
||
Path(self.params.get_param_path(DOCUMENT_KEY)).unlink()
|
||
|
||
def test_absent_corrupt_reset_rechecks_legacy_and_waits_for_valid_units(self):
|
||
legacy = Path(self.params.get_param_path("Offset4"))
|
||
legacy.write_bytes(b"bad")
|
||
reset = self.row("slc_reset")
|
||
legacy.write_bytes(b"different bad")
|
||
self.assertFalse(self.owner.apply(self.confirm(reset)))
|
||
self.assertIsNone(self.raw(DOCUMENT_KEY))
|
||
Path(self.params.get_param_path("IsMetric")).write_bytes(b"invalid")
|
||
self.assertFalse(self.row("slc_reset").available)
|
||
self.assertFalse(self.owner.apply(self.confirm(self.row("slc_reset"))))
|
||
self.assertIsNone(self.raw(DOCUMENT_KEY))
|
||
self.assertTrue(self.units.change_units(True, b"invalid"))
|
||
self.assertTrue(self.row("slc_reset").available)
|
||
self.assertTrue(self.owner.apply(self.confirm(self.row("slc_reset"))))
|
||
self.assertEqual(od.decode(self.raw(DOCUMENT_KEY)).bounds_mps, od.METRIC_BOUNDS)
|
||
|
||
def test_saved_off_corrupt_unit_change_rechecks_legacy_before_unit_write(self):
|
||
legacy = Path(self.params.get_param_path("Offset1"))
|
||
legacy.write_bytes(b"bad")
|
||
original = self.units._same_unit_source
|
||
|
||
def mutate_at_final_guard(snap, capability):
|
||
legacy.write_bytes(b"changed")
|
||
return original(snap, capability)
|
||
|
||
with patch.object(self.units, "_same_unit_source", side_effect=mutate_at_final_guard):
|
||
self.assertFalse(self.units.change_units(True, None))
|
||
self.assertIsNone(self.raw("IsMetric"))
|
||
self.assertIsNone(self.raw(DOCUMENT_KEY))
|
||
|
||
def test_stale_unit_capability_and_parked_edit_write_nothing(self):
|
||
self.assertTrue(self.owner.apply(self.confirm(self.row("slc_adopt"))))
|
||
edit = row_change(self.row("Offset2"))
|
||
assert edit is not None
|
||
self.params.put_bool("IsMetric", True, block=True)
|
||
self.assertFalse(self.owner.apply(edit))
|
||
self.params.put_bool("IsMetric", False, block=True)
|
||
assert self.cp is not None
|
||
self.cp.carFingerprint = "TOYOTA_CAMRY"
|
||
self.assertFalse(self.owner.apply(edit))
|
||
self.cp.carFingerprint = "TOYOTA_COROLLA_TSS2"
|
||
self.parked = False
|
||
self.assertFalse(self.owner.apply(edit))
|
||
|
||
def test_valid_document_ignores_legacy_bytes_and_reset_keeps_bounds(self):
|
||
self.assertTrue(self.owner.apply(self.confirm(self.row("slc_adopt"))))
|
||
original = od.decode(self.raw(DOCUMENT_KEY))
|
||
Path(self.params.get_param_path("Offset1")).write_bytes(b"bad")
|
||
self.assertTrue(self.units.change_units(True, None))
|
||
self.assertEqual(od.decode(self.raw(DOCUMENT_KEY)), original)
|
||
reset = self.row("slc_reset")
|
||
self.assertTrue(self.owner.apply(self.confirm(reset)))
|
||
self.assertEqual(od.decode(self.raw(DOCUMENT_KEY)).bounds_mps, original.bounds_mps)
|
||
|
||
def test_unit_write_failure_after_adoption_keeps_original_unit_and_si_schedule(self):
|
||
self.params.put_bool("SpeedLimitController", True, block=True)
|
||
self.params.put("Offset2", 3.0, block=True)
|
||
expected = od.adopt_legacy(False, (0.0, 3.0, 0.0, 0.0, 0.0, 0.0, 0.0))
|
||
with patch.object(self.params, "put_bool", side_effect=OSError("unit write failed")):
|
||
self.assertFalse(self.units.change_units(True, None))
|
||
self.assertEqual(od.decode(self.raw(DOCUMENT_KEY)), expected)
|
||
self.assertIsNone(self.raw("IsMetric"))
|
||
self.assertEqual(self.raw("Offset2"), b"3.0")
|
||
|
||
def test_corrupt_document_and_units_require_explicit_repairs(self):
|
||
self.params.put_bool("SpeedLimitController", True, block=True)
|
||
Path(self.params.get_param_path(DOCUMENT_KEY)).write_bytes(b"{broken")
|
||
Path(self.params.get_param_path("IsMetric")).write_bytes(b"broken")
|
||
self.assertTrue(self.units.change_units(True, b"broken"))
|
||
self.assertEqual(self.raw(DOCUMENT_KEY), b"{broken")
|
||
self.assertFalse(read_params(self.params).enabled)
|
||
reset = self.row("slc_reset")
|
||
self.assertTrue(self.owner.apply(self.confirm(reset)))
|
||
self.assertEqual(od.decode(self.raw(DOCUMENT_KEY)).offsets_mps, (0.0,) * 7)
|
||
self.assertTrue(read_params(self.params).enabled)
|
||
|
||
def test_all_seven_edits_and_stale_unit_request(self):
|
||
self.assertTrue(self.owner.apply(self.confirm(self.row("slc_adopt"))))
|
||
for index in range(1, 8):
|
||
row = self.row(f"Offset{index}")
|
||
edit = row_change(row, -1)
|
||
assert edit is not None
|
||
self.assertTrue(self.owner.apply(edit))
|
||
document = od.decode(self.raw(DOCUMENT_KEY))
|
||
self.assertEqual(document.offsets_mps, (-od.MPH_TO_MPS,) * 7)
|
||
self.assertEqual(document.schedule().bands[-1].offset_mps, 0.0)
|
||
self.params.put_bool("IsMetric", True, block=True)
|
||
self.assertFalse(self.units.change_units(False, None))
|
||
self.assertEqual(od.decode(self.raw(DOCUMENT_KEY)), document)
|
||
|
||
def test_large_and_compact_metric_callbacks_use_same_owner_and_restore_veto(self):
|
||
from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout
|
||
from openpilot.selfdrive.ui.mici.layouts.settings import toggles as compact
|
||
|
||
self.cp = None
|
||
self.params.put("Offset1", 5.0, block=True)
|
||
large = TogglesLayout.__new__(TogglesLayout)
|
||
large._params = self.params
|
||
large._slc_offsets = self.units
|
||
large._metric_source = None
|
||
large._toggles = {"IsMetric": NS(action_item=NS(set_state=lambda value: setattr(large, "shown", value)))}
|
||
large._toggle_defs = {"IsMetric": (lambda: "metric", "", "", False)}
|
||
self.enterContext(patch.object(large, "_update_toggles", return_value=None))
|
||
self.assertTrue(large._toggle_callback(True, "IsMetric"))
|
||
self.assertTrue(large.shown)
|
||
self.assertEqual(self.raw("IsMetric"), b"1")
|
||
adopted = od.decode(self.raw(DOCUMENT_KEY))
|
||
|
||
compact_layout = compact.TogglesLayoutMici.__new__(compact.TogglesLayoutMici)
|
||
compact_layout._slc_offsets = self.units
|
||
self.enterContext(patch.object(compact_layout, "_update_toggles", return_value=None))
|
||
compact_layout._metric_source = b"0" # stale displayed source
|
||
cast(Any, compact_layout)._metric_toggle = NS(set_checked=lambda value: setattr(compact_layout, "shown", value))
|
||
with patch.object(compact, "ui_state", NS(params=self.params)):
|
||
compact_layout._on_metric(False)
|
||
self.assertTrue(compact_layout.shown)
|
||
self.assertEqual(self.raw("IsMetric"), b"1")
|
||
compact_layout._metric_source = b"1"
|
||
compact_layout._on_metric(False)
|
||
self.assertFalse(compact_layout.shown)
|
||
self.assertEqual(self.raw("IsMetric"), b"0")
|
||
self.assertEqual(od.decode(self.raw(DOCUMENT_KEY)), adopted)
|
||
|
||
def test_both_native_adapters_confirm_adoption_with_displayed_unit_source(self):
|
||
from openpilot.starpilot.ui import feature_settings_compact as compact
|
||
from openpilot.starpilot.ui.runtime_app import StarShellSession
|
||
from openpilot.system.ui.widgets import DialogResult
|
||
|
||
class Dialog:
|
||
def __init__(self, _question, *_args, callback=None, **_kwargs):
|
||
self.confirmed = callback or _args[-1]
|
||
|
||
self.params.put_bool("IsMetric", True, block=True)
|
||
row = self.row("slc_adopt")
|
||
large = StarShellSession.__new__(StarShellSession)
|
||
cast(Any, large).feature_request = self.owner.apply
|
||
pushed = []
|
||
with patch("openpilot.system.ui.widgets.confirm_dialog.ConfirmDialog", Dialog), \
|
||
patch("openpilot.starpilot.ui.runtime_app.gui_app.push_widget", pushed.append):
|
||
large._confirm_feature_reset(row)
|
||
pushed[-1].confirmed(DialogResult.CONFIRM)
|
||
self.assertIsInstance(od.decode(self.raw(DOCUMENT_KEY)), od.OffsetDocument)
|
||
|
||
Path(self.params.get_param_path(DOCUMENT_KEY)).unlink()
|
||
row = self.row("slc_adopt")
|
||
compact_adapter = compact.FeatureSettingsCompact(cast(Any, NS(feature_request=self.owner.apply)))
|
||
pushed = []
|
||
with patch.object(compact, "BigConfirmationDialog", Dialog), \
|
||
patch.object(compact.gui_app, "push_widget", pushed.append), \
|
||
patch.object(compact.gui_app, "texture", lambda *_args: None):
|
||
compact_adapter._confirm_reset(row, lambda: None)
|
||
pushed[-1].confirmed()
|
||
self.assertIsInstance(od.decode(self.raw(DOCUMENT_KEY)), od.OffsetDocument)
|
||
|
||
def test_both_native_reset_confirmations_recover_corrupt_legacy_and_recheck_source(self):
|
||
from openpilot.starpilot.ui import feature_settings_compact as compact
|
||
from openpilot.starpilot.ui.runtime_app import StarShellSession
|
||
from openpilot.system.ui.widgets import DialogResult
|
||
|
||
class Dialog:
|
||
def __init__(self, _question, *_args, callback=None, **_kwargs):
|
||
self.confirmed = callback or _args[-1]
|
||
|
||
legacy = Path(self.params.get_param_path("Offset1"))
|
||
legacy.write_bytes(b"bad")
|
||
large = StarShellSession.__new__(StarShellSession)
|
||
cast(Any, large).feature_request = self.owner.apply
|
||
pushed = []
|
||
with patch("openpilot.system.ui.widgets.confirm_dialog.ConfirmDialog", Dialog), \
|
||
patch("openpilot.starpilot.ui.runtime_app.gui_app.push_widget", pushed.append):
|
||
large._confirm_feature_reset(self.row("slc_reset"))
|
||
legacy.write_bytes(b"changed")
|
||
pushed[-1].confirmed(DialogResult.CONFIRM)
|
||
self.assertIsNone(self.raw(DOCUMENT_KEY))
|
||
large._confirm_feature_reset(self.row("slc_reset"))
|
||
pushed[-1].confirmed(DialogResult.CONFIRM)
|
||
self.assertFalse(read_params(self.params).enabled)
|
||
self.assertEqual(od.decode(self.raw(DOCUMENT_KEY)).offsets_mps, (0.0,) * 7)
|
||
Path(self.params.get_param_path(DOCUMENT_KEY)).unlink()
|
||
|
||
self.params.put_bool("SpeedLimitController", True, block=True)
|
||
compact_adapter = compact.FeatureSettingsCompact(cast(Any, NS(feature_request=self.owner.apply)))
|
||
pushed = []
|
||
with patch.object(compact, "BigConfirmationDialog", Dialog), \
|
||
patch.object(compact.gui_app, "push_widget", pushed.append), \
|
||
patch.object(compact.gui_app, "texture", lambda *_args: None):
|
||
compact_adapter._confirm_reset(self.row("slc_reset"), lambda: None)
|
||
pushed[-1].confirmed()
|
||
self.assertTrue(read_params(self.params).enabled)
|
||
self.assertEqual(self.raw("SpeedLimitController"), b"1")
|
||
self.assertEqual(od.decode(self.raw(DOCUMENT_KEY)).offsets_mps, (0.0,) * 7)
|
||
|
||
def test_both_native_reset_dialogs_describe_saved_or_default_ranges(self):
|
||
from openpilot.starpilot.ui import feature_settings_compact as compact
|
||
from openpilot.starpilot.ui.runtime_app import StarShellSession
|
||
|
||
class Dialog:
|
||
def __init__(self, question, *_args, **_kwargs):
|
||
self.question = question
|
||
|
||
large = StarShellSession.__new__(StarShellSession)
|
||
compact_adapter = compact.FeatureSettingsCompact(cast(Any, NS(feature_request=self.owner.apply)))
|
||
pushed = []
|
||
with patch("openpilot.system.ui.widgets.confirm_dialog.ConfirmDialog", Dialog), \
|
||
patch.object(compact, "BigConfirmationDialog", Dialog), \
|
||
patch("openpilot.starpilot.ui.runtime_app.gui_app.push_widget", pushed.append), \
|
||
patch.object(compact.gui_app, "texture", lambda *_args: None):
|
||
Path(self.params.get_param_path("Offset1")).write_bytes(b"bad")
|
||
recovery = self.row("slc_reset")
|
||
self.assertEqual(recovery.value, "Use default mph speed ranges")
|
||
large._confirm_feature_reset(recovery)
|
||
compact_adapter._confirm_reset(recovery, lambda: None)
|
||
self.assertTrue(all("default mph speed ranges" in dialog.question for dialog in pushed[-2:]))
|
||
|
||
self.assertTrue(self.owner.apply(self.confirm(recovery)))
|
||
saved = self.row("slc_reset")
|
||
self.assertEqual(saved.value, "Keep saved speed ranges")
|
||
large._confirm_feature_reset(saved)
|
||
compact_adapter._confirm_reset(saved, lambda: None)
|
||
self.assertTrue(all("Keep saved speed ranges" in dialog.question for dialog in pushed[-2:]))
|