emdashes begone (#10847)

This commit is contained in:
Tom
2026-07-01 19:01:27 -05:00
committed by GitHub
co-authored by GitHub
parent dee94e0758
commit 3becaf2d95
276 changed files with 1795 additions and 1793 deletions
+7 -7
View File
@@ -1,7 +1,7 @@
"""Shared helper for mesh receive tests.
`pio device monitor` captures firmware log output, which does NOT include
decoded text message contents or telemetry payloads those are only
decoded text message contents or telemetry payloads - those are only
accessible through `meshtastic.SerialInterface`'s pubsub mechanism.
`ReceiveCollector` opens a long-lived SerialInterface on a port, subscribes
@@ -9,7 +9,7 @@ to the pubsub topic of interest, and exposes an atomic `wait_for(predicate)`
that mesh tests use to verify end-to-end delivery.
This module also exposes two module-level helpers for forcing a device to
broadcast a fresh NodeInfo the on-demand path that sidesteps the
broadcast a fresh NodeInfo - the on-demand path that sidesteps the
firmware's 10-minute NodeInfo rate-limit. Tests doing directed PKI-encrypted
sends need BOTH endpoints to hold current pubkeys for each other:
@@ -31,7 +31,7 @@ from typing import Any, Callable
def nudge_nodeinfo(iface: Any) -> None:
"""Force the device behind ``iface`` to broadcast a fresh NodeInfo.
Sends a ``ToRadio.Heartbeat(nonce=1)`` the firmware's documented
Sends a ``ToRadio.Heartbeat(nonce=1)`` - the firmware's documented
on-demand NodeInfo trigger (see `src/mesh/api/PacketAPI.cpp:74-79`
for TCP/UDP and `src/mesh/PhoneAPI.cpp::handleToRadio` for serial,
both routed to `NodeInfoModule::sendOurNodeInfo(..., shorterTimeout=true)`
@@ -49,7 +49,7 @@ def nudge_nodeinfo(iface: Any) -> None:
def nudge_nodeinfo_port(port: str) -> None:
"""Open ``port`` briefly, nudge, close for when no iface is open yet.
"""Open ``port`` briefly, nudge, close - for when no iface is open yet.
Uses the meshtastic_mcp port-lock-aware `connect()` context manager
so we don't race ReceiveCollector or other long-lived handles on
@@ -99,7 +99,7 @@ class ReceiveCollector:
)
from pubsub import pub # type: ignore[import-untyped]
# pubsub uses weak refs by default we stash a strong ref so the
# pubsub uses weak refs by default - we stash a strong ref so the
# handler doesn't disappear between subscribe and wait_for.
def handler(packet: dict, interface: Any) -> None:
with self._lock:
@@ -169,7 +169,7 @@ class ReceiveCollector:
"""Send a text packet through the already-open SerialInterface.
Use this when a test also has a ReceiveCollector open on the same port
`admin.send_text(port=...)` would try to open a second SerialInterface
- `admin.send_text(port=...)` would try to open a second SerialInterface
and fail the port lock.
"""
if self._iface is None:
@@ -187,7 +187,7 @@ class ReceiveCollector:
Thin wrapper around the module-level :func:`nudge_nodeinfo` that
also validates the context-manager invariant. Delegates so tests
that need to nudge BOTH sides (bilateral PKI warmup) share one
implementation the caller just passes each iface in turn.
implementation - the caller just passes each iface in turn.
Firmware-side details (rate-limit bypass, nonce==1 trigger path,
shorterTimeout=true window) are documented on the module-level
+2 -2
View File
@@ -4,7 +4,7 @@ Opens a ReceiveCollector on EVERY role, sends a uniquely-tagged broadcast
from each role in turn, and asserts every OTHER role saw it. One atomic
test that answers "is the mesh actually working both directions?".
Not parametrized it inherently involves the full hub.
Not parametrized - it inherently involves the full hub.
"""
from __future__ import annotations
@@ -44,7 +44,7 @@ def test_bidirectional_mesh_communication(
time.sleep(2.0)
# From each role, send a uniquely-tagged broadcast. We MUST send through
# the already-open collector opening a new SerialInterface here would
# the already-open collector - opening a new SerialInterface here would
# race the collector's exclusive lock on the port.
tags: dict[str, str] = {}
for sender in roles:
@@ -1,7 +1,7 @@
"""Mesh: broadcast text from TX arrives at RX.
Uses `meshtastic.SerialInterface` pubsub on RX to detect the decoded text
packet `pio device monitor` output doesn't include message bodies.
packet - `pio device monitor` output doesn't include message bodies.
"""
from __future__ import annotations
@@ -38,7 +38,7 @@ def test_direct_with_ack_roundtrip(
unique = f"mcp-ack-{tx_role}-to-{rx_role}-{int(time.time())}"
# TX iface stays open across the RX wait sendText+wantAck relies on
# TX iface stays open across the RX wait - sendText+wantAck relies on
# the firmware's retransmit loop, which races the SerialInterface close.
# Bilateral NodeInfo nudge: directed packets are PKI-encrypted, so BOTH
# sides need current pubkeys (err=35/39 otherwise). See
@@ -72,7 +72,7 @@ def test_direct_with_ack_roundtrip(
)
# Retry covers LoRa collisions. Re-nudge both sides between
# attempts if RX's cached TX pubkey is stale, just re-sending
# attempts - if RX's cached TX pubkey is stale, just re-sending
# the text doesn't heal it; re-broadcasting NodeInfo does.
got = None
for _attempt in range(2):
+1 -1
View File
@@ -16,7 +16,7 @@ from meshtastic_mcp.connection import connect
@pytest.mark.timeout(180)
def test_mesh_formation_within_60s(mesh_pair: dict[str, Any], wait_until) -> None:
"""Runs for every directed role pair so we prove `A sees B in its node
"""Runs for every directed role pair - so we prove `A sees B in its node
DB` AND `B sees A in its node DB` independently. A one-sided pass can
mask a real problem (e.g. device A's RX works but its TX is dead).
"""
@@ -11,15 +11,15 @@ mesh exercises:
* when the established relay drops and returns, delivery recovers rather than
black-holing (the M3 stale-route decay / re-learn path).
TOPOLOGY REQUIREMENT why this usually SKIPS:
TOPOLOGY REQUIREMENT - why this usually SKIPS:
A NextHop relay only happens when the two endpoints are NOT direct neighbors.
Three co-located radios all hear each other, so A→C is a single direct hop and
next_hop never engages. To run this test the bench must be a *line* A B C
with the endpoints out of each other's direct RF range (physical distance or
next_hop never engages. To run this test the bench must be a *line* - A - B - C
- with the endpoints out of each other's direct RF range (physical distance or
attenuators). The `multihop_topology` fixture detects this automatically: it
warms the mesh, looks for a pair that is ≥1 hop apart, confirms the relay via
traceroute, and `pytest.skip`s cleanly when the bench is all-direct. So this
file is safe to commit and run anywhere it only *asserts* when the topology
file is safe to commit and run anywhere - it only *asserts* when the topology
genuinely requires a relay.
REQUIREMENTS:
@@ -56,12 +56,12 @@ def _hops_away(rec: dict[str, Any]) -> int | None:
def _warm_mesh(ports: list[str], rounds: int = 2, settle: float = 6.0) -> None:
"""Flood a fresh NodeInfo from every node so the whole mesh (including
multi-hop pairs, reached via relayed broadcasts) populates pubkeys and hop
distances. Best-effort a single node failing to nudge shouldn't abort."""
distances. Best-effort - a single node failing to nudge shouldn't abort."""
for _ in range(rounds):
for port in ports:
try:
nudge_nodeinfo_port(port)
except Exception: # noqa: BLE001 warmup is best-effort
except Exception: # noqa: BLE001 - warmup is best-effort
pass
time.sleep(0.5)
time.sleep(settle)
@@ -135,7 +135,7 @@ def multihop_topology(baked_mesh: dict[str, Any]) -> dict[str, Any]:
_warm_mesh([port for port, _ in by_role.values()])
# Find an ordered pair that is ≥1 hop apart, using each node's own nodeDB
# (cheap no traceroute yet). On an all-direct bench nothing qualifies.
# (cheap - no traceroute yet). On an all-direct bench nothing qualifies.
multihop_pair: tuple[str, str] | None = None
for a_role in roles:
a_port, _ = by_role[a_role]
@@ -157,8 +157,8 @@ def multihop_topology(baked_mesh: dict[str, Any]) -> dict[str, Any]:
if not multihop_pair:
pytest.skip(
"no multi-hop pair found every device appears to be a direct "
"neighbor. Arrange the bench as a line (A B C) with the "
"no multi-hop pair found - every device appears to be a direct "
"neighbor. Arrange the bench as a line (A - B - C) with the "
"endpoints out of direct RF range (distance or attenuators) so a "
"relay is actually required, then re-run."
)
@@ -231,21 +231,21 @@ def test_multihop_dm_delivers(multihop_topology: dict[str, Any]) -> None:
assert got is not None, (
f"multi-hop directed DM {tx_role}{rx_role} via relay "
f"{relay_role!r} never landed NextHop multi-hop delivery is broken"
f"{relay_role!r} never landed - NextHop multi-hop delivery is broken"
)
@pytest.mark.timeout(600)
def test_multihop_relay_recovery(
multihop_topology: dict[str, Any],
power_cycle, # noqa: ARG001 forces the uhubctl-availability skip
power_cycle, # noqa: ARG001 - forces the uhubctl-availability skip
) -> None:
"""Delivery recovers after the established relay drops and returns.
Establishes a baseline DM (route via relay learned), powers the relay OFF
(confirming TX survives sending across a downed relay), then powers it back
ON and asserts directed delivery resumes the M3 stale-route decay /
re-learn path. With a strict A B C line there is no path while B is down,
ON and asserts directed delivery resumes - the M3 stale-route decay /
re-learn path. With a strict A - B - C line there is no path while B is down,
so we only assert TX doesn't crash during the outage; the delivery assertion
is after B returns.
"""
@@ -266,7 +266,7 @@ def test_multihop_relay_recovery(
post = f"mh-recover-post-{int(time.time())}"
# Baseline: confirm delivery works (so the route via the relay is learned)
# before we perturb anything otherwise a later failure is ambiguous.
# before we perturb anything - otherwise a later failure is ambiguous.
with ReceiveCollector(rx_port, topic="meshtastic.receive.text") as rx:
rx.broadcast_nodeinfo_ping()
with connect(port=tx_port) as tx_iface:
@@ -279,7 +279,7 @@ def test_multihop_relay_recovery(
lambda p: p.get("decoded", {}).get("text") == base, timeout=45
)
is not None
), "baseline multi-hop delivery failed skipping recovery to avoid a false result"
), "baseline multi-hop delivery failed - skipping recovery to avoid a false result"
# Power the relay OFF.
try:
@@ -304,7 +304,7 @@ def test_multihop_relay_recovery(
)
assert pkt is not None
time.sleep(8.0) # let retransmissions + route decay run
except Exception as exc: # noqa: BLE001 restore bench state before failing
except Exception as exc: # noqa: BLE001 - restore bench state before failing
_power.power_on(relay_role)
resolve_port_by_role(relay_role, timeout_s=30.0)
raise AssertionError(
@@ -316,7 +316,7 @@ def test_multihop_relay_recovery(
time.sleep(0.5)
try:
resolve_port_by_role(relay_role, timeout_s=30.0)
except Exception: # noqa: BLE001 relay port isn't one we connect to directly
except Exception: # noqa: BLE001 - relay port isn't one we connect to directly
pass
time.sleep(8.0)
_warm_mesh([tx_port, rx_port], rounds=1) # re-flood so the relay re-learns
@@ -343,5 +343,5 @@ def test_multihop_relay_recovery(
assert got is not None, (
f"after relay {relay_role!r} returned, multi-hop DM {tx_role}{rx_role} "
"never resumed stale-route recovery (M3) may be broken"
"never resumed - stale-route recovery (M3) may be broken"
)
@@ -5,16 +5,16 @@ off mid-send via uhubctl, then powered back on.
Flow (parametrized over every directed mesh_pair):
1. Bilateral PKI warmup (same pattern as test_direct_with_ack).
2. TX sends a broadcast text "msg-1" RX confirms receipt via pubsub.
2. TX sends a broadcast text "msg-1" - RX confirms receipt via pubsub.
3. Power OFF RX via uhubctl. The RX device disappears from the OS.
4. TX sends a directed text "msg-2" with wantAck=True. Firmware retries
internally for ~30s before giving up. Assertion: the packet object
was accepted by the TX stack (non-None) we don't assert an ACK
was accepted by the TX stack (non-None) - we don't assert an ACK
since there's no peer to send one.
5. Power ON RX. Wait for re-enumeration + boot.
6. Bilateral PKI re-nudge RX's in-RAM PKI cache was wiped on reboot,
6. Bilateral PKI re-nudge - RX's in-RAM PKI cache was wiped on reboot,
so the first directed send may err=35 without a fresh NodeInfo ping.
7. TX sends a directed "msg-3" RX receives it via pubsub, confirming
7. TX sends a directed "msg-3" - RX receives it via pubsub, confirming
the mesh recovered.
Skips cleanly if uhubctl isn't installed (via the `power_cycle` fixture's
@@ -38,7 +38,7 @@ from ._receive import ReceiveCollector, nudge_nodeinfo
@pytest.mark.timeout(360)
def test_peer_offline_then_recovers(
mesh_pair: dict[str, Any],
power_cycle, # noqa: ARG001 forces uhubctl-availability skip
power_cycle, # noqa: ARG001 - forces uhubctl-availability skip
hub_devices: dict[str, str],
) -> None:
tx_port = mesh_pair["tx"]["port"]
@@ -80,7 +80,7 @@ def test_peer_offline_then_recovers(
timeout=30,
)
assert got is not None, (
f"baseline directed send ({tx_role}{rx_role}) didn't land "
f"baseline directed send ({tx_role}{rx_role}) didn't land - "
"skipping offline test to avoid false positive"
)
@@ -111,7 +111,7 @@ def test_peer_offline_then_recovers(
assert packet is not None
# Give firmware a moment to do a retry or two while RX is down.
time.sleep(5.0)
except Exception as exc: # noqa: BLE001 TX should survive the peer being gone
except Exception as exc: # noqa: BLE001 - TX should survive the peer being gone
# Restore RX before reraising so the bench state is sane.
_power.power_on(rx_role)
resolve_port_by_role(rx_role, timeout_s=30.0)
@@ -151,5 +151,5 @@ def test_peer_offline_then_recovers(
assert got is not None, (
f"post-recovery directed send {unique_post!r} ({tx_role}{rx_role}) "
"never landed recovery path may be broken"
"never landed - recovery path may be broken"
)
+5 -5
View File
@@ -33,13 +33,13 @@ def test_traceroute_one_hop(mesh_pair: dict[str, Any]) -> None:
Why the listener is on TX (not RX):
The traceroute RESPONSE is addressed to TX (the original requester).
The meshtastic Python client publishes `meshtastic.receive.traceroute`
on the interface that received that response which is TX's iface.
on the interface that received that response - which is TX's iface.
A listener on RX would only see the inbound REQUEST, which lacks
the SNR-towards / SNR-back fields the firmware only fills on reply.
Why we ping RX's NodeInfo before sending:
Traceroute requests are directed sends (wantResponse=True, specific
destinationId) subject to the same PKI_SEND_FAIL_PUBLIC_KEY trap
destinationId) - subject to the same PKI_SEND_FAIL_PUBLIC_KEY trap
as `test_direct_with_ack`. We open RX briefly to trigger the
on-demand NodeInfo broadcast, then wait for TX's nodesByNum to
populate RX's publicKey before calling sendTraceRoute.
@@ -54,7 +54,7 @@ def test_traceroute_one_hop(mesh_pair: dict[str, Any]) -> None:
with ReceiveCollector(
tx_port, topic="meshtastic.receive.traceroute"
) as tx_listener:
# Bilateral PKI warmup traceroute requests are directed and
# Bilateral PKI warmup - traceroute requests are directed and
# PKI-encrypted, so both sides need current pubkeys. See
# `_receive.py::nudge_nodeinfo` and the test_direct_with_ack
# comment for the full rationale (one-sided nudge lets err=35
@@ -105,7 +105,7 @@ def test_traceroute_one_hop(mesh_pair: dict[str, Any]) -> None:
)
# sendTraceRoute already waited for the response internally, but
# pubsub dispatch runs on the meshtastic-python reader thread
# pubsub dispatch runs on the meshtastic-python reader thread -
# give it a short grace window to queue the packet.
packet = tx_listener.wait_for(
lambda p: p.get("from") == rx_node_num,
@@ -138,7 +138,7 @@ def test_traceroute_one_hop(mesh_pair: dict[str, Any]) -> None:
f"traceroute `routeBack` should be empty on a 2-device direct "
f"mesh; got {back_hops!r}"
)
# `snr_towards` has len(route) + 1 entries one per hop plus a final
# `snr_towards` has len(route) + 1 entries - one per hop plus a final
# entry for the destination's receive SNR. Direct mesh → len(route)
# is 0 → exactly 1 SNR entry.
assert len(snr_towards) == 1, (