Files
StarPilot/openpilot/starpilot/analytics/tests/test_settings.py
T
2026-10-30 13:00:19 -05:00

145 lines
7.6 KiB
Python

"""Usage settings use owner values without raw private data or mutations."""
from dataclasses import replace
import json
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch
from openpilot.common.params import Params
from openpilot.starpilot.analytics import settings
from openpilot.starpilot.ui.feature_settings_state import FeatureRow
from openpilot.starpilot.ui.presentation import Profile
class SecretTrap:
def __init__(self, params):
self.params = params
self.secret_reads = []
def __getattr__(self, name):
if name in ("put", "put_bool", "remove", "clear_all", "all_keys"):
raise AssertionError(f"Collector attempted {name}")
function = getattr(self.params, name)
def call(key, *args, **kwargs):
if key in ("AccessToken", "SecOCKey", "LastGPSPosition", "GalaxyPairing", "CarParams", "DongleId"):
self.secret_reads.append(key)
raise AssertionError("Private data read")
return function(key, *args, **kwargs)
return call
class TestScalarProjection(unittest.TestCase):
def row(self, **kwargs):
return FeatureRow("NewFeature", "New Feature", "On", choices=("Off", "On"), default_value="Off", **kwargs)
def test_new_rows_are_automatic_without_exporting_sources(self):
row = self.row(source=b"private-bytes", related_source=b"private-related", dependencies=(("AccessToken", b"private-token"),))
with patch.object(settings, "_owner_rows", return_value=iter((("data", row),))):
result = settings.collect_settings(object(), None)
self.assertTrue(result["setting_data_NewFeature"])
self.assertFalse(result["default_data_NewFeature"])
self.assertTrue(result["valid_data_NewFeature"])
self.assertNotIn("private", json.dumps(result))
def test_invalid_numbers_fixed_enums_and_commands(self):
number = FeatureRow("Gain", "Gain", ".75", step=.05, minimum=.5, maximum=1.5, default_value="1")
mode = FeatureRow("Mode", "Mode", "Auto", choices=("Auto", "Manual"), default_value="Manual")
rows = (("lane", number), ("lane", mode), ("lane", replace(number, key="BadGain", value="NaN")),
("lane", replace(number, key="TooLarge", value="2")),
("lane", replace(mode, key="FreeText", choices=(), default_value=None, value="SECRET")),
("sounds", replace(mode, key="SoundPack", choices=("SECRET",), value="SECRET")),
("lane", replace(self.row(), key="lane:reset")),
("lane", replace(self.row(), key="torque_prepare_firestar")))
result = settings._fields(rows)
self.assertEqual(result["setting_lane_Gain"], .75)
self.assertEqual(result["default_lane_Gain"], 1.0)
self.assertEqual(result["setting_lane_Mode"], "Auto")
self.assertFalse(result["valid_lane_BadGain"])
self.assertNotIn("setting_lane_BadGain", result)
self.assertFalse(result["valid_lane_TooLarge"])
self.assertNotIn("SECRET", json.dumps(result))
self.assertFalse(any("reset" in key or "prepare" in key for key in result))
def test_deterministic_bounds_and_field_id_collisions(self):
rows = [("page", replace(self.row(), key=f"Feature{i}")) for i in range(settings.MAX_SETTINGS + 10)]
first, second = settings._fields(rows), settings._fields(reversed(rows))
self.assertEqual(first, second)
self.assertTrue(first["settings_truncated"])
self.assertLessEqual(len(json.dumps(first).encode()), settings.MAX_BYTES)
collision = settings._fields((("page", replace(self.row(), key="a:b")), ("page", replace(self.row(), key="a_b"))))
self.assertFalse(collision["valid_page_a_b"])
self.assertNotIn("setting_page_a_b", collision)
class TestActualSettingsOwners(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.params = Params(self.temp.name)
self.params.put_bool("IsOffroad", True, block=True)
def files(self):
return {str(path.relative_to(self.temp.name)): path.read_bytes()
for path in Path(self.temp.name).rglob("*") if path.is_file()}
def test_real_owners_default_values_and_profile_defaults(self):
self.params.put_bool("LaneCentering", True, block=True)
self.params.put_bool("StockConfidenceBallWidget", True, block=True)
trap = SecretTrap(self.params)
before = self.files()
large = settings.collect_settings(trap, None, profile=Profile.LARGE)
compact = settings.collect_settings(trap, None, profile=Profile.COMPACT)
self.assertNotIn("setting_data_ShareUsageStats", large)
self.assertNotIn("default_data_ShareUsageStats", large)
self.assertTrue(large["setting_lane_LaneCentering"])
self.assertTrue(large["default_lane_LaneCentering"])
self.assertFalse(compact["setting_appearance_StockConfidenceBallWidget"])
self.assertTrue(compact["default_appearance_StockConfidenceBallWidget"])
self.assertEqual(large["default_display_ScreenTimeoutOnroad"], 10.0)
self.assertEqual(compact["default_display_ScreenTimeoutOnroad"], 5.0)
self.assertEqual(large["setting_display_ScreenBrightness"], "Auto")
self.assertEqual(self.files(), before)
self.assertEqual(trap.secret_reads, [])
self.assertTrue(all(type(value) in (bool, float, str) for value in large.values()))
def test_malformed_saved_value_and_secret_files_remain_private(self):
Path(self.params.get_param_path("LaneCentering")).write_bytes(b"PRIVATE_MALFORMED")
for key in ("AccessToken", "LastGPSPosition", "SecOCKey"):
Path(self.params.get_param_path(key)).write_bytes(b"PRIVATE_SECRET")
before = self.files()
trap = SecretTrap(self.params)
result = settings.collect_settings(trap, None, profile=Profile.LARGE)
self.assertFalse(result["valid_lane_LaneCentering"])
self.assertNotIn("setting_lane_LaneCentering", result)
self.assertNotIn("PRIVATE", json.dumps(result))
self.assertEqual(self.files(), before)
self.assertEqual(trap.secret_reads, [])
def test_nested_routes_and_saved_monitor_choices_exclude_layouts(self):
from openpilot.starpilot.sentry_mode.preferences import KEY as SENTRY, Preferences as Sentry, encode as encode_sentry
from openpilot.starpilot.sentry_mode.policy import Settings
from openpilot.starpilot.spot_monitor.preferences import KEY as VASM, Preferences as Visual, encode as encode_visual
self.params.put_bool("PIPPreviewShowOnBlinker", True, block=True)
self.params.put(SENTRY, json.loads(encode_sentry(Sentry(True, Settings(.123, 2.5)))), block=True)
self.params.put(VASM, json.loads(encode_visual(Visual(confidence=.96, smooth_seconds=.3))), block=True)
before = self.files()
result = settings.collect_settings(SecretTrap(self.params), None, profile=Profile.COMPACT)
self.assertTrue(result["setting_pip_PIPPreviewShowOnBlinker"])
self.assertTrue(result["setting_sentry_sentry_enabled"])
self.assertEqual(result["setting_sentry_sentry_sensitivity"], .123)
self.assertEqual(result["setting_sentry_sentry_warning"], 2.5)
self.assertEqual(result["setting_vasm_vasm_confidence"], .96)
self.assertEqual(result["setting_vasm_vasm_smooth"], .3)
for personality in ("traffic", "aggressive", "standard", "relaxed"):
for category in ("acceleration", "braking", "following"):
key = f"setting_{personality}_{category}_profile_{personality}_{category}"
self.assertIn(key, result)
self.assertIsInstance(result[key], str)
self.assertTrue(any(key.startswith("setting_conditional_cem_") for key in result))
self.assertTrue(any(key.startswith("setting_conditional_ccm_") for key in result))
self.assertFalse(any("pip_mask" in key or "pip_format" in key or "annotation" in key for key in result))
self.assertEqual(self.files(), before)