154 lines
5.0 KiB
Python
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
|