Files
duck-preview/tests/test_telemetry_collector.py

113 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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()
# Simulate Windows: wset is present and 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
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