""" Tests for data/latency.py — latency tracking. """ import time from data.latency import LatencyTracker def test_empty_stats(): lt = LatencyTracker() s = lt.stats() for key in ("transport_ms", "signal_ms", "order_ms", "roundtrip_ms"): assert s[key]["count"] == 0 def test_record_transport(): lt = LatencyTracker() lt.record_transport(1705312800000, 1705312800.100) # 100ms s = lt.stats() assert s["transport_ms"]["p50"] == 100.0 assert s["transport_ms"]["count"] == 1 def test_record_signal(): lt = LatencyTracker() lt.record_signal(5.2) lt.record_signal(3.1) s = lt.stats() assert s["signal_ms"]["p50"] == 4.15 # interpolated median of [3.1, 5.2] assert s["signal_ms"]["count"] == 2 def test_record_order(): lt = LatencyTracker() lt.record_order(12.5) s = lt.stats() assert s["order_ms"]["p50"] == 12.5 def test_record_roundtrip(): lt = LatencyTracker() lt.record_roundtrip(150.0) s = lt.stats() assert s["roundtrip_ms"]["p50"] == 150.0 def test_negative_latency_ignored(): lt = LatencyTracker() lt.record_transport(1705312800100, 1705312800.000) # local before exchange s = lt.stats() assert s["transport_ms"]["count"] == 0 def test_summary_compact(): lt = LatencyTracker() lt.record_transport(1705312800000, 1705312800.100) lt.record_signal(5.0) summary = lt.summary() assert "transport_ms" in summary assert "signal_ms" in summary assert "p50" in summary["transport_ms"] def test_percentiles_multiple_values(): lt = LatencyTracker() for lat in [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]: lt.record_signal(float(lat)) s = lt.stats() assert s["signal_ms"]["p50"] == 55.0 # interpolated median of 10 values assert s["signal_ms"]["p90"] == 91.0 # (10-1)*90/100=8.1, lo=8, vals[8]=90, vals[9]=100, frac=0.1, result=91 def test_window_pruning_approx(): """Old records should be pruned after window expires.""" lt = LatencyTracker(window_seconds=0.1) lt.record_signal(10.0) time.sleep(0.15) lt.record_signal(20.0) s = lt.stats() assert s["signal_ms"]["p50"] == 20.0 # only the recent one