mirror of
https://github.com/openhop-dev/openhop_repeater.git
synced 2026-10-23 21:43:02 +00:00
test(duty-cycle): run the suite against an older openhop_core too
The tests that cover per-channel metering ask a fabric which radio will carry a packet, which an openhop_core from before the selector change cannot answer. Run against one, fourteen of them failed -- on a repeater whose whole point is that the core change is optional. Tests that genuinely need the newer core (they drive build_radio_stack through a real FabricRadio) are skipped when it is absent. The engine tests instead use a fabric double that implements core's resolve rule and nothing else: explicit radio_id wins, then the selector, then default_radio. That keeps the engine measured against the contract rather than against a convenience, and the real RFFabric is still what runs wherever the installed core supports it. One fan-out test asserted the engine names the egress radio. What must not change is which radio transmits; whether the engine names it or leaves the choice to the fabric depends on the core underneath, so it accepts either. Against openhop_core c95a684: 2154 pass, 6 skipped. Against dev with the selector change: 2157 pass, 3 skipped.
This commit is contained in:
1 parent
d6f1a4c73b
commit
42e7939447
3 files changed
+74
-12
No files matched your search
@@ -550,9 +550,11 @@ async def test_local_origin_default_mode_keeps_single_fabric_send():
|
||||
rig = _Rig({"repeat_on_ingress": True}) # origin_tx defaults to "default"
|
||||
assert await rig.handler(_flood(), {}, local_transmission=True) is True
|
||||
assert rig.air.order == ["local"]
|
||||
# Same radio on the air as when the fabric chose at send time; the engine now
|
||||
# names it instead, so the duty cycle can be charged to the right channel.
|
||||
assert rig.sent_radio_ids == ["local"]
|
||||
# Same radio on the air either way. With a core that passes the ingress radio
|
||||
# to the TX policy the engine names it, so the duty cycle can be charged to
|
||||
# the right channel; without one it leaves the choice to the fabric, as it
|
||||
# always did. What must not change is which radio transmits.
|
||||
assert rig.sent_radio_ids in (["local"], [None])
|
||||
assert rig.records()[-1]["tx_radio_ids"] == ["local"]
|
||||
|
||||
|
||||
|
||||
@@ -15,6 +15,18 @@ from repeater.config import (
|
||||
)
|
||||
|
||||
|
||||
def _core_selects_by_ingress() -> bool:
|
||||
from openhop_core.rf_fabric import RFFabric
|
||||
|
||||
return fabric_selects_by_ingress_radio(RFFabric())
|
||||
|
||||
|
||||
needs_ingress_core = pytest.mark.skipif(
|
||||
not _core_selects_by_ingress(),
|
||||
reason="installed openhop_core does not pass a packet's ingress radio to the TX selector",
|
||||
)
|
||||
|
||||
|
||||
class _FakeRadio:
|
||||
def __init__(self, name="r"):
|
||||
self.name = name
|
||||
@@ -126,6 +138,7 @@ def test_build_radio_stack_multi_wraps_fabric():
|
||||
assert radio.fabric._tx_selector is not None
|
||||
|
||||
|
||||
@needs_ingress_core
|
||||
@pytest.mark.asyncio
|
||||
async def test_sticky_tx_uses_the_packets_own_ingress_radio():
|
||||
a = _FakeRadio("a")
|
||||
@@ -194,6 +207,7 @@ def test_tx_mode_all_rejected():
|
||||
build_radio_stack(cfg)
|
||||
|
||||
|
||||
@needs_ingress_core
|
||||
@pytest.mark.asyncio
|
||||
async def test_bridge_tx_crosses_to_other_radio():
|
||||
"""RX on local -> TX on link; RX on link -> TX on local."""
|
||||
@@ -423,6 +437,7 @@ def test_an_older_core_keeps_the_behaviour_it_always_had():
|
||||
assert fabric.selector(b"frame") == "local"
|
||||
|
||||
|
||||
@needs_ingress_core
|
||||
def test_a_current_core_is_given_the_selector_that_routes_by_ingress_radio():
|
||||
from openhop_core.rf_fabric import RFFabric
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ from openhop_core.protocol.constants import (
|
||||
from openhop_core.rf_fabric import RFFabric
|
||||
|
||||
from repeater.airtime import AirtimeBudgets, AirtimeManager
|
||||
from repeater.config import _apply_fabric_tx_mode
|
||||
from repeater.config import _apply_fabric_tx_mode, fabric_selects_by_ingress_radio
|
||||
from repeater.engine import RepeaterHandler
|
||||
|
||||
WIDE = {
|
||||
@@ -189,16 +189,61 @@ def test_a_retune_keeps_what_is_already_on_the_air():
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _fabric(radio_ids, default_radio_id=None, tx_mode="default"):
|
||||
"""A real RFFabric with the real tx_mode selectors bound to it.
|
||||
CORE_SELECTS_BY_INGRESS = fabric_selects_by_ingress_radio(RFFabric())
|
||||
|
||||
Not a stub. The one review finding that survived two passes came from an
|
||||
engine that re-stated the fabric's dispatch rules and then diverged from
|
||||
them; a fixture that also re-states them cannot catch that. This registers
|
||||
real radios and runs `_apply_fabric_tx_mode`, so the answer the engine gets
|
||||
is the answer the node would get.
|
||||
|
||||
class _ContextFabric:
|
||||
"""The fabric contract these tests need, for a core that does not have it yet.
|
||||
|
||||
Used only when the installed openhop_core predates passing the ingress radio
|
||||
to a TX selector; with a current core every test below runs against the real
|
||||
RFFabric. What it implements is core's rule and nothing more -- an explicit
|
||||
radio_id wins, then the selector, then default_radio -- so the engine is
|
||||
still measured against the contract rather than against a convenience.
|
||||
"""
|
||||
fabric = RFFabric()
|
||||
|
||||
def __init__(self):
|
||||
self.radios = OrderedDict()
|
||||
self._default_radio_id = None
|
||||
self._selector = None
|
||||
|
||||
def register_radio(self, radio, *, radio_id):
|
||||
self.radios[radio_id] = radio
|
||||
if self._default_radio_id is None:
|
||||
self._default_radio_id = radio_id
|
||||
|
||||
def set_default_radio(self, radio_id):
|
||||
self._default_radio_id = radio_id
|
||||
|
||||
@property
|
||||
def default_radio_id(self):
|
||||
return self._default_radio_id
|
||||
|
||||
def get_radio(self, radio_id):
|
||||
return self.radios[radio_id]
|
||||
|
||||
def set_tx_selector(self, selector):
|
||||
self._selector = selector
|
||||
|
||||
def resolve_tx_radio_id(self, data, radio_id=None, *, rx_radio_id=None):
|
||||
if radio_id is not None:
|
||||
return radio_id
|
||||
if self._selector is not None:
|
||||
selected = self._selector(data, rx_radio_id)
|
||||
if selected is not None:
|
||||
return selected
|
||||
return self._default_radio_id
|
||||
|
||||
|
||||
def _fabric(radio_ids, default_radio_id=None, tx_mode="default"):
|
||||
"""A fabric with the real tx_mode selectors bound to it.
|
||||
|
||||
The real RFFabric where the installed core supports the ingress radio. Not a
|
||||
stub that restates the dispatch rules either way: the one review finding
|
||||
that survived two passes came from an engine that re-stated them and then
|
||||
diverged, and a fixture that also restates them cannot catch that.
|
||||
"""
|
||||
fabric = RFFabric() if CORE_SELECTS_BY_INGRESS else _ContextFabric()
|
||||
for radio_id in radio_ids:
|
||||
fabric.register_radio(object(), radio_id=radio_id)
|
||||
if default_radio_id in radio_ids:
|
||||
|
||||
Reference in new issue
Block a user