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

86 lines
4.7 KiB
Python

"""Scene sketches and eye controls must agree with the native driving presentation."""
import json
from collections.abc import Callable
from pathlib import Path
import shutil
import subprocess
from typing import NoReturn, cast
import pytest
from openpilot.starpilot.system.starport.projection_layout import default_layout_for_viewport, projection_customization
from openpilot.starpilot.ui.layout_preview_renderer import sample_state
from openpilot.starpilot.ui.layout_preview_transport import SCENES
from openpilot.starpilot.ui.onroad import axis_status_color, driving_mode_description
from openpilot.starpilot.ui.onroad_cem_gauge import gauge_data
from openpilot.starpilot.ui.onroad_customization import PROFILES, default_document
from openpilot.starpilot.ui.onroad_dm import monitor_visible
from openpilot.starpilot.ui.onroad_state import compact_sign_obscured_by_actions, slc_controls
from openpilot.starpilot.ui.presentation import Profile
from openpilot.starpilot.ui.unified_speed_presentation import resolve_unified_speed
@pytest.mark.parametrize('target', ('large', 'compact', 'projection'))
def test_every_scene_matches_native_border_mode_and_widget_visibility(target):
node = shutil.which('node')
if node is None:
cast('Callable[[str], NoReturn]', pytest.skip)('Node is required for the browser/native scene contract')
profile = Profile.COMPACT if target == 'compact' else Profile.LARGE
document = default_document()
if target == 'projection':
document = projection_customization(default_layout_for_viewport((2880, 1080)), document)
layout = document['layouts'][str(profile)]
for placed in layout.values():
placed['enabled'] = True
module = Path(__file__).parents[2] / 'galaxy/web/js/layout-preview.js'
script = f"""
import {{PREVIEW_SCENES, previewScene, sceneHidesWidget}} from {json.dumps(module.as_uri())};
const input = JSON.parse(await new Promise(resolve => {{
let data = ''; process.stdin.on('data', chunk => data += chunk); process.stdin.on('end', () => resolve(data));
}}));
process.stdout.write(JSON.stringify(PREVIEW_SCENES.map(({{id}}) => ({{id, ...previewScene(id), hidden:
Object.fromEntries(input.ids.map(key => [key, sceneHidesWidget(id, input.profile, key, input.layout, input.widgets)]))}}))));
"""
ids = set(layout) | {'stopped_timer'}
for move in (False, True):
if move and profile == Profile.COMPACT:
layout['driver_monitor'].update(x=200, y=100)
layout['speed_limit'].update(x=200, y=100)
layout['speed_limit_actions'].update(x=16, y=0)
result = subprocess.run([node, '--input-type=module', '-e', script], input=json.dumps({
'profile': str(profile), 'layout': layout, 'widgets': PROFILES[str(profile)]['widgets'], 'ids': sorted(ids),
}), text=True, capture_output=True, check=True)
scenes = json.loads(result.stdout)
assert {scene['id'] for scene in scenes} == SCENES
for scene in scenes:
state = sample_state(scene['id'], document)
color = axis_status_color(state)
assert scene['border'] == f'#{color.r:02x}{color.g:02x}{color.b:02x}', (target, scene['id'])
assert scene['mode'] == driving_mode_description(state)
assert (scene['lateral'], scene['longitudinal']) == (state.lateral_active, state.longitudinal_active)
expected = dict.fromkeys(ids, False)
if scene['id'] != 'general':
expected['stopped_timer'] = state.stopped_duration_s is None
expected['torque_bar'] = not state.lateral_active
expected['speed_limit_actions'] = scene['id'] in ('slc_pending', 'slc_notice') or not slc_controls(profile, state)
if profile == Profile.LARGE:
expected['cem_gauge'] = gauge_data(state) is None
else:
expected['steering_wheel'] = not state.lateral_active
expected['max_speed'] = not state.cruise_active
expected['speed_limit'] = str(state.speed_limit.kind) != 'valid' or compact_sign_obscured_by_actions(state)
expected['driver_monitor'] = not monitor_visible(profile, state, fresh=True, onroad=True,
top_icons=state.cruise_active and layout['max_speed']['enabled'])
assert scene['hidden'] == expected, (target, scene['id'], move)
def test_speed_limit_scenes_change_the_presentation_without_disengaging_driving():
expected = {'slc_max': 'Using set speed', 'slc_equal': 'Using set speed', 'slc_pedal': 'Using accelerator',
'slc_retained': 'Set-speed override', 'slc_disengaged': 'Cruise off',
'slc_unavailable': 'Unavailable', 'slc_display': 'Display only'}
for scene, status in expected.items():
state = sample_state(scene, default_document())
assert state.engaged == (scene != 'slc_disengaged')
assert resolve_unified_speed(state).status == status