"""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, cpu_count: int = 8): """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._target_fps = None col._cpu_count = cpu_count col._process = MagicMock() # Simulate Windows: wset takes priority over rss mem_info = MagicMock() mem_info.wset = 50 * 1024 * 1024 # 50 MB private working set mem_info.rss = 70 * 1024 * 1024 # RSS (larger, includes shared) col._process.memory_info.return_value = mem_info col._process.cpu_percent.return_value = 0.0 # Inference stats — None by default (inference disabled) col._inference_device = None col._inference_time_ms = None 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() 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() 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() 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) col._last_frame_time = now with patch("app.telemetry.telemetry_collector.time") as mock_time: col._last_frame_time = now big_delta = normal_interval * 5 # 5× average → drop mock_time.perf_counter.return_value = now + big_delta 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 def test_cpu_sys_is_core_divided_by_cpu_count(self): col = self._make_collector(cpu_count=8) col._process.cpu_percent.return_value = 80.0 # 80% of one core snap = col._compute_snapshot() assert snap.cpu_percent_core == 80.0 assert snap.cpu_percent_sys == round(80.0 / 8, 1) def test_cpu_sys_never_exceeds_100_on_single_core_machine(self): col = self._make_collector(cpu_count=1) col._process.cpu_percent.return_value = 95.0 snap = col._compute_snapshot() assert snap.cpu_percent_sys == snap.cpu_percent_core == 95.0 def test_cpu_sys_le_cpu_core(self): """cpu_percent_sys must always be <= cpu_percent_core.""" col = self._make_collector(cpu_count=4) col._process.cpu_percent.return_value = 150.0 snap = col._compute_snapshot() assert snap.cpu_percent_sys <= snap.cpu_percent_core def test_target_fps_none_by_default(self): col = self._make_collector() snap = col._compute_snapshot() assert snap.target_fps is None def test_set_target_fps_reflected_in_snapshot(self): col = self._make_collector() col.set_target_fps(60.0) snap = col._compute_snapshot() assert snap.target_fps == 60.0