Files
StarPilot/openpilot/starpilot/ui/tests/test_layout_preview_transport.py
T
firestar5683 a82935f326 User interfaces and Android Auto
Integrate onroad views, configurable widgets, local settings, steering-profile contracts, Bluetooth and Android Auto with their device and runtime services.

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
Co-authored-by: Daniel Koepping <elkoled@gmail.com>
Co-authored-by: Danny <dannyirwin31@yahoo.com>
Co-authored-by: DimitriChrysafis <Dimitri.chrysafis@gmail.com>
Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com>
Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu>
Co-authored-by: Shane Smiskol <shane@smiskol.com>
Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com>
Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>
Co-authored-by: stef <19478336+stefpi@users.noreply.github.com>
Co-authored-by: whoisdomi <dcantu83@gmail.com>

Co-authored-by: Intelli <contact@intelli.software>
Co-authored-by: pharmacomaniac <blittle65@gmail.com>

Co-authored-by: prabhaavp <prabhaav@msu.edu>
2026-10-14 20:02:39 -05:00

151 lines
5.7 KiB
Python

from pathlib import Path
import struct
import tempfile
import threading
import time
import unittest
import zlib
from openpilot.starpilot.ui.layout_preview_transport import (
PreviewDenied, PreviewService, PreviewUnavailable, request_preview, request_profile, validate_png,
)
from openpilot.starpilot.ui.onroad_customization import default_document
def png(width=2, height=1):
def chunk(tag, data):
return struct.pack('!I', len(data)) + tag + data + struct.pack('!I', zlib.crc32(tag + data))
pixels = b''.join(b'\x00' + b'\x00\x00\x00\xff' * width for _ in range(height))
return (b'\x89PNG\r\n\x1a\n' + chunk(b'IHDR', struct.pack('!IIBBBBB', width, height, 8, 6, 0, 0, 0)) +
chunk(b'IDAT', zlib.compress(pixels)) + chunk(b'IEND', b''))
class TestLayoutPreviewTransport(unittest.TestCase):
def setUp(self):
temp = tempfile.TemporaryDirectory()
self.addCleanup(temp.cleanup)
self.path = Path(temp.name) / 'preview.sock'
self.parked = True
self.calls = []
def renderer(payload):
self.calls.append((time.monotonic(), payload))
return png()
self.service = PreviewService(renderer, lambda: self.parked, self.path, active_profile='large')
self.service.start()
self.addCleanup(self.service.close)
self.payload = {'document': default_document(), 'profile': 'large', 'scene': 'engaged'}
def request_with_pump(self, payload=None):
result = {}
def client():
try:
result['image'] = request_preview(payload or self.payload, self.path)
except Exception as error:
result['error'] = error
worker = threading.Thread(target=client)
worker.start()
deadline = time.monotonic() + 3
while worker.is_alive() and time.monotonic() < deadline:
self.service.poll()
worker.join(0.01)
self.assertFalse(worker.is_alive())
return result
def test_main_thread_render_exact_payload_and_live_profile(self):
self.assertEqual(request_profile(self.path, timeout=0.5), 'large')
self.service.set_active_profile('compact')
self.assertEqual(request_profile(self.path), 'compact')
result = self.request_with_pump()
self.assertEqual(result['image'], png())
self.assertEqual(self.calls[0][1], self.payload)
self.assertTrue(validate_png(result['image']))
def test_parked_rechecked_before_and_after_render(self):
self.parked = False
result = self.request_with_pump()
self.assertIsInstance(result['error'], PreviewDenied)
self.assertEqual(self.calls, [])
self.parked = True
self.service.renderer = lambda payload: (setattr(self, 'parked', False), png())[1]
result = self.request_with_pump()
self.assertIsInstance(result['error'], PreviewDenied)
def test_rate_limit_defers_latest_request_then_expires_unpolled_request(self):
self.assertIn('image', self.request_with_pump())
self.assertIn('image', self.request_with_pump())
self.assertGreaterEqual(self.calls[1][0] - self.calls[0][0], 0.49)
result = {}
client = threading.Thread(target=lambda: result.setdefault('error', self._unpolled_request()))
client.start()
client.join(3)
self.assertFalse(client.is_alive())
self.assertIsInstance(result['error'], PreviewUnavailable)
self.assertFalse(self.service.poll())
self.assertEqual(len(self.calls), 2)
def _unpolled_request(self):
try:
request_preview(self.payload, self.path)
except Exception as error:
return error
return None
def test_invalid_request_and_offline_are_bounded(self):
invalid = {**self.payload, 'scene': 'unknown'}
with self.assertRaises(ValueError):
request_preview(invalid, self.path)
with self.assertRaises(ValueError):
validate_png(png(5000, 1))
self.service.close()
self.assertIsNone(request_profile(self.path))
with self.assertRaises(PreviewUnavailable):
request_preview(self.payload, self.path, timeout=0.2)
def test_second_service_cannot_remove_live_socket(self):
contender = PreviewService(lambda payload: png(), lambda: True, self.path)
with self.assertRaises(PreviewUnavailable):
contender.start()
contender.close()
self.assertTrue(self.path.exists())
deadline = time.monotonic() + 1.5
profile = None
while profile is None and time.monotonic() < deadline:
profile = request_profile(self.path, timeout=0.5)
time.sleep(0.02)
self.assertEqual(profile, 'large')
def test_unexpected_render_error_is_logged_once_and_client_gets_generic_error(self):
def broken(payload):
raise RuntimeError('Missing render texture')
self.service.renderer = broken
with self.assertLogs('openpilot.starpilot.ui.layout_preview_transport', level='ERROR') as captured:
first = self.request_with_pump()
second = self.request_with_pump()
self.assertIsInstance(first['error'], PreviewUnavailable)
self.assertIsInstance(second['error'], PreviewUnavailable)
self.assertEqual(str(first['error']), 'Onroad UI preview is unavailable')
self.assertEqual(len(captured.output), 1)
self.assertIn('RuntimeError', captured.output[0])
self.assertIn('broken', captured.output[0])
self.assertIn('Missing render texture', captured.output[0])
self.assertNotIn(str(self.payload), captured.output[0])
def test_deliberate_unavailable_does_not_log(self):
def unavailable(payload):
raise PreviewUnavailable('Graphics context unavailable')
self.service.renderer = unavailable
with self.assertNoLogs('openpilot.starpilot.ui.layout_preview_transport', level='ERROR'):
result = self.request_with_pump()
self.assertIsInstance(result['error'], PreviewUnavailable)
self.assertEqual(str(result['error']), 'Graphics context unavailable')
if __name__ == '__main__':
unittest.main()