mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-09-26 15:17:54 +00:00
Skip redundant modulation writes during owned fast RX retunes when the last acknowledged SF/BW/CR/LDRO tuple matches. Invalidate that cache after ordinary setters, failed writes, and lifecycle changes. Include the remaining scan, preamble, settling, and memory-soak experiments, their collectors, validation notes, and original capture records. Preserve capture bytes across checkouts and keep private soak credentials local. Run lab collector and compiled contract tests in CI. Update the expectation, profile mapping, and result-buffer tests for the extended lab tools, and make the private WiFi override header optional for ordinary soak diagnostics. Validation: 145 host tests passed from the staged source snapshot. Clean heltec_v4_repeater and Xiao_S3_WIO_companion_radio_usb builds passed their RAM/flash gates. All 229 staged capture files retain their original bytes; all 75 local documentation links resolve in the clean snapshot.
130 lines
6.9 KiB
Python
130 lines
6.9 KiB
Python
"""Controller invariants for stop-on-first-miss channel-capacity experiments."""
|
|
import unittest
|
|
from unittest.mock import patch
|
|
from profile_switch_channels import capacity_levels, collect_trace, channel_dwell_us, reception_validity, packet_error_summary
|
|
|
|
|
|
class CapacityControllerTests(unittest.TestCase):
|
|
def test_fixed_sample_finishes_all_400_without_restart_or_retries(self):
|
|
starts=[];calls=[];finishes=[]
|
|
def probe(ch,pause):
|
|
calls.append(ch)
|
|
received=dict(valid=False,timeout=True)
|
|
if ch==0: received=dict(valid=True,len=16,channel=ch)
|
|
if ch==1: received=dict(valid=False,payload_valid=True,len=16,channel=0,payload_channel=ch,expected_channel=ch)
|
|
return dict(sequence=len(calls),valid=ch==0,received=received)
|
|
result=capacity_levels(lambda n:starts.append(n),probe,lambda:finishes.append(1),lambda _:None,
|
|
samples=100,maximum=4,continue_on_miss=True)
|
|
self.assertEqual(starts,[4]);self.assertEqual(finishes,[1]);self.assertEqual(len(calls),400)
|
|
self.assertEqual([calls.count(ch) for ch in range(4)],[100]*4)
|
|
self.assertEqual(result['stopped'],'fixed_sample_complete')
|
|
self.assertIsNone(result['last_perfect_channels'])
|
|
level=result['levels'][0]
|
|
self.assertTrue(level['complete'])
|
|
summary=level['error_rates']['overall']
|
|
self.assertEqual((summary['strict_received'],summary['offchannel_received'],summary['timeouts']),(100,100,200))
|
|
self.assertEqual((summary['strict_error_percent'],summary['delivery_error_percent']),(75,50))
|
|
with self.assertRaises(ValueError):
|
|
capacity_levels(None,None,None,None,maximum=5,continue_on_miss=True)
|
|
|
|
def test_fixed_sample_does_not_treat_fixture_failure_as_rf_loss(self):
|
|
calls=[]
|
|
def probe(*_):
|
|
calls.append(1);raise RuntimeError('hardware failure')
|
|
with self.assertRaises(RuntimeError):
|
|
capacity_levels(lambda _:None,probe,lambda:None,lambda _:None,maximum=4,continue_on_miss=True)
|
|
self.assertEqual(len(calls),1)
|
|
|
|
def test_delivery_diagnostic_does_not_relabel_strict_validity(self):
|
|
wrong=dict(valid=False,payload_valid=True,len=16,channel=2,payload_channel=3,expected_channel=3)
|
|
self.assertEqual(reception_validity(wrong,3),(False,False))
|
|
self.assertEqual(reception_validity(wrong,3,True),(False,True))
|
|
for key,value in dict(payload_valid=False,len=15,payload_channel=2,expected_channel=2).items():
|
|
self.assertEqual(reception_validity(dict(wrong,**{key:value}),3,True),(False,False))
|
|
self.assertEqual(reception_validity(dict(valid=False,timeout=True),3,True),(False,False))
|
|
|
|
def test_sf8_dwell_is_four_times_sf6_before_rounding(self):
|
|
self.assertEqual(channel_dwell_us(6, 4.8), 2458)
|
|
self.assertEqual(channel_dwell_us(6, 5.1), 2612)
|
|
self.assertEqual(channel_dwell_us(8, 5.1), 10445)
|
|
self.assertEqual(channel_dwell_us(8, 32), 65536)
|
|
for sf, dwell in ((11, 5.1), (8, float("nan")), (8, float("inf")), (6, 3.9)):
|
|
with self.assertRaises(ValueError):
|
|
channel_dwell_us(sf, dwell)
|
|
|
|
def test_sf10_to_sf5_dwell_grid(self):
|
|
self.assertEqual([channel_dwell_us(sf, 5.1) for sf in range(10,4,-1)],
|
|
[41780,20890,10445,5223,2612,1306])
|
|
|
|
def test_arrival_phase_range_includes_added_settling(self):
|
|
pauses=[]
|
|
capacity_levels(lambda _: {}, lambda ch,pause: pauses.append(pause) or dict(valid=True),
|
|
lambda: {}, lambda _: None, samples=100, maximum=4,
|
|
dwell_us=41780, settle_us=23300)
|
|
self.assertGreater(max(pauses), 2*4*(41.780+.7))
|
|
self.assertLessEqual(max(pauses), 2*4*(41.780+23.300+.7))
|
|
|
|
def test_sf5_250_dwell_grid_and_bandwidth_validation(self):
|
|
self.assertEqual([channel_dwell_us(5, t / 10, 250) for t in range(41, 82, 5)],
|
|
[525, 589, 653, 717, 781, 845, 909, 973, 1037])
|
|
with self.assertRaises(ValueError):
|
|
channel_dwell_us(5, 4.1, 500)
|
|
|
|
def test_starts_at_four_balances_samples_then_stops_without_another_tx(self):
|
|
starts, probes = [], []
|
|
def start(n):
|
|
starts.append(n)
|
|
return {"channels": n}
|
|
def probe(channel, pause):
|
|
probes.append((starts[-1], channel, pause))
|
|
return {"sequence": len(probes), "valid": len(probes) != 10}
|
|
result = capacity_levels(start, probe, lambda: {}, lambda _: None, samples=2, maximum=8)
|
|
self.assertEqual(starts, [4, 5])
|
|
self.assertEqual(len(probes), 10)
|
|
self.assertEqual(result["last_perfect_channels"], 4)
|
|
self.assertEqual(result["first_failed_channels"], 5)
|
|
self.assertTrue(result["complete"])
|
|
self.assertEqual(result["levels"][0]["per_channel"], [{"attempted": 2, "received": 2}] * 4)
|
|
self.assertEqual(result["levels"][1]["attempted"], 2)
|
|
self.assertTrue(all(pause >= 0 for _, _, pause in probes))
|
|
|
|
def test_first_packet_miss_at_four_does_not_try_fewer_or_more_channels(self):
|
|
starts = []
|
|
result = capacity_levels(lambda n: starts.append(n),
|
|
lambda *_: {"sequence": 1, "valid": False},
|
|
lambda: {}, lambda _: None)
|
|
self.assertEqual(starts, [4])
|
|
self.assertIsNone(result["last_perfect_channels"])
|
|
self.assertEqual(result["first_failed_channels"], 4)
|
|
|
|
def test_fixture_failure_does_not_expand_or_become_an_rf_miss(self):
|
|
starts = []
|
|
def probe(*_):
|
|
raise TimeoutError("USB result unavailable")
|
|
with self.assertRaises(TimeoutError):
|
|
capacity_levels(lambda n: starts.append(n), probe, lambda: {}, lambda _: None)
|
|
self.assertEqual(starts, [4])
|
|
|
|
def test_fixed_four_channel_diagnostic_uses_requested_dwell(self):
|
|
pauses = []
|
|
result = capacity_levels(lambda n: n,
|
|
lambda ch, pause: pauses.append(pause) or {"sequence": 1, "valid": True},
|
|
lambda: {}, lambda _: None, samples=1, maximum=4, dwell_us=2612)
|
|
self.assertEqual(result["last_perfect_channels"], 4)
|
|
self.assertEqual(len(pauses), 4)
|
|
self.assertTrue(all(0 <= p <= 2 * 4 * (2.612 + 0.7) for p in pauses))
|
|
|
|
def test_collect_trace_checks_page_identity(self):
|
|
common = {"result": 42, "trace": 42, "total": 2, "origin_us": 1000}
|
|
with patch("profile_switch_channels.exchange", side_effect=[
|
|
dict(common, offset=0, next=1, events=[[0, 1, 0, 0, 3, 0]]),
|
|
dict(common, offset=1, next=2, events=[[100, 2, 0, 4, 0, 0]])]):
|
|
self.assertEqual(len(collect_trace(None, 42)["events"]), 2)
|
|
with patch("profile_switch_channels.exchange", return_value=dict(common, offset=1, next=1, events=[])):
|
|
with self.assertRaises(RuntimeError):
|
|
collect_trace(None, 42)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|