feat: implement core functionality for camera preview application
- Add FrameDispatcher for distributing QVideoFrames to subscribers - Implement TelemetryCollector to measure video pipeline performance metrics - Create MainWindow as the main application interface with video rendering - Develop AppMenuBar for camera selection, resolution, and FPS settings - Establish overlay system for displaying telemetry metrics - Set up project structure and configuration files - Add unit tests for FrameDispatcher and TelemetryCollector
This commit is contained in:
76
tests/test_frame_dispatcher.py
Normal file
76
tests/test_frame_dispatcher.py
Normal file
@@ -0,0 +1,76 @@
|
||||
"""Tests for FrameDispatcher."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from app.pipeline.frame_dispatcher import FrameDispatcher
|
||||
|
||||
|
||||
def _make_frame():
|
||||
frame = MagicMock()
|
||||
frame.isValid.return_value = True
|
||||
return frame
|
||||
|
||||
|
||||
class TestFrameDispatcher:
|
||||
def setup_method(self):
|
||||
# FrameDispatcher is a QObject — needs QApplication
|
||||
# Use minimal mock to avoid Qt dependency in unit tests
|
||||
with patch("app.pipeline.frame_dispatcher.QObject.__init__", return_value=None):
|
||||
self.dispatcher = FrameDispatcher.__new__(FrameDispatcher)
|
||||
self.dispatcher._subscribers = []
|
||||
self.dispatcher._frame_count = 0
|
||||
self.dispatcher._last_dispatch_time = 0.0
|
||||
|
||||
def test_subscribe_adds_subscriber(self):
|
||||
cb = MagicMock()
|
||||
self.dispatcher.subscribe(cb)
|
||||
assert self.dispatcher.subscriber_count() == 1
|
||||
|
||||
def test_subscribe_same_callback_twice_is_noop(self):
|
||||
cb = MagicMock()
|
||||
self.dispatcher.subscribe(cb)
|
||||
self.dispatcher.subscribe(cb)
|
||||
assert self.dispatcher.subscriber_count() == 1
|
||||
|
||||
def test_unsubscribe_removes_subscriber(self):
|
||||
cb = MagicMock()
|
||||
self.dispatcher.subscribe(cb)
|
||||
self.dispatcher.unsubscribe(cb)
|
||||
assert self.dispatcher.subscriber_count() == 0
|
||||
|
||||
def test_unsubscribe_nonexistent_does_not_raise(self):
|
||||
cb = MagicMock()
|
||||
self.dispatcher.unsubscribe(cb) # should not raise
|
||||
|
||||
def test_dispatch_calls_all_subscribers(self):
|
||||
cb1 = MagicMock()
|
||||
cb2 = MagicMock()
|
||||
self.dispatcher.subscribe(cb1)
|
||||
self.dispatcher.subscribe(cb2)
|
||||
frame = _make_frame()
|
||||
self.dispatcher.dispatch(frame)
|
||||
cb1.assert_called_once_with(frame)
|
||||
cb2.assert_called_once_with(frame)
|
||||
|
||||
def test_dispatch_increments_frame_count(self):
|
||||
frame = _make_frame()
|
||||
self.dispatcher.dispatch(frame)
|
||||
self.dispatcher.dispatch(frame)
|
||||
assert self.dispatcher.frame_count == 2
|
||||
|
||||
def test_dispatch_with_no_subscribers_does_not_raise(self):
|
||||
frame = _make_frame()
|
||||
self.dispatcher.dispatch(frame) # should not raise
|
||||
|
||||
def test_subscriber_exception_does_not_stop_others(self):
|
||||
def bad_cb(f):
|
||||
raise RuntimeError("boom")
|
||||
|
||||
good_cb = MagicMock()
|
||||
self.dispatcher.subscribe(bad_cb)
|
||||
self.dispatcher.subscribe(good_cb)
|
||||
frame = _make_frame()
|
||||
self.dispatcher.dispatch(frame) # should not raise
|
||||
good_cb.assert_called_once_with(frame)
|
||||
Reference in New Issue
Block a user