Skip to content

Commit 06e3b36

Browse files
authored
Improve connection reuse test coverage (#10949)
1 parent 3c88f81 commit 06e3b36

File tree

2 files changed

+128
-0
lines changed

2 files changed

+128
-0
lines changed

tests/test_client_functional.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4382,3 +4382,128 @@ async def handler(request: web.Request) -> web.Response:
43824382
response.raise_for_status()
43834383

43844384
assert len(client._session.connector._conns) == 1
4385+
4386+
4387+
async def test_post_content_exception_connection_kept(
4388+
aiohttp_client: AiohttpClient,
4389+
) -> None:
4390+
"""Test that connections are kept after content.set_exception() with POST."""
4391+
4392+
async def handler(request: web.Request) -> web.Response:
4393+
await request.read()
4394+
return web.Response(
4395+
body=b"x" * 1000
4396+
) # Larger response to ensure it's not pre-buffered
4397+
4398+
app = web.Application()
4399+
app.router.add_post("/", handler)
4400+
client = await aiohttp_client(app)
4401+
4402+
# POST request with body - connection should be closed after content exception
4403+
resp = await client.post("/", data=b"request body")
4404+
4405+
with pytest.raises(RuntimeError):
4406+
async with resp:
4407+
assert resp.status == 200
4408+
resp.content.set_exception(RuntimeError("Simulated error"))
4409+
await resp.read()
4410+
4411+
assert resp.closed
4412+
4413+
# Wait for any pending operations to complete
4414+
await resp.wait_for_close()
4415+
4416+
assert client._session.connector is not None
4417+
# Connection is kept because content.set_exception() is a client-side operation
4418+
# that doesn't affect the underlying connection state
4419+
assert len(client._session.connector._conns) == 1
4420+
4421+
4422+
async def test_network_error_connection_closed(
4423+
aiohttp_client: AiohttpClient,
4424+
) -> None:
4425+
"""Test that connections are closed after network errors."""
4426+
4427+
async def handler(request: web.Request) -> NoReturn:
4428+
# Read the request body
4429+
await request.read()
4430+
4431+
# Start sending response but close connection before completing
4432+
response = web.StreamResponse()
4433+
response.content_length = 1000 # Promise 1000 bytes
4434+
await response.prepare(request)
4435+
4436+
# Send partial data then force close the connection
4437+
await response.write(b"x" * 100) # Only send 100 bytes
4438+
# Force close the transport to simulate network error
4439+
assert request.transport is not None
4440+
request.transport.close()
4441+
assert False, "Will not return"
4442+
4443+
app = web.Application()
4444+
app.router.add_post("/", handler)
4445+
client = await aiohttp_client(app)
4446+
4447+
# POST request that will fail due to network error
4448+
with pytest.raises(aiohttp.ClientPayloadError):
4449+
resp = await client.post("/", data=b"request body")
4450+
async with resp:
4451+
await resp.read() # This should fail
4452+
4453+
# Give event loop a chance to process connection cleanup
4454+
await asyncio.sleep(0)
4455+
4456+
assert client._session.connector is not None
4457+
# Connection should be closed due to network error
4458+
assert len(client._session.connector._conns) == 0
4459+
4460+
4461+
async def test_client_side_network_error_connection_closed(
4462+
aiohttp_client: AiohttpClient,
4463+
) -> None:
4464+
"""Test that connections are closed after client-side network errors."""
4465+
handler_done = asyncio.Event()
4466+
4467+
async def handler(request: web.Request) -> NoReturn:
4468+
# Read the request body
4469+
await request.read()
4470+
4471+
# Start sending a large response
4472+
response = web.StreamResponse()
4473+
response.content_length = 10000 # Promise 10KB
4474+
await response.prepare(request)
4475+
4476+
# Send some data
4477+
await response.write(b"x" * 1000)
4478+
4479+
# Keep the response open - we'll interrupt from client side
4480+
await asyncio.wait_for(handler_done.wait(), timeout=5.0)
4481+
assert False, "Will not return"
4482+
4483+
app = web.Application()
4484+
app.router.add_post("/", handler)
4485+
client = await aiohttp_client(app)
4486+
4487+
# POST request that will fail due to client-side network error
4488+
with pytest.raises(aiohttp.ClientPayloadError):
4489+
resp = await client.post("/", data=b"request body")
4490+
async with resp:
4491+
# Simulate client-side network error by closing the transport
4492+
# This simulates connection reset, network failure, etc.
4493+
assert resp.connection is not None
4494+
assert resp.connection.protocol is not None
4495+
assert resp.connection.protocol.transport is not None
4496+
resp.connection.protocol.transport.close()
4497+
4498+
# This should fail with connection error
4499+
await resp.read()
4500+
4501+
# Signal handler to finish
4502+
handler_done.set()
4503+
4504+
# Give event loop a chance to process connection cleanup
4505+
await asyncio.sleep(0)
4506+
4507+
assert client._session.connector is not None
4508+
# Connection should be closed due to client-side network error
4509+
assert len(client._session.connector._conns) == 0

tests/test_web_functional.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1945,6 +1945,9 @@ async def handler(request: web.Request) -> web.Response:
19451945
await resp.read()
19461946
assert resp.closed
19471947

1948+
# Wait for any pending operations to complete
1949+
await resp.wait_for_close()
1950+
19481951
assert session._connector is not None
19491952
assert len(session._connector._conns) == 1
19501953

0 commit comments

Comments
 (0)