Files
StarPilot/selfdrive/ui/mici/tests/test_camera_cleanup.py
T
firestar5683 d89371c518 WUMBO
2026-08-02 12:20:20 -05:00

237 lines
7.5 KiB
Python

import gc
from types import SimpleNamespace
import weakref
import pytest
from openpilot.selfdrive.ui.onroad import cameraview as big_cameraview
from openpilot.selfdrive.ui.onroad import augmented_road_view as big_augmented_road_view
from openpilot.selfdrive.ui.mici.onroad import augmented_road_view as mici_augmented_road_view
def test_road_transition_releases_camera_buffers(monkeypatch):
module = big_cameraview
class FakeClient:
pass
view = module.CameraView.__new__(module.CameraView)
old_client = FakeClient()
old_client_ref = weakref.ref(old_client)
view._name = "camerad"
view._stream_type = object()
view.client = old_client
view.frame = None
view.available_streams = [object()]
view._target_client = FakeClient()
view._target_stream_type = object()
view._switching = True
view._texture_needs_update = False
view._regressive_frame_count = 2
view.last_connection_attempt = 123.0
view._closed = True
cleared = []
view._clear_textures = lambda: cleared.append(True)
del old_client
view._offroad_transition()
gc.collect()
assert old_client_ref() is None
assert cleared == [True]
assert view.frame is None
assert view.available_streams == []
assert view._target_client is None
assert view._target_stream_type is None
assert view._switching is False
assert view._texture_needs_update
assert view._regressive_frame_count == 0
assert view.last_connection_attempt == 0.0
def test_transition_callback_does_not_retain_camera_view(monkeypatch):
module = big_cameraview
class FakeClient:
pass
callbacks = []
monkeypatch.setattr(module, "TICI", False)
monkeypatch.setattr(module, "VisionIpcClient", lambda *_args, **_kwargs: FakeClient())
monkeypatch.setattr(module.rl, "load_shader_from_memory", lambda *_args: SimpleNamespace(id=1))
monkeypatch.setattr(module.rl, "get_shader_location", lambda *_args: 0)
monkeypatch.setattr(module.rl, "unload_shader", lambda *_args: None)
monkeypatch.setattr(module.ui_state, "add_offroad_transition_callback", callbacks.append)
monkeypatch.setattr(module.ui_state, "remove_offroad_transition_callback", callbacks.remove)
view = module.CameraView("camerad", object())
view_ref = weakref.ref(view)
assert len(callbacks) == 1
del view
gc.collect()
assert view_ref() is None
assert callbacks == []
def test_stream_switch_releases_graphics_before_old_client():
module = big_cameraview
events = []
class FakeClient:
pass
class FakeFrame:
pass
view = module.CameraView.__new__(module.CameraView)
old_client = FakeClient()
old_client_finalizer = weakref.finalize(old_client, events.append, "client")
old_frame = FakeFrame()
old_frame.frame_id = 10
old_frame.owner = old_client
old_frame_finalizer = weakref.finalize(old_frame, events.append, "frame")
view.client = old_client
view._target_client = FakeClient()
view._target_stream_type = object()
view._stream_type = object()
view._switching = True
view.frame = old_frame
view._regressive_frame_count = 2
view._texture_needs_update = False
view._closed = True
view._clear_textures = lambda: events.append("graphics")
view._initialize_textures = lambda: events.append("initialize")
del old_frame
del old_client
view._complete_switch(SimpleNamespace(frame_id=11))
gc.collect()
assert old_client_finalizer.alive is False
assert old_frame_finalizer.alive is False
assert events == ["graphics", "frame", "client", "initialize"]
assert view._regressive_frame_count == 0
def test_egl_cleanup_deletes_texture_before_images(monkeypatch):
module = big_cameraview
events = []
view = module.CameraView.__new__(module.CameraView)
view.texture_y = None
view.texture_uv = None
view.egl_texture = SimpleNamespace(id=7)
view._external_texture_id = 11
view.egl_images = {0: object(), 1: object()}
view._closed = True
view._use_egl = True
monkeypatch.setattr(module.rl, "unload_texture", lambda _texture: events.append("texture"))
monkeypatch.setattr(module, "destroy_external_texture", lambda _texture: events.append("external"))
monkeypatch.setattr(module, "destroy_egl_image", lambda _image: events.append("image"))
view._clear_textures()
assert events == ["external", "texture", "image", "image"]
assert view.egl_texture is None
assert view._external_texture_id == 0
assert view.egl_images == {}
def test_egl_cleanup_synchronizes_after_backend_switch(monkeypatch):
module = big_cameraview
events = []
view = module.CameraView.__new__(module.CameraView)
view.texture_y = None
view.texture_uv = None
view.egl_texture = SimpleNamespace(id=7)
view._external_texture_id = 11
view.egl_images = {0: object()}
view._use_egl = False
view._closed = True
monkeypatch.setattr(module, "is_egl_initialized", lambda: True)
monkeypatch.setattr(module.rl, "rl_draw_render_batch_active", lambda: events.append("flush"))
monkeypatch.setattr(module, "finish_gl", lambda: events.append("finish"))
monkeypatch.setattr(module.rl, "unload_texture", lambda _texture: events.append("texture"))
monkeypatch.setattr(module, "destroy_external_texture", lambda _texture: events.append("external"))
monkeypatch.setattr(module, "destroy_egl_image", lambda _image: events.append("image"))
view._clear_textures()
assert events == ["flush", "finish", "external", "texture", "image"]
def test_reverse_activation_cancels_mismatched_pending_switch():
view = mici_augmented_road_view.AugmentedRoadView.__new__(mici_augmented_road_view.AugmentedRoadView)
view._stream_type = mici_augmented_road_view.DRIVER_CAM
view._target_stream_type = mici_augmented_road_view.WIDE_CAM
view._target_client = object()
view._switching = True
view._closed = True
view._update_reverse_driver_camera_state = lambda: True
view._switch_stream_if_needed(None, mici_augmented_road_view.CAMERA_VIEW_AUTO)
assert view._target_client is None
assert view._target_stream_type is None
assert not view._switching
def test_onroad_reentry_keeps_matching_candidate_alive():
for module in (big_augmented_road_view, mici_augmented_road_view):
view = module.AugmentedRoadView.__new__(module.AugmentedRoadView)
candidate = object()
view._stream_type = module.ROAD_CAM
view._target_stream_type = module.ROAD_CAM
view._target_client = candidate
view._switching = True
view._onroad_reentry_pending = True
view._reentry_stream_selected = True
view.available_streams = [module.ROAD_CAM]
view._closed = True
view._update_reverse_driver_camera_state = lambda: False
view._refresh_available_streams = lambda: pytest.fail("reentry selection was repeated")
view._switch_stream_if_needed(None, module.CAMERA_VIEW_STANDARD)
assert view._target_client is candidate
assert view._target_stream_type == module.ROAD_CAM
assert view._switching
def test_onroad_transition_marks_camera_reentry(monkeypatch):
module = big_cameraview
class FakeClient:
pass
view = module.CameraView.__new__(module.CameraView)
view._name = "camerad"
view._stream_type = object()
view.client = FakeClient()
view.frame = None
view.available_streams = []
view._target_client = None
view._target_stream_type = None
view._switching = False
view._texture_needs_update = False
view._regressive_frame_count = 1
view._closed = True
view._onroad_reentry_pending = False
view._reentry_stream_selected = False
view._clear_textures = lambda: None
monkeypatch.setattr(module.ui_state, "is_onroad", lambda: True)
view._offroad_transition()
assert view._onroad_reentry_pending
assert not view._reentry_stream_selected