[tests] Fix flaky log assertion race in oversized payload tests (#14414)

This commit is contained in:
J. Nick Koston
2026-03-02 11:48:50 -10:00
committed by GitHub
parent 727fa07377
commit 2e623fd6c3
+41 -28
View File
@@ -17,10 +17,10 @@ async def test_oversized_payload_plaintext(
) -> None: ) -> None:
"""Test that oversized payloads (>32768 bytes) from client cause disconnection without crashing.""" """Test that oversized payloads (>32768 bytes) from client cause disconnection without crashing."""
process_exited = False process_exited = False
helper_log_found = False helper_log_event = asyncio.Event()
def check_logs(line: str) -> None: def check_logs(line: str) -> None:
nonlocal process_exited, helper_log_found nonlocal process_exited
# Check for signs that the process exited/crashed # Check for signs that the process exited/crashed
if "Segmentation fault" in line or "core dumped" in line: if "Segmentation fault" in line or "core dumped" in line:
process_exited = True process_exited = True
@@ -30,7 +30,7 @@ async def test_oversized_payload_plaintext(
and "Bad packet: message size" in line and "Bad packet: message size" in line
and "exceeds maximum" in line and "exceeds maximum" in line
): ):
helper_log_found = True helper_log_event.set()
async with run_compiled(yaml_config, line_callback=check_logs): async with run_compiled(yaml_config, line_callback=check_logs):
async with api_client_connected_with_disconnect() as (client, disconnect_event): async with api_client_connected_with_disconnect() as (client, disconnect_event):
@@ -54,10 +54,13 @@ async def test_oversized_payload_plaintext(
# After disconnection, verify process didn't crash # After disconnection, verify process didn't crash
assert not process_exited, "ESPHome process should not crash" assert not process_exited, "ESPHome process should not crash"
# Verify we saw the expected HELPER_LOG message # Wait for the expected log message (may arrive after disconnect event)
assert helper_log_found, ( try:
"Expected to see HELPER_LOG about message size exceeding maximum" await asyncio.wait_for(helper_log_event.wait(), timeout=2.0)
) except TimeoutError:
pytest.fail(
"Expected to see HELPER_LOG about message size exceeding maximum"
)
# Try to reconnect to verify the process is still running # Try to reconnect to verify the process is still running
async with api_client_connected_with_disconnect() as (client2, _): async with api_client_connected_with_disconnect() as (client2, _):
@@ -77,10 +80,10 @@ async def test_oversized_protobuf_message_id_plaintext(
This tests the message type limit - message IDs must fit in a uint16_t (0-65535). This tests the message type limit - message IDs must fit in a uint16_t (0-65535).
""" """
process_exited = False process_exited = False
helper_log_found = False helper_log_event = asyncio.Event()
def check_logs(line: str) -> None: def check_logs(line: str) -> None:
nonlocal process_exited, helper_log_found nonlocal process_exited
# Check for signs that the process exited/crashed # Check for signs that the process exited/crashed
if "Segmentation fault" in line or "core dumped" in line: if "Segmentation fault" in line or "core dumped" in line:
process_exited = True process_exited = True
@@ -90,7 +93,7 @@ async def test_oversized_protobuf_message_id_plaintext(
and "Bad packet: message type" in line and "Bad packet: message type" in line
and "exceeds maximum" in line and "exceeds maximum" in line
): ):
helper_log_found = True helper_log_event.set()
async with run_compiled(yaml_config, line_callback=check_logs): async with run_compiled(yaml_config, line_callback=check_logs):
async with api_client_connected_with_disconnect() as (client, disconnect_event): async with api_client_connected_with_disconnect() as (client, disconnect_event):
@@ -114,10 +117,13 @@ async def test_oversized_protobuf_message_id_plaintext(
# After disconnection, verify process didn't crash # After disconnection, verify process didn't crash
assert not process_exited, "ESPHome process should not crash" assert not process_exited, "ESPHome process should not crash"
# Verify we saw the expected HELPER_LOG message # Wait for the expected log message (may arrive after disconnect event)
assert helper_log_found, ( try:
"Expected to see HELPER_LOG about message type exceeding maximum" await asyncio.wait_for(helper_log_event.wait(), timeout=2.0)
) except TimeoutError:
pytest.fail(
"Expected to see HELPER_LOG about message type exceeding maximum"
)
# Try to reconnect to verify the process is still running # Try to reconnect to verify the process is still running
async with api_client_connected_with_disconnect() as (client2, _): async with api_client_connected_with_disconnect() as (client2, _):
@@ -135,10 +141,10 @@ async def test_oversized_payload_noise(
"""Test that oversized payloads from client cause disconnection without crashing with noise encryption.""" """Test that oversized payloads from client cause disconnection without crashing with noise encryption."""
noise_key = "N4Yle5YirwZhPiHHsdZLdOA73ndj/84veVaLhTvxCuU=" noise_key = "N4Yle5YirwZhPiHHsdZLdOA73ndj/84veVaLhTvxCuU="
process_exited = False process_exited = False
helper_log_found = False helper_log_event = asyncio.Event()
def check_logs(line: str) -> None: def check_logs(line: str) -> None:
nonlocal process_exited, helper_log_found nonlocal process_exited
# Check for signs that the process exited/crashed # Check for signs that the process exited/crashed
if "Segmentation fault" in line or "core dumped" in line: if "Segmentation fault" in line or "core dumped" in line:
process_exited = True process_exited = True
@@ -149,7 +155,7 @@ async def test_oversized_payload_noise(
and "Bad packet: message size" in line and "Bad packet: message size" in line
and "exceeds maximum" in line and "exceeds maximum" in line
): ):
helper_log_found = True helper_log_event.set()
async with run_compiled(yaml_config, line_callback=check_logs): async with run_compiled(yaml_config, line_callback=check_logs):
async with api_client_connected_with_disconnect(noise_psk=noise_key) as ( async with api_client_connected_with_disconnect(noise_psk=noise_key) as (
@@ -177,10 +183,13 @@ async def test_oversized_payload_noise(
# After disconnection, verify process didn't crash # After disconnection, verify process didn't crash
assert not process_exited, "ESPHome process should not crash" assert not process_exited, "ESPHome process should not crash"
# Verify we saw the expected HELPER_LOG message # Wait for the expected log message (may arrive after disconnect event)
assert helper_log_found, ( try:
"Expected to see HELPER_LOG about message size exceeding maximum" await asyncio.wait_for(helper_log_event.wait(), timeout=2.0)
) except TimeoutError:
pytest.fail(
"Expected to see HELPER_LOG about message size exceeding maximum"
)
# Try to reconnect to verify the process is still running # Try to reconnect to verify the process is still running
async with api_client_connected_with_disconnect(noise_psk=noise_key) as ( async with api_client_connected_with_disconnect(noise_psk=noise_key) as (
@@ -274,10 +283,10 @@ async def test_noise_corrupt_encrypted_frame(
""" """
noise_key = "N4Yle5YirwZhPiHHsdZLdOA73ndj/84veVaLhTvxCuU=" noise_key = "N4Yle5YirwZhPiHHsdZLdOA73ndj/84veVaLhTvxCuU="
process_exited = False process_exited = False
cipherstate_failed = False cipherstate_event = asyncio.Event()
def check_logs(line: str) -> None: def check_logs(line: str) -> None:
nonlocal process_exited, cipherstate_failed nonlocal process_exited
# Check for signs that the process exited/crashed # Check for signs that the process exited/crashed
if "Segmentation fault" in line or "core dumped" in line: if "Segmentation fault" in line or "core dumped" in line:
process_exited = True process_exited = True
@@ -290,7 +299,7 @@ async def test_noise_corrupt_encrypted_frame(
"[W][api.connection" in line "[W][api.connection" in line
and "Reading failed CIPHERSTATE_DECRYPT_FAILED" in line and "Reading failed CIPHERSTATE_DECRYPT_FAILED" in line
): ):
cipherstate_failed = True cipherstate_event.set()
async with run_compiled(yaml_config, line_callback=check_logs): async with run_compiled(yaml_config, line_callback=check_logs):
async with api_client_connected_with_disconnect(noise_psk=noise_key) as ( async with api_client_connected_with_disconnect(noise_psk=noise_key) as (
@@ -326,10 +335,14 @@ async def test_noise_corrupt_encrypted_frame(
assert not process_exited, ( assert not process_exited, (
"ESPHome process should not crash on corrupt encrypted frames" "ESPHome process should not crash on corrupt encrypted frames"
) )
# Verify we saw the expected log message about decryption failure # Wait for the expected log message (may arrive after disconnect event)
assert cipherstate_failed, ( try:
"Expected to see log about noise_cipherstate_decrypt failure or CIPHERSTATE_DECRYPT_FAILED" await asyncio.wait_for(cipherstate_event.wait(), timeout=2.0)
) except TimeoutError:
pytest.fail(
"Expected to see log about noise_cipherstate_decrypt failure"
" or CIPHERSTATE_DECRYPT_FAILED"
)
# Verify we can still reconnect after handling the corrupt frame # Verify we can still reconnect after handling the corrupt frame
async with api_client_connected_with_disconnect(noise_psk=noise_key) as ( async with api_client_connected_with_disconnect(noise_psk=noise_key) as (