radicle-reticulum/tests/test_adaptive.py

154 lines
5.0 KiB
Python

"""Tests for adaptive sync strategy selection."""
import pytest
from radicle_reticulum.adaptive import (
SyncStrategy,
LinkQuality,
estimate_transfer_time,
select_strategy,
THRESHOLD_FULL,
THRESHOLD_INCREMENTAL,
RTT_FAST,
RTT_MEDIUM,
RTT_SLOW,
QR_MAX_BYTES,
)
def make_quality(
rtt_ms: float = 50,
throughput_bps: float = 1_000_000,
is_lora: bool = False,
strategy: SyncStrategy = SyncStrategy.FULL,
) -> LinkQuality:
return LinkQuality(
rtt_ms=rtt_ms,
throughput_bps=throughput_bps,
packet_loss=0.0,
is_lora=is_lora,
strategy=strategy,
)
class TestLinkQuality:
def test_throughput_kbps(self):
q = make_quality(throughput_bps=50_000)
assert q.throughput_kbps == pytest.approx(50.0)
def test_repr(self):
q = make_quality(rtt_ms=100, throughput_bps=1000)
r = repr(q)
assert "100ms" in r
assert "1.0Kbps" in r
class TestEstimateTransferTime:
def test_zero_throughput_is_infinite(self):
q = make_quality(throughput_bps=0)
assert estimate_transfer_time(1024, q) == float("inf")
def test_known_value(self):
# 1 MB at 1 Mbps effective = 8s, but 80% efficiency → 10s
q = make_quality(throughput_bps=1_000_000)
t = estimate_transfer_time(1_000_000, q)
assert t == pytest.approx(10.0, rel=0.01)
def test_larger_file_takes_longer(self):
q = make_quality(throughput_bps=10_000)
t_small = estimate_transfer_time(1_000, q)
t_large = estimate_transfer_time(100_000, q)
assert t_large > t_small
class TestSelectStrategy:
def test_fast_link_small_repo_uses_full(self):
q = make_quality(rtt_ms=10, throughput_bps=10_000_000, strategy=SyncStrategy.FULL)
strategy, reason = select_strategy(
bundle_size=100_000,
incremental_size=None,
quality=q,
)
assert strategy == SyncStrategy.FULL
assert "full" in reason.lower() or "fast" in reason.lower()
def test_lora_link_prefers_incremental_when_viable(self):
q = make_quality(rtt_ms=5000, throughput_bps=2400, is_lora=True, strategy=SyncStrategy.MINIMAL)
strategy, reason = select_strategy(
bundle_size=500_000,
incremental_size=5_000,
quality=q,
max_transfer_time=3600,
)
assert strategy == SyncStrategy.INCREMENTAL
def test_lora_link_falls_back_to_minimal_when_too_large(self):
q = make_quality(rtt_ms=5000, throughput_bps=100, is_lora=True, strategy=SyncStrategy.MINIMAL)
strategy, reason = select_strategy(
bundle_size=10_000_000,
incremental_size=5_000_000,
quality=q,
max_transfer_time=3600,
)
assert strategy == SyncStrategy.MINIMAL
def test_tiny_incremental_uses_qr(self):
q = make_quality(rtt_ms=10, throughput_bps=10_000_000, strategy=SyncStrategy.FULL)
strategy, reason = select_strategy(
bundle_size=50_000,
incremental_size=QR_MAX_BYTES - 1,
quality=q,
)
assert strategy == SyncStrategy.QR
assert str(QR_MAX_BYTES - 1) in reason
def test_qr_not_selected_when_incremental_too_large(self):
q = make_quality(rtt_ms=10, throughput_bps=10_000_000, strategy=SyncStrategy.FULL)
strategy, _ = select_strategy(
bundle_size=50_000,
incremental_size=QR_MAX_BYTES + 1,
quality=q,
)
assert strategy != SyncStrategy.QR
def test_medium_link_prefers_incremental_over_full(self):
q = make_quality(
rtt_ms=500,
throughput_bps=50_000,
strategy=SyncStrategy.INCREMENTAL,
)
strategy, _ = select_strategy(
bundle_size=5_000_000,
incremental_size=50_000,
quality=q,
max_transfer_time=3600,
)
assert strategy == SyncStrategy.INCREMENTAL
def test_no_incremental_available_falls_back_to_full_or_minimal(self):
q = make_quality(rtt_ms=50, throughput_bps=10_000_000, strategy=SyncStrategy.FULL)
strategy, _ = select_strategy(
bundle_size=100_000,
incremental_size=None,
quality=q,
)
assert strategy in (SyncStrategy.FULL, SyncStrategy.INCREMENTAL)
def test_unreachably_slow_link_uses_minimal(self):
q = make_quality(rtt_ms=30000, throughput_bps=10, strategy=SyncStrategy.MINIMAL)
strategy, _ = select_strategy(
bundle_size=10_000_000,
incremental_size=None,
quality=q,
max_transfer_time=3600,
)
assert strategy == SyncStrategy.MINIMAL
def test_fast_link_large_repo_prefers_incremental_if_faster(self):
q = make_quality(rtt_ms=10, throughput_bps=500_000, strategy=SyncStrategy.FULL)
strategy, _ = select_strategy(
bundle_size=100_000_000,
incremental_size=100_000,
quality=q,
)
assert strategy == SyncStrategy.INCREMENTAL