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:
107
tests/test_telemetry_collector.py
Normal file
107
tests/test_telemetry_collector.py
Normal file
@@ -0,0 +1,107 @@
|
||||
"""Tests for TelemetryCollector."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from collections import deque
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
|
||||
class TestTelemetryCollector:
|
||||
"""Test telemetry calculations in isolation (no Qt event loop required)."""
|
||||
|
||||
def _make_collector(self):
|
||||
"""Construct a TelemetryCollector bypassing Qt machinery."""
|
||||
from app.telemetry.telemetry_collector import TelemetryCollector
|
||||
|
||||
with patch.object(TelemetryCollector, "__init__", return_value=None):
|
||||
col = TelemetryCollector.__new__(TelemetryCollector)
|
||||
|
||||
col._frame_times = deque(maxlen=120)
|
||||
col._last_frame_time = 0.0
|
||||
col._total_frames = 0
|
||||
col._dropped_frames = 0
|
||||
col._fps_window = deque()
|
||||
col._fps_window_size_s = 1.0
|
||||
col._process = MagicMock()
|
||||
col._process.memory_info.return_value.rss = 50 * 1024 * 1024 # 50 MB
|
||||
return col
|
||||
|
||||
def test_initial_snapshot_has_zero_fps(self):
|
||||
col = self._make_collector()
|
||||
snap = col._compute_snapshot()
|
||||
assert snap.fps == 0.0
|
||||
|
||||
def test_fps_counts_frames_in_window(self):
|
||||
col = self._make_collector()
|
||||
now = time.perf_counter()
|
||||
# Simulate 30 frames within the last second
|
||||
for i in range(30):
|
||||
col._fps_window.append(now - 0.9 + i * 0.03)
|
||||
snap = col._compute_snapshot()
|
||||
assert snap.fps == 30.0
|
||||
|
||||
def test_fps_excludes_old_frames(self):
|
||||
col = self._make_collector()
|
||||
now = time.perf_counter()
|
||||
# 10 frames older than 1 second — should not count
|
||||
for i in range(10):
|
||||
col._fps_window.append(now - 2.0 + i * 0.05)
|
||||
# 5 frames within the last second
|
||||
for i in range(5):
|
||||
col._fps_window.append(now - 0.4 + i * 0.05)
|
||||
snap = col._compute_snapshot()
|
||||
assert snap.fps == 5.0
|
||||
|
||||
def test_frame_time_average(self):
|
||||
col = self._make_collector()
|
||||
# 10 frames at 33.3 ms each
|
||||
interval = 0.0333
|
||||
for _ in range(10):
|
||||
col._frame_times.append(interval)
|
||||
snap = col._compute_snapshot()
|
||||
assert abs(snap.frame_time_ms - interval * 1000) < 0.1
|
||||
|
||||
def test_drop_detection(self):
|
||||
col = self._make_collector()
|
||||
# Seed with 10 normal frames at ~16 ms
|
||||
normal_interval = 0.016
|
||||
now = time.perf_counter()
|
||||
col._last_frame_time = now - normal_interval * 10
|
||||
for _ in range(9):
|
||||
col._last_frame_time += normal_interval
|
||||
col._frame_times.append(normal_interval)
|
||||
|
||||
# Simulate a big gap (3× normal) — should trigger drop detection
|
||||
col._last_frame_time = now - normal_interval * 10 # reset base
|
||||
# Manually call on_frame-like logic
|
||||
with patch("app.telemetry.telemetry_collector.time") as mock_time:
|
||||
# Set last_frame_time to something reasonable
|
||||
col._last_frame_time = now
|
||||
big_delta = normal_interval * 5 # 5× average → drop
|
||||
mock_time.perf_counter.return_value = now + big_delta
|
||||
# Replicate the drop detection logic
|
||||
delta = big_delta
|
||||
col._frame_times.append(delta)
|
||||
avg = sum(col._frame_times) / len(col._frame_times)
|
||||
if delta > avg * 2.5:
|
||||
col._dropped_frames += 1
|
||||
|
||||
assert col._dropped_frames == 1
|
||||
|
||||
def test_reset_counters(self):
|
||||
col = self._make_collector()
|
||||
col._total_frames = 100
|
||||
col._dropped_frames = 5
|
||||
col._frame_times.append(0.016)
|
||||
col._fps_window.append(time.perf_counter())
|
||||
col.reset_counters()
|
||||
assert col._total_frames == 0
|
||||
assert col._dropped_frames == 0
|
||||
assert len(col._frame_times) == 0
|
||||
assert len(col._fps_window) == 0
|
||||
|
||||
def test_snapshot_memory_mb(self):
|
||||
col = self._make_collector()
|
||||
snap = col._compute_snapshot()
|
||||
assert snap.memory_mb == 50.0
|
||||
Reference in New Issue
Block a user