mirror of
https://github.com/esphome/esphome.git
synced 2026-05-28 13:37:24 +08:00
[tests] Fix flaky log assertion race in oversized payload tests (#14414)
This commit is contained in:
@@ -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 (
|
||||||
|
|||||||
Reference in New Issue
Block a user