- 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
108 lines
3.8 KiB
Python
108 lines
3.8 KiB
Python
"""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
|