[api] Check the encoded end against the reserved size under ESPHOME_DEBUG_API

The fixed32 store helper moves to a private section since it neither bounds checks nor
advances the cursor, its comment describes the path each target takes, the generated
file scan flags any ProtoEncode call that does not assign the cursor, and StateWaiter
timeouts can carry a label so gathered waits are told apart.
This commit is contained in:
J. Nick Koston
2026-09-07 15:57:10 +02:00
parent 7c774699d7
commit c4e1360cdf
6 changed files with 40 additions and 20 deletions
+5 -2
View File
@@ -78,7 +78,10 @@ class StateWaiter:
future.set_result(state)
async def expect(
self, predicate: Callable[[EntityState], bool], timeout: float = 5.0
self,
predicate: Callable[[EntityState], bool],
timeout: float = 5.0,
label: str | None = None,
) -> EntityState:
"""Wait for the next state matching ``predicate``; states seen before this call do not count."""
entry = (predicate, asyncio.get_running_loop().create_future())
@@ -88,7 +91,7 @@ class StateWaiter:
return await entry[1]
except TimeoutError:
raise TimeoutError(
f"no state matched {predicate} within {timeout}s"
f"no state matched {label or predicate} within {timeout}s"
) from None
finally:
self._waiters.remove(entry)
@@ -64,13 +64,15 @@ async def test_api_encode_boundaries(
isinstance(s, SensorState)
and s.key == sensor.key
and s.state == 12.5
)
),
label="sensor 12.5",
),
waiter.expect(
lambda s: (
isinstance(s, TextSensorState)
and s.key == text.key
and s.state == "y" * 200
)
),
label="text 200 x y",
),
)
@@ -387,6 +387,6 @@ def test_generated_encode_calls_keep_the_cursor() -> None:
dropped = [
line
for line in CPP_TEXT.splitlines()
if line.lstrip().startswith("ProtoEncode::")
if "ProtoEncode::" in line and "pos = ProtoEncode::" not in line
]
assert not dropped, dropped[:5]