mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-06 20:43:46 +08:00
283 lines
12 KiB
Python
283 lines
12 KiB
Python
"""Actual file-owner tests, CPU-only, no IPC/Params runtime required."""
|
|
import copy
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
from openpilot.starpilot.galaxy.onroad_layout import LayoutChanged
|
|
from openpilot.starpilot.galaxy.projection_layout import ProjectionLayoutOwner, host_owner
|
|
from openpilot.starpilot.system.starport.display_profile import record_screen
|
|
from openpilot.starpilot.system.starport.projection_layout import ProjectionLayoutSource
|
|
from openpilot.starpilot.ui.onroad_customization import default_document
|
|
|
|
SCREEN = {'version': 1, 'width': 1280, 'height': 720, 'margin_width': 0,
|
|
'margin_height': 240, 'fps': 60, 'config_index': 0}
|
|
|
|
|
|
class Params:
|
|
def __init__(self, root):
|
|
self.root = root
|
|
self.enabled = True
|
|
self.jetlink_mode = 0
|
|
def get(self, key):
|
|
assert key == 'JetlinkMode'
|
|
return self.jetlink_mode
|
|
def get_param_path(self, key):
|
|
return str(self.root / key)
|
|
def get_bool(self, key):
|
|
assert key == 'StarPortEnabled'
|
|
return self.enabled
|
|
|
|
|
|
class TestProjectionLayoutOwner(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.temp.cleanup)
|
|
self.root = Path(self.temp.name)
|
|
self.params = Params(self.root)
|
|
self.parked = True
|
|
self.source = ProjectionLayoutSource(self.root / 'layouts/document.json')
|
|
self.screen_path = self.root / 'screen.json'
|
|
self.owner = ProjectionLayoutOwner(self.params, lambda: self.parked, self.source, self.screen_path)
|
|
|
|
def screen(self, **changes):
|
|
record_screen(SCREEN | changes, self.screen_path)
|
|
|
|
def payload(self):
|
|
snapshot = self.owner.snapshot()
|
|
return {'revision': snapshot['revision'], 'document': copy.deepcopy(snapshot['document'])}
|
|
|
|
def test_no_screen_no_fake_geometry(self):
|
|
value = self.owner.snapshot()
|
|
self.assertFalse(value['available'])
|
|
self.assertFalse(value['editable'])
|
|
self.assertFalse(value['valid'])
|
|
self.assertIsNone(value['document'])
|
|
self.assertIsNone(value['metadata'])
|
|
self.assertIsNone(value['defaults'])
|
|
self.assertIn('actual screen', value['reason'])
|
|
|
|
def test_jetlink_widget_available_when_selected_and_placement_retained_when_off(self):
|
|
self.screen()
|
|
self.assertFalse(self.owner.snapshot()['metadata']['widgets']['jetlink_status']['available'])
|
|
for mode in (1, 2):
|
|
self.params.jetlink_mode = mode
|
|
self.assertTrue(self.owner.snapshot()['metadata']['widgets']['jetlink_status']['available'])
|
|
payload = self.payload()
|
|
payload['document']['widgets']['jetlink_status'].update(enabled=True, x=700, y=500)
|
|
self.owner.save(payload, session_valid=lambda: True)
|
|
self.params.jetlink_mode = 0
|
|
snapshot = self.owner.snapshot()
|
|
self.assertFalse(snapshot['metadata']['widgets']['jetlink_status']['available'])
|
|
self.assertEqual(snapshot['document'], payload['document'])
|
|
|
|
def test_defaults_valid_false_then_exact_saved_document(self):
|
|
self.screen()
|
|
value = self.owner.snapshot()
|
|
self.assertTrue(value['editable'])
|
|
self.assertFalse(value['valid'])
|
|
self.assertEqual(value['document']['canvas'], {'width': 2880, 'height': 1080})
|
|
payload = self.payload()
|
|
payload['document']['widgets']['current_speed']['x'] += 10
|
|
payload['document']['blindSpotCamera'] = True
|
|
saved = self.owner.save(payload, session_valid=lambda: True)
|
|
self.assertTrue(saved['valid'])
|
|
self.assertEqual(saved['document'], payload['document'])
|
|
self.assertNotEqual(saved['revision'], payload['revision'])
|
|
self.assertEqual(json.loads(self.source.path.read_bytes()), payload['document'])
|
|
self.assertFalse((self.root / 'OnroadCustomizations').exists())
|
|
|
|
def test_first_layout_starts_from_comma_display_choices(self):
|
|
self.screen()
|
|
for key, raw in {'BlindSpotMetrics': b'0', 'SignalMetrics': b'1', 'HidePersonalityAlerts': b'1', 'ShowBrakeStatus': b'1',
|
|
'HideLeadMarker': b'0', 'LeadInfo': b'1', 'LeadInfoMode': b'1'}.items():
|
|
(self.root / key).write_bytes(raw)
|
|
value = self.owner.snapshot()
|
|
seeded = {key: value['document'][key] for key in ('blindSpotBorder', 'signalBorder', 'hidePersonalityAlerts',
|
|
'wheelPedalFeedback', 'leadIndicator', 'leadInfo', 'blindSpotCamera')}
|
|
self.assertEqual(seeded, {'blindSpotBorder': False, 'signalBorder': True, 'hidePersonalityAlerts': True,
|
|
'wheelPedalFeedback': True, 'leadIndicator': True, 'leadInfo': 'Distance', 'blindSpotCamera': False})
|
|
self.assertFalse(value['defaults']['signalBorder'], 'reset still returns to the plain defaults')
|
|
saved = self.owner.save(self.payload(), session_valid=lambda: True)
|
|
# Once saved, the AA screen keeps its own choices whatever the comma changes.
|
|
(self.root / 'SignalMetrics').write_bytes(b'0')
|
|
self.assertTrue(self.owner.snapshot()['document']['signalBorder'])
|
|
self.assertEqual(self.owner.snapshot()['document'], saved['document'])
|
|
|
|
def test_disable_map_turns_only_the_map_off_and_only_when_it_was_on(self):
|
|
self.screen()
|
|
self.assertFalse(self.owner.disable_map(session_valid=lambda: True), 'nothing saved yet')
|
|
payload = self.payload()
|
|
payload['document']['widgets']['nav_map']['enabled'] = True
|
|
payload['document']['widgets']['nav_card']['x'] -= 5
|
|
self.owner.save(payload, session_valid=lambda: True)
|
|
self.assertFalse(self.owner.disable_map(session_valid=lambda: False), 'a stale session cannot change it')
|
|
self.assertTrue(self.owner.snapshot()['document']['widgets']['nav_map']['enabled'])
|
|
self.parked = False
|
|
self.assertFalse(self.owner.disable_map(session_valid=lambda: True), 'only while parked')
|
|
self.parked = True
|
|
self.assertTrue(self.owner.disable_map(session_valid=lambda: True))
|
|
saved = self.owner.snapshot()
|
|
self.assertTrue(saved['valid'])
|
|
expected = copy.deepcopy(payload['document'])
|
|
expected['widgets']['nav_map']['enabled'] = False
|
|
self.assertEqual(saved['document'], expected)
|
|
self.assertFalse(self.owner.disable_map(session_valid=lambda: True))
|
|
|
|
def test_disabled_retains_layout_but_unavailable(self):
|
|
self.screen()
|
|
self.owner.save(self.payload(), session_valid=lambda: True)
|
|
before = self.source.path.read_bytes()
|
|
self.params.enabled = False
|
|
snapshot = self.owner.snapshot()
|
|
self.assertFalse(snapshot['available'])
|
|
self.assertFalse(snapshot['editable'])
|
|
self.assertTrue(snapshot['valid'])
|
|
self.assertIn('Enable StarPort', snapshot['reason'])
|
|
with self.assertRaises(LayoutChanged):
|
|
self.owner.save(self.payload(), session_valid=lambda: True)
|
|
self.assertEqual(self.source.path.read_bytes(), before)
|
|
|
|
def test_corrupt_and_other_canvas_default_without_overwrite(self):
|
|
self.screen()
|
|
self.source.prepare()
|
|
self.source.path.write_bytes(b'bad')
|
|
self.assertFalse(self.owner.snapshot()['valid'])
|
|
self.assertEqual(self.source.path.read_bytes(), b'bad')
|
|
self.owner.save(self.payload(), session_valid=lambda: True)
|
|
before = self.source.path.read_bytes()
|
|
self.screen(margin_height=0)
|
|
value = self.owner.snapshot()
|
|
self.assertFalse(value['valid'])
|
|
self.assertEqual(value['document'], value['defaults'])
|
|
self.assertEqual(self.source.path.read_bytes(), before)
|
|
|
|
def test_revision_binds_exact_screen_bytes_and_document(self):
|
|
self.screen()
|
|
payload = self.payload()
|
|
# Same geometry with a changed selected mode is still a changed receiver receipt.
|
|
self.screen(config_index=1)
|
|
with self.assertRaises(LayoutChanged):
|
|
self.owner.save(payload, session_valid=lambda: True)
|
|
self.assertFalse(self.source.path.exists())
|
|
payload = self.payload()
|
|
self.source.prepare()
|
|
self.source.path.write_bytes(b'changed')
|
|
with self.assertRaises(LayoutChanged):
|
|
self.owner.save(payload, session_valid=lambda: True)
|
|
self.assertEqual(self.source.path.read_bytes(), b'changed')
|
|
|
|
def test_screen_change_during_commit_authorization_rejects(self):
|
|
self.screen()
|
|
payload = self.payload()
|
|
calls = 0
|
|
def session():
|
|
nonlocal calls
|
|
calls += 1
|
|
if calls == 3:
|
|
self.screen(config_index=2)
|
|
return True
|
|
with self.assertRaises(LayoutChanged):
|
|
self.owner.save(payload, session_valid=session)
|
|
self.assertGreaterEqual(calls, 3)
|
|
self.assertFalse(self.source.path.exists())
|
|
|
|
def test_session_revoke_or_unpark_inside_commit_rejects(self):
|
|
self.screen()
|
|
for mode in ('session', 'parked', 'disabled'):
|
|
self.parked = True
|
|
self.params.enabled = True
|
|
calls = 0
|
|
def session(mode=mode):
|
|
nonlocal calls
|
|
calls += 1
|
|
if calls >= 3:
|
|
if mode == 'session':
|
|
return False
|
|
if mode == 'parked':
|
|
self.parked = False
|
|
if mode == 'disabled':
|
|
self.params.enabled = False
|
|
return True
|
|
with self.assertRaises(LayoutChanged):
|
|
self.owner.save(self.payload(), session_valid=session)
|
|
self.assertFalse(self.source.path.exists())
|
|
|
|
def test_invalid_payload_or_bounds_never_written(self):
|
|
self.screen()
|
|
payload = self.payload()
|
|
for broken in (payload | {'colors': {}}, payload | {'revision': None}):
|
|
with self.assertRaises(ValueError):
|
|
self.owner.save(broken, session_valid=lambda: True)
|
|
payload['document']['widgets']['current_speed']['x'] = float('nan')
|
|
with self.assertRaises(ValueError):
|
|
self.owner.save(payload, session_valid=lambda: True)
|
|
self.assertFalse(self.source.path.exists())
|
|
|
|
def test_shared_colors_are_readonly_and_outside_layout(self):
|
|
self.screen()
|
|
colors = default_document()
|
|
colors['palette']['text'] = '#123456FF'
|
|
path = self.root / 'OnroadCustomizations'
|
|
raw = json.dumps(colors).encode()
|
|
path.write_bytes(raw)
|
|
self.assertEqual(self.owner.snapshot()['colors']['palette']['text'], '#123456FF')
|
|
self.owner.save(self.payload(), session_valid=lambda: True)
|
|
self.assertEqual(path.read_bytes(), raw)
|
|
self.assertNotIn('colors', json.loads(self.source.path.read_bytes()))
|
|
|
|
def test_unreadable_or_invalid_screen_cannot_edit(self):
|
|
for raw in (b'not json', b'x' * 2049, b'{"version":1,"version":1}'):
|
|
self.screen_path.write_bytes(raw)
|
|
self.assertFalse(self.owner.snapshot()['available'])
|
|
with self.assertRaises(LayoutChanged):
|
|
self.owner.save({'revision': 'any', 'document': {}}, session_valid=lambda: True)
|
|
|
|
|
|
def test_host_runtime_seeds_private_screen_and_ignores_disabled(self):
|
|
root = self.root / 'host'
|
|
self.assertIsNone(host_owner(self.params, lambda: True, root, environ={}))
|
|
self.assertFalse(root.exists())
|
|
owner = host_owner(self.params, lambda: True, root, environ={'SP_HOST_RUNTIME': '1'})
|
|
self.params.enabled = False
|
|
snapshot = owner.snapshot()
|
|
self.assertTrue(snapshot['editable'])
|
|
self.assertIsNone(snapshot['reason'])
|
|
self.assertEqual(snapshot['screen']['width'], 1920)
|
|
payload = {'revision': snapshot['revision'], 'document': copy.deepcopy(snapshot['document'])}
|
|
payload['document']['widgets']['current_speed']['x'] += 10
|
|
owner.save(payload, session_valid=lambda: True)
|
|
screen = (root / 'screen.json').read_bytes()
|
|
again = host_owner(self.params, lambda: True, root, environ={'SP_HOST_RUNTIME': '1'})
|
|
self.assertEqual((root / 'screen.json').read_bytes(), screen)
|
|
self.assertEqual(again.snapshot()['document'], payload['document'])
|
|
|
|
def test_host_editor_and_renderer_share_default_paths(self):
|
|
# Constants are initialized at import time, as in the actual launcher processes.
|
|
code = """
|
|
import copy
|
|
import os
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
from openpilot.starpilot.galaxy.projection_layout import host_owner
|
|
from openpilot.starpilot.system.starport.projection_layout_runtime import load_projection_layout
|
|
root = Path(os.environ['STARPORT_DIR'])
|
|
params = SimpleNamespace(get_param_path=lambda key: str(root / 'params' / key), get_bool=lambda key: False, get=lambda key: None)
|
|
owner = host_owner(params, lambda: True)
|
|
snapshot = owner.snapshot()
|
|
payload = {'revision': snapshot['revision'], 'document': copy.deepcopy(snapshot['document'])}
|
|
payload['document']['widgets']['current_speed']['x'] += 10
|
|
owner.save(payload, session_valid=lambda: True)
|
|
assert load_projection_layout((1920, 1080)) == payload['document']
|
|
"""
|
|
env = os.environ | {'SP_HOST_RUNTIME': '1', 'STARPORT_DIR': str(self.root / 'host')}
|
|
subprocess.run([sys.executable, '-c', code], env=env, check=True, capture_output=True)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|