b4fbd6fe9f
Deploy Site / deploy-vercel (push) Has been skipped
Deploy Site / deploy-docs (push) Has been skipped
Build Skills Index / build-index (push) Has been skipped
CI / Deny unrelated histories (push) Has been skipped
CI / Detect affected areas (push) Successful in 27m35s
CI / OSV scan (push) Failing after 4s
CI / Build&Test Docker image (push) Successful in 9s
CI / Supply-chain scan (push) Has been skipped
CI / Lint Docker scripts (push) Failing after 5m13s
CI / Check contributors (push) Failing after 12m8s
CI / Docs Site (push) Failing after 12m8s
CI / TypeScript (push) Failing after 12m8s
CI / Python lints (push) Failing after 12m9s
CI / Python tests (push) Failing after 12m9s
CI / Check uv.lock (push) Failing after 23m22s
CI / CI timing report (push) Has been cancelled
Build Skills Index / trigger-deploy (push) Has been cancelled
CI / All required checks pass (push) Has been cancelled
115 lines
3.5 KiB
Python
115 lines
3.5 KiB
Python
"""Tests for tui_gateway.loop_noise — the WS peer-hangup teardown filter (#50005)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
|
|
import pytest
|
|
|
|
from tui_gateway.loop_noise import (
|
|
_is_benign_teardown,
|
|
install_loop_noise_filter,
|
|
)
|
|
|
|
|
|
class _FakeConnectionLostCallback:
|
|
"""Stand-in whose repr matches asyncio's ``_call_connection_lost`` flood."""
|
|
|
|
def __repr__(self) -> str:
|
|
return "<Handle _ProactorBasePipeTransport._call_connection_lost(None)>"
|
|
|
|
|
|
def test_benign_teardown_matches_reset_in_connection_lost():
|
|
ctx = {
|
|
"exception": ConnectionResetError(10054, "forcibly closed"),
|
|
"handle": _FakeConnectionLostCallback(),
|
|
}
|
|
assert _is_benign_teardown(ctx) is True
|
|
|
|
|
|
def test_benign_teardown_matches_aborted_and_broken_pipe():
|
|
for exc in (
|
|
ConnectionAbortedError(10053, "aborted"),
|
|
BrokenPipeError("epipe"),
|
|
):
|
|
ctx = {"exception": exc, "callback": _FakeConnectionLostCallback()}
|
|
assert _is_benign_teardown(ctx) is True
|
|
|
|
|
|
def test_reset_outside_connection_lost_is_not_suppressed():
|
|
# Same error type, but NOT from the connection-lost teardown path — must
|
|
# fall through to the default handler.
|
|
ctx = {
|
|
"exception": ConnectionResetError("reset in a real handler"),
|
|
"handle": "<Handle some_other_handler()>",
|
|
}
|
|
assert _is_benign_teardown(ctx) is False
|
|
|
|
|
|
def test_unrelated_exception_is_not_suppressed():
|
|
ctx = {
|
|
"exception": ValueError("boom"),
|
|
"handle": _FakeConnectionLostCallback(),
|
|
}
|
|
assert _is_benign_teardown(ctx) is False
|
|
|
|
|
|
def test_no_exception_is_not_suppressed():
|
|
assert _is_benign_teardown({"message": "loop warning, no exc"}) is False
|
|
|
|
|
|
def test_install_suppresses_flood_and_forwards_real_errors():
|
|
loop = asyncio.new_event_loop()
|
|
try:
|
|
forwarded: list[dict] = []
|
|
loop.set_exception_handler(lambda _loop, ctx: forwarded.append(ctx))
|
|
|
|
install_loop_noise_filter(loop)
|
|
|
|
# Benign teardown flood → swallowed, not forwarded.
|
|
loop.call_exception_handler(
|
|
{
|
|
"exception": ConnectionResetError(10054, "forcibly closed"),
|
|
"handle": _FakeConnectionLostCallback(),
|
|
}
|
|
)
|
|
assert forwarded == []
|
|
|
|
# Genuine loop error → forwarded to the previous handler unchanged.
|
|
real_ctx = {"exception": RuntimeError("genuine loop bug")}
|
|
loop.call_exception_handler(real_ctx)
|
|
assert len(forwarded) == 1
|
|
assert forwarded[0] is real_ctx
|
|
finally:
|
|
loop.close()
|
|
|
|
|
|
def test_install_is_idempotent():
|
|
loop = asyncio.new_event_loop()
|
|
try:
|
|
install_loop_noise_filter(loop)
|
|
first = loop.get_exception_handler()
|
|
install_loop_noise_filter(loop)
|
|
# Second install must NOT wrap again — same handler object.
|
|
assert loop.get_exception_handler() is first
|
|
finally:
|
|
loop.close()
|
|
|
|
|
|
def test_install_falls_back_to_default_handler_when_none_set():
|
|
loop = asyncio.new_event_loop()
|
|
try:
|
|
# No previous handler installed; benign flood still swallowed, and a
|
|
# real error must not raise out of the filter.
|
|
install_loop_noise_filter(loop)
|
|
loop.call_exception_handler(
|
|
{
|
|
"exception": ConnectionResetError(10054, "reset"),
|
|
"handle": _FakeConnectionLostCallback(),
|
|
}
|
|
)
|
|
# A genuine error routes to default_exception_handler — should not raise.
|
|
loop.call_exception_handler({"message": "some loop warning"})
|
|
finally:
|
|
loop.close()
|