mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 13:33:42 +08:00
a82935f326
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>
124 lines
6.7 KiB
Python
124 lines
6.7 KiB
Python
"""Native API imports with synthetic allocations for failure/ownership checks."""
|
|
|
|
from contextlib import ExitStack
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
from tempfile import TemporaryDirectory
|
|
from types import SimpleNamespace
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
import pyray as rl
|
|
|
|
from openpilot.starpilot.ui.settings_assets import SettingsAssets
|
|
|
|
MODULE = "openpilot.starpilot.ui.settings_assets"
|
|
|
|
|
|
class TestSettingsResources(unittest.TestCase):
|
|
def test_galaxy_menu_icon_is_the_approved_transparent_white_artwork(self):
|
|
root = Path(__file__).parents[3] / "selfdrive/assets"
|
|
icon = root / "icons_mici/settings/galaxy.png"
|
|
record = next(item for item in json.loads(Path(__file__).parents[1].joinpath("settings-assets.json").read_text())["files"]
|
|
if item["file"] == "icons_mici/settings/galaxy.png")
|
|
self.assertEqual(icon.stat().st_size, record["bytes"])
|
|
with icon.open("rb") as stream:
|
|
header = stream.read(24)
|
|
self.assertEqual(header[:8], b"\x89PNG\r\n\x1a\n")
|
|
self.assertEqual(tuple(int.from_bytes(header[offset:offset + 4], "big") for offset in (16, 20)), (256, 195))
|
|
self.assertEqual(hashlib.sha256(icon.read_bytes()).hexdigest(),
|
|
"817d30f1d18084ffa81567c70a1797982b7cdc404aa078a231484f50fc113dd6")
|
|
self.assertEqual(record["sha256"], hashlib.sha256(icon.read_bytes()).hexdigest())
|
|
|
|
def test_menu_images_keep_native_proportions_at_both_pixel_scales(self):
|
|
for native, bounds, expected in (((256, 195), (64, 64), (64, 49)), ((128, 68), (72, 56), (72, 38))):
|
|
for pixel_scale in (1, 2):
|
|
with self.subTest(native=native, pixel_scale=pixel_scale), TemporaryDirectory() as directory, ExitStack() as stack:
|
|
path = Path(directory) / "fixture.png"
|
|
path.write_bytes(b"fixture")
|
|
assets = SettingsAssets(Path(directory))
|
|
assets._pixel_scale = (pixel_scale, pixel_scale)
|
|
assets._manifest = {"fixture.png": {"bytes": 7, "sha256": hashlib.sha256(b"fixture").hexdigest()}}
|
|
image = SimpleNamespace(data=rl.ffi.new("char[4]"), width=native[0], height=native[1])
|
|
texture = SimpleNamespace(id=17, width=0, height=0)
|
|
stack.enter_context(patch(f"{MODULE}.rl.load_image", return_value=image))
|
|
stack.enter_context(patch(f"{MODULE}.rl.image_resize"))
|
|
stack.enter_context(patch(f"{MODULE}.rl.load_texture_from_image", return_value=texture))
|
|
stack.enter_context(patch(f"{MODULE}.rl.set_texture_filter"))
|
|
stack.enter_context(patch(f"{MODULE}.rl.set_texture_wrap"))
|
|
release = stack.enter_context(patch(f"{MODULE}.rl.unload_image"))
|
|
self.assertIs(assets.image("fixture.png", *bounds), texture)
|
|
self.assertEqual((texture.width, texture.height), expected)
|
|
self.assertIs(assets.image("fixture.png", *bounds), texture)
|
|
release.assert_called_once()
|
|
|
|
def test_unreviewed_or_changed_bytes_are_rejected_before_native_decode(self):
|
|
with TemporaryDirectory() as directory, patch(f"{MODULE}.rl.load_image") as load:
|
|
assets = SettingsAssets(Path(directory))
|
|
with self.assertRaises(ValueError):
|
|
assets.image("../../outside.png", 10, 10)
|
|
image = Path(directory) / "icons/backspace.png"
|
|
image.parent.mkdir()
|
|
image.write_bytes(b"wrong bytes")
|
|
with self.assertRaisesRegex(ValueError, "reviewed manifest"):
|
|
assets.image("icons/backspace.png", 70, 70)
|
|
load.assert_not_called()
|
|
|
|
def test_failed_upload_and_filter_release_only_owned_allocations(self):
|
|
for failure in ("empty_image", "gpu_upload", "filter"):
|
|
with self.subTest(failure=failure), TemporaryDirectory() as directory, ExitStack() as stack:
|
|
path = Path(directory) / "fixture.png"
|
|
path.write_bytes(b"reviewed-fixture")
|
|
assets = SettingsAssets(Path(directory))
|
|
assets._manifest = {"fixture.png": {"bytes": path.stat().st_size, "sha256": hashlib.sha256(path.read_bytes()).hexdigest()}}
|
|
image = SimpleNamespace(data=rl.ffi.NULL if failure == "empty_image" else rl.ffi.new("char[4]"), width=1, height=1)
|
|
texture = SimpleNamespace(id=0 if failure == "gpu_upload" else 17)
|
|
stack.enter_context(patch(f"{MODULE}.rl.load_image", return_value=image))
|
|
upload = stack.enter_context(patch(f"{MODULE}.rl.load_texture_from_image", return_value=texture))
|
|
free_image = stack.enter_context(patch(f"{MODULE}.rl.unload_image"))
|
|
free_texture = stack.enter_context(patch(f"{MODULE}.rl.unload_texture"))
|
|
stack.enter_context(patch(f"{MODULE}.rl.set_texture_filter", side_effect=RuntimeError("filter failed")))
|
|
with self.assertRaises((ValueError, RuntimeError)):
|
|
assets.image("fixture.png", 1, 1)
|
|
self.assertEqual(assets._images, {})
|
|
self.assertEqual(free_image.call_count, failure != "empty_image")
|
|
self.assertEqual(free_texture.call_count, failure == "filter")
|
|
if failure == "empty_image":
|
|
upload.assert_not_called()
|
|
|
|
def test_vector_render_failure_unwinds_modes_and_owned_target(self):
|
|
assets = SettingsAssets(Path("unused"))
|
|
target = SimpleNamespace(id=20, texture=SimpleNamespace(id=21))
|
|
with ExitStack() as stack:
|
|
stack.enter_context(patch(f"{MODULE}.rl.load_render_texture", return_value=target))
|
|
release = stack.enter_context(patch(f"{MODULE}.rl.unload_render_texture"))
|
|
calls = {}
|
|
for name in ("begin_texture_mode", "clear_background", "rl_set_blend_factors_separate", "begin_blend_mode",
|
|
"end_blend_mode", "end_texture_mode"):
|
|
calls[name] = stack.enter_context(patch(f"{MODULE}.rl.{name}"))
|
|
stack.enter_context(patch(f"{MODULE}.draw_icon_geometry", side_effect=RuntimeError("draw failed")))
|
|
with self.assertRaisesRegex(RuntimeError, "draw failed"):
|
|
assets.prepare_icon("sound", 1, rl.Color(255, 255, 255, 255))
|
|
release.assert_called_once_with(target)
|
|
calls["end_blend_mode"].assert_called_once()
|
|
calls["end_texture_mode"].assert_called_once()
|
|
self.assertEqual(assets._icons, {})
|
|
|
|
def test_repeated_close_releases_each_owned_image_and_vector_target_once(self):
|
|
assets = SettingsAssets(Path("unused"))
|
|
image, target = SimpleNamespace(id=17), SimpleNamespace(id=20)
|
|
assets._images[("fixture", 1, 1)], assets._icons[("vector",)] = image, target
|
|
with patch(f"{MODULE}.rl.is_window_ready", return_value=True), \
|
|
patch(f"{MODULE}.rl.unload_texture") as free_image, patch(f"{MODULE}.rl.unload_render_texture") as free_target:
|
|
assets.close()
|
|
assets.close()
|
|
free_image.assert_called_once_with(image)
|
|
free_target.assert_called_once_with(target)
|
|
self.assertEqual(assets._images, {})
|
|
self.assertEqual(assets._icons, {})
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|